Skip to main content

goud_engine/assets/storage/
container.rs

1//! [`AssetStorage`]: type-erased container for all asset types.
2
3use crate::assets::{
4    Asset, AssetHandle, AssetId, AssetInfo, AssetPath, AssetState, UntypedAssetHandle,
5};
6use std::collections::HashMap;
7use std::fmt;
8
9use super::any_storage::AnyAssetStorage;
10use super::entry::AssetEntry;
11use super::typed::TypedAssetStorage;
12
13// =============================================================================
14// AssetStorage
15// =============================================================================
16
17/// Type-erased container for all asset types.
18///
19/// `AssetStorage` is the main asset storage container that holds typed storages
20/// for each registered asset type. It provides both typed and untyped access
21/// patterns.
22///
23/// # Example
24///
25/// ```
26/// use goud_engine::assets::{Asset, AssetStorage, AssetPath};
27///
28/// struct Texture { width: u32 }
29/// impl Asset for Texture {}
30///
31/// struct Audio { duration: f32 }
32/// impl Asset for Audio {}
33///
34/// let mut storage = AssetStorage::new();
35///
36/// // Insert different asset types
37/// let tex_handle = storage.insert(Texture { width: 256 });
38/// let audio_handle = storage.insert(Audio { duration: 2.5 });
39///
40/// // Type-safe access
41/// assert_eq!(storage.get::<Texture>(&tex_handle).unwrap().width, 256);
42/// assert_eq!(storage.get::<Audio>(&audio_handle).unwrap().duration, 2.5);
43///
44/// // Count by type
45/// assert_eq!(storage.len::<Texture>(), 1);
46/// assert_eq!(storage.len::<Audio>(), 1);
47/// ```
48pub struct AssetStorage {
49    /// Type-erased storage for each asset type.
50    storages: HashMap<AssetId, Box<dyn AnyAssetStorage>>,
51}
52
53impl AssetStorage {
54    /// Creates a new, empty asset storage.
55    #[inline]
56    pub fn new() -> Self {
57        Self {
58            storages: HashMap::new(),
59        }
60    }
61
62    /// Creates a new asset storage with pre-allocated capacity for storage map.
63    #[inline]
64    pub fn with_capacity(capacity: usize) -> Self {
65        Self {
66            storages: HashMap::with_capacity(capacity),
67        }
68    }
69
70    /// Inserts an asset and returns its handle.
71    ///
72    /// Automatically creates the typed storage if it doesn't exist.
73    pub fn insert<A: Asset>(&mut self, asset: A) -> AssetHandle<A> {
74        self.get_or_create_storage::<A>().insert(asset)
75    }
76
77    /// Inserts an asset with an associated path.
78    ///
79    /// If an asset with the same path exists and is alive, returns the existing handle.
80    pub fn insert_with_path<A: Asset>(&mut self, asset: A, path: AssetPath<'_>) -> AssetHandle<A> {
81        self.get_or_create_storage::<A>()
82            .insert_with_path(asset, path)
83    }
84
85    /// Reserves a handle for later loading.
86    pub fn reserve<A: Asset>(&mut self) -> AssetHandle<A> {
87        self.get_or_create_storage::<A>().reserve()
88    }
89
90    /// Reserves a handle with an associated path.
91    pub fn reserve_with_path<A: Asset>(&mut self, path: AssetPath<'_>) -> AssetHandle<A> {
92        self.get_or_create_storage::<A>().reserve_with_path(path)
93    }
94
95    /// Sets a loaded asset for a reserved handle.
96    pub fn set_loaded<A: Asset>(&mut self, handle: &AssetHandle<A>, asset: A) -> bool {
97        self.get_or_create_storage::<A>().set_loaded(handle, asset)
98    }
99
100    /// Removes an asset and returns it.
101    pub fn remove<A: Asset>(&mut self, handle: &AssetHandle<A>) -> Option<A> {
102        self.get_storage_mut::<A>().and_then(|s| s.remove(handle))
103    }
104
105    /// Removes an asset by untyped handle.
106    pub fn remove_untyped(&mut self, handle: &UntypedAssetHandle) -> bool {
107        self.storages
108            .get_mut(&handle.asset_id())
109            .map(|s| s.remove_untyped(handle))
110            .unwrap_or(false)
111    }
112
113    /// Gets a reference to an asset.
114    pub fn get<A: Asset>(&self, handle: &AssetHandle<A>) -> Option<&A> {
115        self.get_storage::<A>().and_then(|s| s.get(handle))
116    }
117
118    /// Gets a mutable reference to an asset.
119    pub fn get_mut<A: Asset>(&mut self, handle: &AssetHandle<A>) -> Option<&mut A> {
120        self.get_storage_mut::<A>().and_then(|s| s.get_mut(handle))
121    }
122
123    /// Gets the entry for a handle.
124    pub fn get_entry<A: Asset>(&self, handle: &AssetHandle<A>) -> Option<&AssetEntry<A>> {
125        self.get_storage::<A>().and_then(|s| s.get_entry(handle))
126    }
127
128    /// Gets the mutable entry for a handle.
129    pub fn get_entry_mut<A: Asset>(
130        &mut self,
131        handle: &AssetHandle<A>,
132    ) -> Option<&mut AssetEntry<A>> {
133        self.get_storage_mut::<A>()
134            .and_then(|s| s.get_entry_mut(handle))
135    }
136
137    /// Checks if a handle is alive.
138    pub fn is_alive<A: Asset>(&self, handle: &AssetHandle<A>) -> bool {
139        self.get_storage::<A>()
140            .map(|s| s.is_alive(handle))
141            .unwrap_or(false)
142    }
143
144    /// Checks if an untyped handle is alive.
145    pub fn is_alive_untyped(&self, handle: &UntypedAssetHandle) -> bool {
146        self.storages
147            .get(&handle.asset_id())
148            .map(|s| s.is_alive_raw(handle.index(), handle.generation()))
149            .unwrap_or(false)
150    }
151
152    /// Gets the state for a handle.
153    pub fn get_state<A: Asset>(&self, handle: &AssetHandle<A>) -> Option<AssetState> {
154        self.get_storage::<A>().and_then(|s| s.get_state(handle))
155    }
156
157    /// Gets the state for an untyped handle.
158    pub fn get_state_untyped(&self, handle: &UntypedAssetHandle) -> Option<AssetState> {
159        self.storages
160            .get(&handle.asset_id())
161            .and_then(|s| s.get_state_untyped(handle))
162    }
163
164    /// Gets a handle by path.
165    pub fn get_handle_by_path<A: Asset>(&self, path: &str) -> Option<AssetHandle<A>> {
166        self.get_storage::<A>()
167            .and_then(|s| s.get_handle_by_path(path))
168    }
169
170    /// Gets an asset by path.
171    pub fn get_by_path<A: Asset>(&self, path: &str) -> Option<&A> {
172        self.get_storage::<A>().and_then(|s| s.get_by_path(path))
173    }
174
175    /// Checks if a path exists for an asset type.
176    pub fn has_path<A: Asset>(&self, path: &str) -> bool {
177        self.get_storage::<A>()
178            .map(|s| s.has_path(path))
179            .unwrap_or(false)
180    }
181
182    /// Sets the path for a handle.
183    pub fn set_path<A: Asset>(&mut self, handle: &AssetHandle<A>, path: AssetPath<'_>) -> bool {
184        self.get_storage_mut::<A>()
185            .map(|s| s.set_path(handle, path))
186            .unwrap_or(false)
187    }
188
189    /// Returns the number of assets of a specific type.
190    pub fn len<A: Asset>(&self) -> usize {
191        self.get_storage::<A>().map(|s| s.len()).unwrap_or(0)
192    }
193
194    /// Returns `true` if no assets of a specific type are stored.
195    pub fn is_empty_type<A: Asset>(&self) -> bool {
196        self.get_storage::<A>()
197            .map(|s| s.is_empty())
198            .unwrap_or(true)
199    }
200
201    /// Returns the total number of assets across all types.
202    pub fn total_len(&self) -> usize {
203        self.storages.values().map(|s| s.len()).sum()
204    }
205
206    /// Returns `true` if no assets are stored.
207    pub fn is_empty(&self) -> bool {
208        self.storages.values().all(|s| s.is_empty())
209    }
210
211    /// Returns the number of registered asset types.
212    pub fn type_count(&self) -> usize {
213        self.storages.len()
214    }
215
216    /// Clears all assets of a specific type.
217    pub fn clear_type<A: Asset>(&mut self) {
218        if let Some(storage) = self.get_storage_mut::<A>() {
219            storage.clear();
220        }
221    }
222
223    /// Clears all assets from all storages.
224    pub fn clear(&mut self) {
225        for storage in self.storages.values_mut() {
226            storage.clear();
227        }
228    }
229
230    /// Returns `true` if storage exists for a type.
231    pub fn has_type<A: Asset>(&self) -> bool {
232        self.storages.contains_key(&AssetId::of::<A>())
233    }
234
235    /// Returns asset info for all registered types.
236    pub fn registered_types(&self) -> Vec<AssetInfo> {
237        self.storages.values().map(|s| s.asset_info()).collect()
238    }
239
240    /// Gets the typed storage for an asset type.
241    pub fn get_storage<A: Asset>(&self) -> Option<&TypedAssetStorage<A>> {
242        self.storages
243            .get(&AssetId::of::<A>())
244            .and_then(|s| s.as_any().downcast_ref())
245    }
246
247    /// Gets mutable typed storage for an asset type.
248    pub fn get_storage_mut<A: Asset>(&mut self) -> Option<&mut TypedAssetStorage<A>> {
249        self.storages
250            .get_mut(&AssetId::of::<A>())
251            .and_then(|s| s.as_any_mut().downcast_mut())
252    }
253
254    /// Sets a loaded asset from a type-erased boxed value using raw handle parts.
255    ///
256    /// Used by async loading to finalize results on the main thread.
257    pub fn set_loaded_raw(
258        &mut self,
259        asset_id: AssetId,
260        index: u32,
261        generation: u32,
262        boxed: Box<dyn std::any::Any + Send>,
263    ) -> bool {
264        self.storages
265            .get_mut(&asset_id)
266            .map(|s| s.set_loaded_raw(index, generation, boxed))
267            .unwrap_or(false)
268    }
269
270    /// Replaces a loaded asset by path with a new type-erased value.
271    ///
272    /// Searches all storage types for an asset with the given path and
273    /// replaces it with the new value. Used for hot-reload.
274    pub fn replace_erased(&mut self, path: &str, boxed: Box<dyn std::any::Any + Send>) -> bool {
275        // First find which storage has this path
276        let target_id = self
277            .storages
278            .iter()
279            .find(|(_, s)| s.has_path_erased(path))
280            .map(|(id, _)| *id);
281
282        if let Some(id) = target_id {
283            if let Some(storage) = self.storages.get_mut(&id) {
284                return storage.replace_by_path(path, boxed);
285            }
286        }
287        false
288    }
289
290    /// Marks an asset as failed using raw handle parts.
291    ///
292    /// Used by async loading to report errors on the main thread.
293    pub fn set_failed_raw(
294        &mut self,
295        asset_id: AssetId,
296        index: u32,
297        generation: u32,
298        error: String,
299    ) -> bool {
300        self.storages
301            .get_mut(&asset_id)
302            .map(|s| s.set_failed_raw(index, generation, error))
303            .unwrap_or(false)
304    }
305
306    /// Gets or creates typed storage for an asset type.
307    pub fn get_or_create_storage<A: Asset>(&mut self) -> &mut TypedAssetStorage<A> {
308        let id = AssetId::of::<A>();
309        self.storages
310            .entry(id)
311            .or_insert_with(|| Box::new(TypedAssetStorage::<A>::new()))
312            .as_any_mut()
313            .downcast_mut()
314            .expect("storage type mismatch")
315    }
316
317    /// Iterates over all assets of a specific type.
318    pub fn iter<A: Asset>(&self) -> impl Iterator<Item = (AssetHandle<A>, &A)> {
319        self.get_storage::<A>().into_iter().flat_map(|s| s.iter())
320    }
321
322    /// Returns handles for all assets of a specific type.
323    pub fn handles<A: Asset>(&self) -> impl Iterator<Item = AssetHandle<A>> + '_ {
324        self.get_storage::<A>()
325            .into_iter()
326            .flat_map(|s| s.handles())
327    }
328}
329
330impl Default for AssetStorage {
331    #[inline]
332    fn default() -> Self {
333        Self::new()
334    }
335}
336
337impl fmt::Debug for AssetStorage {
338    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339        f.debug_struct("AssetStorage")
340            .field("type_count", &self.type_count())
341            .field("total_assets", &self.total_len())
342            .finish()
343    }
344}