distill_loader/storage.rs
1use std::{
2 error::Error,
3 path::PathBuf,
4 sync::{
5 atomic::{AtomicU64, Ordering},
6 Arc,
7 },
8};
9
10use crossbeam_channel::Sender;
11use dashmap::DashMap;
12use distill_core::{AssetMetadata, AssetRef, AssetTypeId, AssetUuid};
13
14/// Loading ID allocated by [`Loader`](crate::loader::Loader) to track loading of a particular asset
15/// or an indirect reference to an asset.
16#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
17pub struct LoadHandle(pub u64);
18
19impl LoadHandle {
20 /// Returns true if the handle needs to be resolved through the [`IndirectionTable`] before use.
21 /// An "indirect" LoadHandle represents a load operation for an identifier that is late-bound,
22 /// meaning the identifier may change which [`AssetUuid`] it resolves to.
23 /// An example of an indirect LoadHandle would be one that loads by filesystem path.
24 /// The specific asset at a path may change as files change, move or are deleted, while a direct
25 /// LoadHandle (one that addresses by AssetUuid) is guaranteed to refer to an AssetUuid for its
26 /// whole lifetime.
27 pub fn is_indirect(&self) -> bool {
28 (self.0 & (1 << 63)) == 1 << 63
29 }
30
31 pub(crate) fn set_indirect(self) -> LoadHandle {
32 LoadHandle(self.0 | (1 << 63))
33 }
34}
35
36pub(crate) enum HandleOp {
37 Error(LoadHandle, u32, Box<dyn Error + Send>),
38 Complete(LoadHandle, u32),
39 Drop(LoadHandle, u32),
40}
41
42/// Type that allows the downstream asset storage implementation to signal that an asset update
43/// operation has completed. See [`AssetStorage::update_asset`].
44pub struct AssetLoadOp {
45 sender: Option<Sender<HandleOp>>,
46 handle: LoadHandle,
47 version: u32,
48}
49
50impl AssetLoadOp {
51 pub(crate) fn new(sender: Sender<HandleOp>, handle: LoadHandle, version: u32) -> Self {
52 Self {
53 sender: Some(sender),
54 handle,
55 version,
56 }
57 }
58
59 /// Returns the `LoadHandle` associated with the load operation
60 pub fn load_handle(&self) -> LoadHandle {
61 self.handle
62 }
63
64 /// Signals that this load operation has completed succesfully.
65 pub fn complete(mut self) {
66 let _ = self
67 .sender
68 .as_ref()
69 .unwrap()
70 .send(HandleOp::Complete(self.handle, self.version));
71 self.sender = None;
72 }
73
74 /// Signals that this load operation has completed with an error.
75 pub fn error<E: Error + 'static + Send>(mut self, error: E) {
76 let _ = self.sender.as_ref().unwrap().send(HandleOp::Error(
77 self.handle,
78 self.version,
79 Box::new(error),
80 ));
81 self.sender = None;
82 }
83}
84
85impl Drop for AssetLoadOp {
86 fn drop(&mut self) {
87 if let Some(ref sender) = self.sender {
88 let _ = sender.send(HandleOp::Drop(self.handle, self.version));
89 }
90 }
91}
92
93/// Storage for all assets of all asset types.
94///
95/// Consumers are expected to provide the implementation for this, as this is the bridge between
96/// [`Loader`](crate::loader::Loader) and the application.
97pub trait AssetStorage {
98 /// Updates the backing data of an asset.
99 ///
100 /// An example usage of this is when a texture such as "player.png" changes while the
101 /// application is running. The asset ID is the same, but the underlying pixel data can differ.
102 ///
103 /// # Parameters
104 ///
105 /// * `loader`: Loader implementation calling this function.
106 /// * `asset_type_id`: UUID of the asset type.
107 /// * `data`: The updated asset byte data.
108 /// * `load_handle`: ID allocated by [`Loader`](crate::loader::Loader) to track loading of a particular asset.
109 /// * `load_op`: Allows the loading implementation to signal when loading is done / errors.
110 /// * `version`: Runtime load version of this asset, increments each time the asset is updated.
111 fn update_asset(
112 &self,
113 loader_info: &dyn LoaderInfoProvider,
114 asset_type_id: &AssetTypeId,
115 data: Vec<u8>,
116 load_handle: LoadHandle,
117 load_op: AssetLoadOp,
118 version: u32,
119 ) -> Result<(), Box<dyn Error + Send + 'static>>;
120
121 /// Commits the specified asset version as loaded and ready to use.
122 ///
123 /// # Parameters
124 ///
125 /// * `asset_type_id`: UUID of the asset type.
126 /// * `load_handle`: ID allocated by [`Loader`](crate::loader::Loader) to track loading of a particular asset.
127 /// * `version`: Runtime load version of this asset, increments each time the asset is updated.
128 fn commit_asset_version(&self, asset_type: &AssetTypeId, load_handle: LoadHandle, version: u32);
129
130 /// Frees the asset identified by the load handle.
131 ///
132 /// # Parameters
133 ///
134 /// * `asset_type_id`: UUID of the asset type.
135 /// * `load_handle`: ID allocated by [`Loader`](crate::loader::Loader) to track loading of a particular asset.
136 fn free(&self, asset_type_id: &AssetTypeId, load_handle: LoadHandle, version: u32);
137}
138
139/// Asset loading status.
140#[derive(Debug)]
141pub enum LoadStatus {
142 /// There is no request for the asset to be loaded.
143 NotRequested,
144 /// The asset is an indirect reference which has not been resolved yet.
145 Unresolved,
146 /// The asset is being loaded.
147 Loading,
148 /// The asset is loaded.
149 Loaded,
150 /// The asset is being unloaded.
151 Unloading,
152 /// The asset does not exist.
153 DoesNotExist,
154 /// There was an error during loading / unloading of the asset.
155 Error(Box<dyn Error>),
156}
157
158/// Information about an asset load operation.
159///
160/// **Note:** The information is true at the time the `LoadInfo` is retrieved. The actual number of
161/// references may change.
162#[derive(Debug)]
163pub struct LoadInfo {
164 /// UUID of the asset.
165 pub asset_id: AssetUuid,
166 /// Number of references to the asset.
167 pub refs: u32,
168 /// Path to asset's source file. Not guaranteed to always be available.
169 pub path: Option<String>,
170 /// Name of asset's source file. Not guaranteed to always be available.
171 pub file_name: Option<String>,
172 /// Asset name. Not guaranteed to always be available.
173 pub asset_name: Option<String>,
174}
175
176/// Provides information about mappings between `AssetUuid` and `LoadHandle`.
177/// Intended to be used for `Handle` serde.
178pub trait LoaderInfoProvider: Send + Sync {
179 /// Returns the load handle for the asset with the given UUID, if present.
180 ///
181 /// This will only return `Some(..)` if there has been a previous call to [`crate::loader::Loader::add_ref`].
182 ///
183 /// # Parameters
184 ///
185 /// * `id`: UUID of the asset.
186 fn get_load_handle(&self, asset_ref: &AssetRef) -> Option<LoadHandle>;
187
188 /// Returns the AssetUUID for the given LoadHandle, if present.
189 ///
190 /// # Parameters
191 ///
192 /// * `load_handle`: ID allocated by [`Loader`](crate::loader::Loader) to track loading of the asset.
193 fn get_asset_id(&self, load: LoadHandle) -> Option<AssetUuid>;
194}
195
196/// Allocates LoadHandles for [`Loader`](crate::loader::Loader) implementations.
197pub trait HandleAllocator: Send + Sync + 'static {
198 /// Allocates a [`LoadHandle`] for use by a [`crate::loader::Loader`].
199 /// The same LoadHandle must not be returned by this function until it has been passed to `free`.
200 /// NOTE: The most significant bit of the u64 in the LoadHandle returned MUST be unset,
201 /// as it is reserved for indicating whether the handle is indirect or not.
202 fn alloc(&self) -> LoadHandle;
203 /// Frees a [`LoadHandle`], allowing the handle to be returned by a future `alloc` call.
204 fn free(&self, handle: LoadHandle);
205}
206
207/// An implementation of [`HandleAllocator`] which uses an incrementing AtomicU64 internally to allocate LoadHandle IDs.
208pub struct AtomicHandleAllocator(AtomicU64);
209impl AtomicHandleAllocator {
210 pub const fn new(starting_value: u64) -> Self {
211 Self(AtomicU64::new(starting_value))
212 }
213}
214impl Default for AtomicHandleAllocator {
215 fn default() -> Self {
216 Self(AtomicU64::new(1))
217 }
218}
219impl HandleAllocator for AtomicHandleAllocator {
220 fn alloc(&self) -> LoadHandle {
221 LoadHandle(self.0.fetch_add(1, Ordering::Relaxed))
222 }
223
224 fn free(&self, _handle: LoadHandle) {}
225}
226
227impl HandleAllocator for &'static AtomicHandleAllocator {
228 fn alloc(&self) -> LoadHandle {
229 LoadHandle(self.0.fetch_add(1, Ordering::Relaxed))
230 }
231
232 fn free(&self, _handle: LoadHandle) {}
233}
234
235/// An indirect identifier that can be resolved to a specific [`AssetUuid`] by an [`IndirectionResolver`] impl.
236#[derive(Clone, PartialEq, Eq, Debug, Hash)]
237pub enum IndirectIdentifier {
238 PathWithTagAndType(String, String, AssetTypeId),
239 PathWithType(String, AssetTypeId),
240 Path(String),
241}
242impl IndirectIdentifier {
243 pub fn path(&self) -> &str {
244 match self {
245 IndirectIdentifier::PathWithTagAndType(path, _, _) => path.as_str(),
246 IndirectIdentifier::PathWithType(path, _) => path.as_str(),
247 IndirectIdentifier::Path(path) => path.as_str(),
248 }
249 }
250
251 pub fn type_id(&self) -> Option<&AssetTypeId> {
252 match self {
253 IndirectIdentifier::PathWithTagAndType(_, _, ty) => Some(ty),
254 IndirectIdentifier::PathWithType(_, ty) => Some(ty),
255 IndirectIdentifier::Path(_) => None,
256 }
257 }
258}
259/// Resolves ambiguous [`IndirectIdentifier`]s to a single asset ID given a set of candidates.
260pub trait IndirectionResolver {
261 fn resolve(
262 &self,
263 id: &IndirectIdentifier,
264 candidates: Vec<(PathBuf, Vec<AssetMetadata>)>,
265 ) -> Option<AssetUuid>;
266}
267
268/// Default implementation of [`IndirectionResolver`] which resolves to the first asset in the list of candidates
269/// of the appropriate type.
270pub struct DefaultIndirectionResolver;
271impl IndirectionResolver for DefaultIndirectionResolver {
272 fn resolve(
273 &self,
274 id: &IndirectIdentifier,
275 candidates: Vec<(PathBuf, Vec<AssetMetadata>)>,
276 ) -> Option<AssetUuid> {
277 let id_type = id.type_id();
278 for candidate in candidates {
279 for asset in candidate.1 {
280 if let Some(artifact) = asset.artifact {
281 if id_type.is_none() || *id_type.unwrap() == artifact.type_id {
282 return Some(asset.id);
283 }
284 }
285 }
286 }
287 None
288 }
289}
290
291/// Resolves indirect [`LoadHandle`]s. See [`LoadHandle::is_indirect`] for details.
292#[derive(Clone)]
293pub struct IndirectionTable(pub(crate) Arc<DashMap<LoadHandle, LoadHandle>>);
294impl IndirectionTable {
295 pub fn resolve(&self, indirect_handle: LoadHandle) -> Option<LoadHandle> {
296 self.0.get(&indirect_handle).map(|l| *l)
297 }
298}