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);