keket/database/
reference.rs

1use crate::database::{
2    AssetDatabase, AssetDatabaseCommandsSender, AssetReferenceCounter, handle::AssetHandle,
3    path::AssetPathStatic,
4};
5use anput::{entity::Entity, query::TypedLookupFetch};
6use serde::{Deserialize, Serialize};
7use std::{
8    error::Error,
9    ops::{Deref, DerefMut},
10    sync::RwLock,
11};
12
13/// A reference to an asset in the asset database.
14///
15/// `AssetRef` encapsulates a reference to an asset, identified by its path,
16/// with an optional handle that can be lazily resolved or explicitly set.
17#[derive(Debug, Serialize, Deserialize)]
18#[serde(from = "AssetPathStatic", into = "AssetPathStatic")]
19pub struct AssetRef {
20    path: AssetPathStatic,
21    #[serde(skip)]
22    handle: RwLock<Option<AssetHandle>>,
23}
24
25impl Default for AssetRef {
26    fn default() -> Self {
27        Self::new("")
28    }
29}
30
31impl AssetRef {
32    /// Creates a new `AssetRef` with the given asset path.
33    ///
34    /// # Arguments
35    /// - `path`: The path to the asset.
36    ///
37    /// # Returns
38    /// An instance of `AssetRef`.
39    pub fn new(path: impl Into<AssetPathStatic>) -> Self {
40        Self {
41            path: path.into(),
42            handle: RwLock::new(None),
43        }
44    }
45
46    /// Creates a new resolved `AssetRef` with the given asset path and handle.
47    ///
48    /// # Arguments
49    /// - `path`: The path to the asset.
50    /// - `handle`: The resolved handle for the asset.
51    ///
52    /// # Returns
53    /// A resolved instance of `AssetRef`.
54    pub fn new_resolved(path: impl Into<AssetPathStatic>, handle: AssetHandle) -> Self {
55        Self {
56            path: path.into(),
57            handle: RwLock::new(Some(handle)),
58        }
59    }
60
61    /// Invalidates the asset handle, making the `AssetRef` unresolved.
62    ///
63    /// # Returns
64    /// An error if the handle could not be invalidated.
65    pub fn invalidate(&self) -> Result<(), Box<dyn Error>> {
66        *self.handle.write().map_err(|error| format!("{error}"))? = None;
67        Ok(())
68    }
69
70    /// Gets the asset path associated with this reference.
71    ///
72    /// # Returns
73    /// A reference to the asset path.
74    pub fn path(&self) -> &AssetPathStatic {
75        &self.path
76    }
77
78    /// Gets the resolved handle for the asset.
79    ///
80    /// # Returns
81    /// The resolved `AssetHandle`, or an error if it has not been resolved.
82    pub fn handle(&self) -> Result<AssetHandle, Box<dyn Error>> {
83        self.handle
84            .read()
85            .map_err(|error| format!("{error}"))?
86            .ok_or_else(|| format!("Asset with `{}` path is not yet resolved!", self.path).into())
87    }
88
89    /// Resolves the asset handle using the asset database, if not already resolved.
90    ///
91    /// # Arguments
92    /// - `database`: Reference to the `AssetDatabase` to resolve the asset.
93    ///
94    /// # Returns
95    /// A resolved `AssetResolved` object, or an error if resolution fails.
96    pub fn resolve<'a>(
97        &'a self,
98        database: &'a AssetDatabase,
99    ) -> Result<AssetResolved<'a>, Box<dyn Error>> {
100        let mut handle = self.handle.write().map_err(|error| format!("{error}"))?;
101        if let Some(result) = handle.as_ref() {
102            Ok(AssetResolved::new(*result, database))
103        } else {
104            let result = database
105                .find(self.path.clone())
106                .ok_or_else(|| format!("Asset with `{}` path not found in database!", self.path))?;
107            *handle = Some(result);
108            Ok(AssetResolved::new(result, database))
109        }
110    }
111
112    /// Ensures existence of the asset with handle using the asset database.
113    ///
114    /// # Arguments
115    /// - `database`: Reference to the `AssetDatabase` to ensure the asset.
116    ///
117    /// # Returns
118    /// An ensured `AssetResolved` object, or an error if resolution fails.
119    pub fn ensure<'a>(
120        &'a self,
121        database: &'a mut AssetDatabase,
122    ) -> Result<AssetResolved<'a>, Box<dyn Error>> {
123        let mut handle = self.handle.write().map_err(|error| format!("{error}"))?;
124        if let Some(result) = handle.as_ref() {
125            Ok(AssetResolved::new(*result, database))
126        } else {
127            let result = database.ensure(self.path.clone())?;
128            *handle = Some(result);
129            Ok(AssetResolved::new(result, database))
130        }
131    }
132}
133
134impl Clone for AssetRef {
135    fn clone(&self) -> Self {
136        Self {
137            path: self.path.clone(),
138            handle: RwLock::new(
139                self.handle
140                    .try_read()
141                    .ok()
142                    .map(|handle| *handle)
143                    .unwrap_or_default(),
144            ),
145        }
146    }
147}
148
149impl PartialEq for AssetRef {
150    fn eq(&self, other: &Self) -> bool {
151        self.path.eq(&other.path)
152    }
153}
154
155impl Eq for AssetRef {}
156
157impl From<AssetPathStatic> for AssetRef {
158    fn from(path: AssetPathStatic) -> Self {
159        Self::new(path)
160    }
161}
162
163impl From<AssetRef> for AssetPathStatic {
164    fn from(value: AssetRef) -> Self {
165        value.path
166    }
167}
168
169/// A wrapper for a resolved asset with direct access to its associated data.
170///
171/// `AssetResolved` provides additional utilities for checking asset state and accessing its data.
172pub struct AssetResolved<'a> {
173    handle: AssetHandle,
174    database: &'a AssetDatabase,
175}
176
177impl<'a> AssetResolved<'a> {
178    /// Creates a new resolved asset.
179    ///
180    /// # Arguments
181    /// - `handle`: The resolved asset handle.
182    /// - `database`: Reference to the `AssetDatabase`.
183    ///
184    /// # Returns
185    /// An instance of `AssetResolved`.
186    pub fn new(handle: AssetHandle, database: &'a AssetDatabase) -> Self {
187        Self { handle, database }
188    }
189
190    /// Gets the entity associated with this asset.
191    pub fn entity(&self) -> Entity {
192        self.handle.entity()
193    }
194
195    /// Checks if the asset exists in the database.
196    pub fn does_exists(&self) -> bool {
197        self.handle.does_exists(self.database)
198    }
199
200    /// Checks if the asset is marked for storing its bytes.
201    pub fn awaits_storing(self) -> bool {
202        self.handle.awaits_storing(self.database)
203    }
204
205    /// Checks if the asset is marked for storing its bytes and is not already stored.
206    pub fn bytes_are_ready_to_store(self) -> bool {
207        self.handle.bytes_are_ready_to_store(self.database)
208    }
209
210    /// Checks if the asset is awaiting an async store.
211    pub fn awaits_async_store(self) -> bool {
212        self.handle.awaits_async_store(self.database)
213    }
214
215    /// Checks if the asset is awaiting resolution.
216    pub fn awaits_resolution(&self) -> bool {
217        self.handle.awaits_resolution(self.database)
218    }
219
220    /// Checks if the asset bytes are ready to be processed.
221    pub fn bytes_are_ready_to_process(&self) -> bool {
222        self.handle.bytes_are_ready_to_process(self.database)
223    }
224
225    /// Checks if the asset is awaiting an async fetch.
226    pub fn awaits_async_fetch(&self) -> bool {
227        self.handle.awaits_async_fetch(self.database)
228    }
229
230    /// Checks if the asset is ready to use.
231    pub fn is_ready_to_use(&self) -> bool {
232        self.handle.is_ready_to_use(self.database)
233    }
234
235    /// Waits asynchronously for the asset to be ready to use.
236    pub async fn wait_for_ready_to_use(&self) {
237        self.handle.wait_for_ready_to_use(self.database).await
238    }
239
240    /// Accesses the asset's typed data, if available.
241    ///
242    /// # Returns
243    /// The asset's data or `None` if access fails.
244    pub fn access_checked<'b, Fetch: TypedLookupFetch<'b, true>>(&'b self) -> Option<Fetch::Value> {
245        self.handle.access_checked::<Fetch>(self.database)
246    }
247
248    /// Accesses the asset's typed data without additional checks.
249    ///
250    /// # Returns
251    /// The asset's data.
252    pub fn access<'b, Fetch: TypedLookupFetch<'b, true>>(&'b self) -> Fetch::Value {
253        self.handle.access::<Fetch>(self.database)
254    }
255
256    /// Iterates over the asset's dependencies as `AssetRef` objects.
257    pub fn dependencies(&self) -> impl Iterator<Item = AssetRef> + '_ {
258        self.handle
259            .dependencies(self.database)
260            .filter_map(|handle| {
261                Some(AssetRef::new_resolved(
262                    handle
263                        .access_checked::<&AssetPathStatic>(self.database)?
264                        .clone(),
265                    handle,
266                ))
267            })
268    }
269
270    /// Iterates over the assets dependent on this one as `AssetRef` objects.
271    pub fn dependent(&self) -> impl Iterator<Item = AssetRef> + '_ {
272        self.handle.dependent(self.database).filter_map(|handle| {
273            Some(AssetRef::new_resolved(
274                handle
275                    .access_checked::<&AssetPathStatic>(self.database)?
276                    .clone(),
277                handle,
278            ))
279        })
280    }
281
282    /// Iterates recursively over all dependencies as `AssetRef` objects.
283    pub fn traverse_dependencies(&self) -> impl Iterator<Item = AssetRef> + '_ {
284        self.handle
285            .traverse_dependencies(self.database)
286            .filter_map(|handle| {
287                Some(AssetRef::new_resolved(
288                    handle
289                        .access_checked::<&AssetPathStatic>(self.database)?
290                        .clone(),
291                    handle,
292                ))
293            })
294    }
295}
296
297/// A smart reference to an asset in the asset database.
298/// Uses asset reference counting to ensure asset lifetime.
299pub struct SmartAssetRef {
300    inner: AssetRef,
301    sender: AssetDatabaseCommandsSender,
302}
303
304impl Drop for SmartAssetRef {
305    fn drop(&mut self) {
306        if let Ok(handle) = self.inner.handle() {
307            self.sender.send(Box::new(move |storage| {
308                if let Ok(mut counter) =
309                    storage.component_mut::<true, AssetReferenceCounter>(handle.entity())
310                {
311                    counter.decrement();
312                    storage.update::<AssetReferenceCounter>(handle.entity());
313                }
314            }));
315        }
316    }
317}
318
319impl SmartAssetRef {
320    /// Creates a new `SmartAssetRef` with the given asset path, ensuring it's
321    /// existance and incrementing asset reference counter.
322    ///
323    /// # Arguments
324    /// - `path`: The path to the asset.
325    /// - `database`: Reference to the `AssetDatabase`.
326    ///
327    /// # Returns
328    /// An instance of `SmartAssetRef`.
329    pub fn new(
330        path: impl Into<AssetPathStatic>,
331        database: &mut AssetDatabase,
332    ) -> Result<Self, Box<dyn Error>> {
333        Self::from_ref(AssetRef::new(path), database)
334    }
335
336    /// Creates a new `SmartAssetRef` from an existing `AssetRef`, ensuring it's
337    /// asset existance and incrementing asset reference counter.
338    ///
339    /// # Arguments
340    /// - `inner`: The `AssetRef` to create the `SmartAssetRef` from.
341    /// - `database`: Reference to the `AssetDatabase`.
342    ///
343    /// # Returns
344    /// An instance of `SmartAssetRef`.
345    pub fn from_ref(inner: AssetRef, database: &mut AssetDatabase) -> Result<Self, Box<dyn Error>> {
346        let sender = database.commands_sender();
347        let handle = inner.ensure(database)?.handle;
348        handle
349            .ensure::<AssetReferenceCounter>(database)?
350            .increment();
351        database
352            .storage
353            .update::<AssetReferenceCounter>(handle.entity());
354        Ok(Self { inner, sender })
355    }
356
357    /// Converts the `SmartAssetRef` into a regular `AssetRef`, decrementing
358    /// asset reference counter in the process.
359    pub fn into_ref(self) -> AssetRef {
360        self.inner.clone()
361    }
362}
363
364impl Clone for SmartAssetRef {
365    fn clone(&self) -> Self {
366        if let Ok(handle) = self.inner.handle() {
367            self.sender.send(Box::new(move |storage| {
368                if let Ok(mut counter) =
369                    storage.component_mut::<true, AssetReferenceCounter>(handle.entity())
370                {
371                    counter.increment();
372                    storage.update::<AssetReferenceCounter>(handle.entity());
373                }
374            }));
375        }
376        Self {
377            inner: self.inner.clone(),
378            sender: self.sender.clone(),
379        }
380    }
381}
382
383impl Deref for SmartAssetRef {
384    type Target = AssetRef;
385
386    fn deref(&self) -> &Self::Target {
387        &self.inner
388    }
389}
390
391impl DerefMut for SmartAssetRef {
392    fn deref_mut(&mut self) -> &mut Self::Target {
393        &mut self.inner
394    }
395}