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}