bones_framework/
storage.rs

1//! Persistant storage API.
2
3use crate::prelude::*;
4
5/// Persitent storage resource.
6///
7/// > **Note:** data is not actually saved until you call [`Storage::save`]
8///
9/// > **🚧 Warning:** The storage interface uses the types [`SchemaData::full_name`] as a storage
10/// > key, so you must ensure that all types that are stored have different full names or it may
11/// > behave unexpectedly.
12#[derive(HasSchema)]
13#[schema(no_clone)]
14pub struct Storage {
15    /// The backend storage API.
16    pub backend: Box<dyn StorageApi>,
17    /// The cache of objects that have been read
18    pub cache: HashMap<SchemaId, SchemaBox>,
19}
20#[allow(clippy::derivable_impls)] // false positive
21impl Default for Storage {
22    fn default() -> Self {
23        Self {
24            backend: Box::<MemoryBackend>::default(),
25            cache: Default::default(),
26        }
27    }
28}
29
30impl Storage {
31    /// Create a new storage resource with the given backend storage API.
32    pub fn with_backend(backend: Box<dyn StorageApi>) -> Self {
33        Self {
34            backend,
35            cache: default(),
36        }
37    }
38
39    /// Load the data from the storage backend.
40    pub fn load(&mut self) {
41        self.cache = self
42            .backend
43            .load()
44            .into_iter()
45            .map(|x| (x.schema().id(), x))
46            .collect();
47    }
48
49    /// Save the data to the storage backend.
50    pub fn save(&mut self) {
51        self.backend.save(self.cache.values().cloned().collect())
52    }
53
54    /// Insert the data into storage cache.
55    pub fn insert<T: HasSchema>(&mut self, data: T) {
56        let b = SchemaBox::new(data);
57        self.cache.insert(b.schema().id(), b);
58    }
59
60    /// Get data from the storage cache.
61    pub fn get<T: HasSchema>(&self) -> Option<&T> {
62        self.cache.get(&T::schema().id()).map(|x| x.cast_ref())
63    }
64
65    /// Get data mutably from the storage cache.
66    pub fn get_mut<T: HasSchema>(&mut self) -> Option<&mut T> {
67        self.cache.get_mut(&T::schema().id()).map(|x| x.cast_mut())
68    }
69
70    /// Get data from the storage cache or insert it's default value
71    pub fn get_or_insert_default<T: HasSchema + Default>(&mut self) -> &T {
72        self.cache
73            .entry(T::schema().id())
74            .or_insert_with(|| SchemaBox::default(T::schema()))
75            .cast_ref()
76    }
77
78    /// Get data mutably from the storage cache or insert it's default value
79    pub fn get_or_insert_default_mut<T: HasSchema + Default>(&mut self) -> &mut T {
80        self.cache
81            .entry(T::schema().id())
82            .or_insert_with(|| SchemaBox::default(T::schema()))
83            .cast_mut()
84    }
85
86    /// Remove data for a type from the storage.
87    pub fn remove<T: HasSchema>(&mut self) {
88        self.cache.remove(&T::schema().id());
89    }
90}
91
92/// Trait implemented by storage backends.
93///
94/// TODO: Implement asynchronous storage API.
95/// Currently all storage access is synchronous, which is not good for the user experience when a
96/// write to storage could delay the rendering of the next frame. We should come up with a
97/// nice-to-use API for asynchronously loading and storing data.
98pub trait StorageApi: Sync + Send {
99    /// Write the entire collection of objects to storage, replacing the previous storage data. If
100    /// set, the `handler` will be called when the data has been written.
101    fn save(&mut self, data: Vec<SchemaBox>);
102    /// Read the entire collection of objects from storage with `handler` being called with the data
103    /// once the load is complete.
104    fn load(&mut self) -> Vec<SchemaBox>;
105}
106
107/// Non-persistent [`Storage`] backend.
108#[derive(Default)]
109pub struct MemoryBackend {
110    data: Vec<SchemaBox>,
111}
112
113impl StorageApi for MemoryBackend {
114    fn save(&mut self, data: Vec<SchemaBox>) {
115        self.data = data;
116    }
117
118    fn load(&mut self) -> Vec<SchemaBox> {
119        self.data.clone()
120    }
121}