Skip to main content

extendable_assets/
manager.rs

1use std::collections::HashMap;
2use std::sync::{Arc, Weak};
3
4use downcast_rs::{DowncastSync, impl_downcast};
5use parking_lot::Mutex;
6
7use crate::asset::{Asset, AssetId, AssetSerializationBackend, NullSerializationBackend};
8use crate::asset_type::AssetType;
9use crate::filesystem::{Filesystem, FilesystemError};
10use crate::util::U64HashMap;
11
12/// Central manager for assets in the system.
13///
14/// The asset manager is responsible for:
15/// - Registering and retrieving asset types
16/// - Managing asset lifecycles and hash-based unique IDs
17/// - Providing access to the filesystem for asset operations
18/// - Thread-safe storage of assets and their metadata
19pub struct AssetManager {
20    /// Registry of asset types by their string names
21    asset_types: Mutex<HashMap<String, Arc<dyn AssetType>>>,
22    /// Storage for loaded assets indexed by their unique IDs
23    assets: Mutex<U64HashMap<AssetId, Arc<Asset>>>,
24    /// Filesystem abstraction for reading and writing asset files
25    filesystem: Arc<dyn Filesystem>,
26    /// Optional context for providing additional state to the asset manager
27    context: Option<Arc<dyn AssetManagerContext>>,
28    /// Backend implementation for serializing and deserializing assets
29    serialization: Box<dyn AssetSerializationBackend>,
30}
31impl AssetManager {
32    /// Creates a new asset manager with the provided filesystem.
33    ///
34    /// # Arguments
35    ///
36    /// * `filesystem` - The filesystem implementation to use for asset I/O
37    #[inline]
38    pub fn new(filesystem: Arc<dyn Filesystem>) -> Self {
39        Self {
40            asset_types: Mutex::new(HashMap::default()),
41            assets: Mutex::new(HashMap::default()),
42            filesystem,
43            context: None,
44            serialization: Box::new(NullSerializationBackend),
45        }
46    }
47
48    /// Retrieves the current asset manager context.
49    ///
50    /// # Returns
51    ///
52    /// The context if one has been set, otherwise `None`.
53    #[inline]
54    pub fn context(&self) -> Option<Arc<dyn AssetManagerContext>> {
55        self.context.clone()
56    }
57    /// Sets the asset manager context.
58    ///
59    /// # Arguments
60    ///
61    /// * `context` - The context implementation to set
62    #[inline]
63    pub fn set_context(&mut self, context: Arc<dyn AssetManagerContext>) {
64        self.context = Some(context);
65    }
66
67    /// Sets the serialization backend for the asset manager.
68    ///
69    /// This allows changing how assets are serialized and deserialized.
70    /// The backend determines the format used for asset persistence.
71    ///
72    /// # Arguments
73    ///
74    /// * `serialization` - The serialization backend implementation to use
75    pub fn set_serialization_backend(&mut self, serialization: Box<dyn AssetSerializationBackend>) {
76        self.serialization = serialization;
77    }
78
79    /// Retrieves an asset type by its name.
80    ///
81    /// # Arguments
82    ///
83    /// * `name` - The name of the asset type to look up
84    ///
85    /// # Returns
86    ///
87    /// The asset type if found, or `None` if no asset type with that name is registered.
88    #[inline]
89    pub fn asset_type_by_name(&self, name: &str) -> Option<Weak<dyn AssetType>> {
90        let asset_types = self.asset_types.lock();
91        asset_types.get(name).map(Arc::downgrade)
92    }
93    /// Registers a new asset type with the manager.
94    ///
95    /// This allows the asset manager to handle assets of this type.
96    /// If an asset type with the same name already exists, it will be replaced.
97    ///
98    /// # Arguments
99    ///
100    /// * `asset_type` - The asset type implementation to register
101    pub fn register_asset_type(&self, asset_type: Arc<dyn AssetType>) {
102        self.asset_types
103            .lock()
104            .insert(asset_type.name().to_string(), asset_type);
105    }
106    /// Retrieves an asset by its unique ID.
107    ///
108    /// # Arguments
109    ///
110    /// * `id` - The unique identifier of the asset
111    ///
112    /// # Returns
113    ///
114    /// The asset if found, or `None` if no asset with that ID is registered.
115    #[inline]
116    pub fn asset_by_id(&self, id: AssetId) -> Option<Arc<Asset>> {
117        let assets = self.assets.lock();
118        assets.get(&id).cloned()
119    }
120
121    /// Registers an asset with the manager and assigns it a deterministic ID.
122    ///
123    /// If the asset's ID is non-zero, the ID is generated from the asset path using
124    /// hash-based generation, ensuring the same path always produces the same ID.
125    ///
126    /// # Arguments
127    ///
128    /// * `asset_path` - The path string used to generate the asset ID
129    /// * `asset` - The asset to register
130    ///
131    /// # Returns
132    ///
133    /// The deterministic ID assigned to the asset
134    pub fn register_asset(&self, asset_path: &str, mut asset: Asset) -> AssetId {
135        let id = if asset.id() == AssetId::default() {
136            let new_id = AssetId::from(asset_path);
137            asset.set_id(new_id);
138            new_id
139        } else {
140            asset.id()
141        };
142        self.assets.lock().insert(id, Arc::new(asset));
143        id
144    }
145
146    /// Unregisters an asset from the manager.
147    ///
148    /// Removes the asset with the given ID from the manager's storage.
149    /// The asset will no longer be accessible through the manager after this call.
150    /// The asset will be de-allocated when all Arc references to it are dropped.
151    ///
152    /// # Arguments
153    ///
154    /// * `id` - The unique identifier of the asset to remove
155    ///
156    /// # Returns
157    ///
158    /// `true` if the asset was found and removed, `false` if no asset with that ID existed.
159    pub fn unregister_asset(&self, id: AssetId) -> bool {
160        self.assets.lock().remove(&id).is_some()
161    }
162
163    /// Asynchronously reads the raw bytes of a file from the filesystem.
164    ///
165    /// This is a convenience method that delegates to the underlying filesystem
166    /// for reading asset files as byte arrays. The operation is non-blocking.
167    ///
168    /// # Arguments
169    ///
170    /// * `asset_path` - The path to the file to read
171    ///
172    /// # Returns
173    ///
174    /// A future that resolves to the file contents as bytes, or a `FilesystemError` if reading fails.
175    #[inline]
176    pub async fn fs_read_bytes(&self, asset_path: &str) -> Result<Vec<u8>, FilesystemError> {
177        self.filesystem.read_bytes(asset_path).await
178    }
179
180    /// Asynchronously reads an asset file and registers it with the manager.
181    ///
182    /// This method combines file reading with asset deserialization and registration.
183    /// It reads the asset file from the filesystem, deserializes it using the configured
184    /// serialization backend, and registers the resulting asset with the manager.
185    /// If the asset has no ID (ID is 0), it generates one deterministically from the path.
186    ///
187    /// # Arguments
188    ///
189    /// * `asset_path` - The path to the asset file to read and register
190    ///
191    /// # Returns
192    ///
193    /// A future that resolves to the asset ID if successful, or an error if reading,
194    /// deserialization, or asset creation fails.
195    ///
196    /// # Errors
197    ///
198    /// This function will return an error if:
199    /// - The file cannot be read from the filesystem
200    /// - The file contents cannot be deserialized
201    /// - Asset creation from the serialized data fails
202    pub async fn fs_read_and_register_asset(&self, asset_path: &str) -> anyhow::Result<AssetId> {
203        // Read the raw bytes from the filesystem
204        let bytes = self.fs_read_bytes(asset_path).await?;
205
206        // Deserialize the bytes into a SerializedAsset structure
207        let serialized = self.serialization.deserialize(&bytes[..])?;
208
209        // Store the ID for return and create the full Asset object
210        let asset = Asset::from_serialized(self, serialized)?;
211
212        Ok(self.register_asset(asset_path, asset))
213    }
214}
215
216/// Trait for providing additional context to the asset manager.
217///
218/// This trait allows extending the asset manager with custom state
219/// while maintaining type safety through downcasting capabilities.
220pub trait AssetManagerContext: DowncastSync {}
221impl_downcast!(sync AssetManagerContext);