Skip to main content

shuyi_app/storage/
mod.rs

1//! Platform [`Storage`] backends (ticket 0207, game ADR-0009): files in the OS
2//! data directory on native, `localStorage` on web.
3
4#[cfg(not(target_arch = "wasm32"))]
5mod native;
6#[cfg(target_arch = "wasm32")]
7mod web;
8
9#[cfg(not(target_arch = "wasm32"))]
10pub use native::FileStorage;
11#[cfg(target_arch = "wasm32")]
12pub use web::WebStorage;
13
14#[cfg(not(target_arch = "wasm32"))]
15pub use native::data_dir;
16
17use shuyi_ui::storage::Storage;
18
19/// The storage backend for this platform, for the game whose storage is
20/// called `name` ([`AppConfig::storage_name`](crate::AppConfig)). Native
21/// falls back to an in-memory store (nothing persists) if the OS has no
22/// usable data directory (the relevant environment variable is unset,
23/// which only happens on unsupported or misconfigured systems); it logs an
24/// error when that happens.
25pub fn platform(name: &str) -> Box<dyn Storage> {
26    #[cfg(not(target_arch = "wasm32"))]
27    {
28        match FileStorage::new(name) {
29            Ok(storage) => Box::new(storage),
30            Err(e) => {
31                macroquad::prelude::error!("storage: {e}; saves and settings will not persist");
32                Box::new(shuyi_ui::storage::MemoryStorage::new())
33            }
34        }
35    }
36    #[cfg(target_arch = "wasm32")]
37    {
38        Box::new(WebStorage::new(name))
39    }
40}
41
42/// The prefix of every key the web build keeps in `localStorage` for the
43/// game whose storage is called `name`: `name/`.
44pub fn web_prefix(name: &str) -> String {
45    format!("{name}/")
46}
47
48/// The key [`smoke_check`] writes and removes.
49const SMOKE_TEST_KEY: &str = "smoke_test";
50
51/// A startup check, which the backend runs in debug builds as it hands the
52/// game its storage: writes and reads back a throwaway key and logs the
53/// result, then removes it.
54pub fn smoke_check(storage: &mut dyn Storage) {
55    use macroquad::prelude::{error, info};
56
57    match run_smoke_check(storage) {
58        Ok(()) => {
59            info!("storage: smoke check passed");
60        }
61        Err(e) => {
62            error!("storage: smoke check failed: {e}");
63        }
64    }
65}
66
67fn run_smoke_check(storage: &mut dyn Storage) -> Result<(), shuyi_ui::storage::StorageError> {
68    storage.write(SMOKE_TEST_KEY, "ok")?;
69    let read_back = storage.read(SMOKE_TEST_KEY)?;
70    storage.delete(SMOKE_TEST_KEY)?;
71    if read_back.as_deref() == Some("ok") {
72        Ok(())
73    } else {
74        Err(shuyi_ui::storage::StorageError::Backend(format!(
75            "wrote \"ok\", read back {read_back:?}"
76        )))
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn the_web_prefix_is_the_storage_name_and_a_slash() {
86        assert_eq!(web_prefix("some-game"), "some-game/");
87    }
88}