bones_bevy_renderer/
storage.rs

1use bevy::log::error;
2use bones_framework::prelude::*;
3use serde::{de::Visitor, Deserialize, Serialize};
4
5#[cfg(target_arch = "wasm32")]
6pub use wasm::StorageBackend;
7#[cfg(target_arch = "wasm32")]
8mod wasm {
9    use super::*;
10
11    /// Contains a namespace that is used for saving data in the filesystem.
12    pub struct StorageBackend {
13        storage_key: String,
14    }
15
16    impl StorageBackend {
17        /// Create a new [`StorageBackend`].
18        pub fn new(qualifier: &str, organization: &str, application: &str) -> Self {
19            Self {
20                storage_key: format!("{qualifier}.{organization}.{application}.storage"),
21            }
22        }
23    }
24
25    impl StorageApi for StorageBackend {
26        fn save(&mut self, data: Vec<SchemaBox>) {
27            let mut buffer = Vec::new();
28            let mut serializer = serde_yaml::Serializer::new(&mut buffer);
29            LoadedStorage(data)
30                .serialize(&mut serializer)
31                .expect("Failed to serialize to storage file.");
32            let data = String::from_utf8(buffer).unwrap();
33            let window = web_sys::window().unwrap();
34            let storage = window.local_storage().unwrap().unwrap();
35            storage.set_item(&self.storage_key, &data).unwrap();
36        }
37
38        fn load(&mut self) -> Vec<SchemaBox> {
39            let window = web_sys::window().unwrap();
40            let storage = window.local_storage().unwrap().unwrap();
41            let Some(data) = storage.get_item(&self.storage_key).unwrap() else {
42                return default();
43            };
44
45            let Ok(loaded) = serde_yaml::from_str::<LoadedStorage>(&data) else {
46                return default();
47            };
48            loaded.0
49        }
50    }
51}
52
53#[cfg(not(target_arch = "wasm32"))]
54pub use native::StorageBackend;
55#[cfg(not(target_arch = "wasm32"))]
56mod native {
57    use super::*;
58
59    /// Contains a namespace that is used for saving data in the filesystem.
60    pub struct StorageBackend {
61        storage_path: std::path::PathBuf,
62    }
63
64    impl StorageBackend {
65        /// Create a new [`StorageBackend`].
66        pub fn new(qualifier: &str, organization: &str, application: &str) -> Self {
67            let project_dirs = directories::ProjectDirs::from(qualifier, organization, application)
68                .expect("Identify system data dir path");
69            Self {
70                storage_path: project_dirs.data_dir().join("storage.yml"),
71            }
72        }
73    }
74
75    impl StorageApi for StorageBackend {
76        fn save(&mut self, data: Vec<SchemaBox>) {
77            let file = std::fs::OpenOptions::new()
78                .write(true)
79                .truncate(true)
80                .create(true)
81                .open(&self.storage_path)
82                .expect("Failed to open storage file");
83            let mut serializer = serde_yaml::Serializer::new(file);
84            LoadedStorage(data)
85                .serialize(&mut serializer)
86                .expect("Failed to serialize to storage file.");
87        }
88
89        fn load(&mut self) -> Vec<SchemaBox> {
90            if self.storage_path.exists() {
91                let result: anyhow::Result<LoadedStorage> = (|| {
92                    let file = std::fs::OpenOptions::new()
93                        .read(true)
94                        .open(&self.storage_path)
95                        .context("Failed to open storage file")?;
96                    let loaded: LoadedStorage = serde_yaml::from_reader(file)
97                        .context("Failed to deserialize storage file")?;
98
99                    anyhow::Result::Ok(loaded)
100                })();
101                match result {
102                    Ok(loaded) => loaded.0,
103                    Err(e) => {
104                        error!(
105                            "Error deserializing storage file, ignoring file, \
106                        data will be overwritten when saved: {e:?}"
107                        );
108                        default()
109                    }
110                }
111            } else {
112                std::fs::create_dir_all(self.storage_path.parent().unwrap()).unwrap();
113                default()
114            }
115        }
116    }
117}
118
119struct LoadedStorage(Vec<SchemaBox>);
120impl Serialize for LoadedStorage {
121    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
122    where
123        S: serde::Serializer,
124    {
125        let data: HashMap<String, SchemaRef> = self
126            .0
127            .iter()
128            .map(|x| (x.schema().full_name.to_string(), x.as_ref()))
129            .collect();
130
131        use serde::ser::SerializeMap;
132        let mut map = serializer.serialize_map(Some(data.len()))?;
133
134        for (key, value) in data {
135            map.serialize_key(&key)?;
136            map.serialize_value(&SchemaSerializer(value))?;
137        }
138
139        map.end()
140    }
141}
142impl<'de> Deserialize<'de> for LoadedStorage {
143    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
144    where
145        D: serde::Deserializer<'de>,
146    {
147        deserializer.deserialize_map(LoadedStorageVisitor).map(Self)
148    }
149}
150struct LoadedStorageVisitor;
151impl<'de> Visitor<'de> for LoadedStorageVisitor {
152    type Value = Vec<SchemaBox>;
153    fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
154        write!(formatter, "Mapping of string type names to type data.")
155    }
156    fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
157    where
158        A: serde::de::MapAccess<'de>,
159    {
160        let mut data = Vec::new();
161        while let Some(type_name) = map.next_key::<String>()? {
162            let Some(schema) = SCHEMA_REGISTRY
163                .schemas
164                .iter()
165                .find(|schema| schema.full_name.as_ref() == type_name)
166            else {
167                error!(
168                    "\n\nCannot find schema registration for `{}` while loading persisted \
169                        storage. This means you that you need to call \
170                        `{}::schema()` to register your persisted storage type before \
171                        creating the `BonesBevyRenderer` or that there is data from an old \
172                        version of the app inside of the persistent storage file.\n\n",
173                    type_name, type_name,
174                );
175                continue;
176            };
177
178            data.push(map.next_value_seed(SchemaDeserializer(schema))?);
179        }
180
181        Ok(data)
182    }
183}