//! [`Handle`]: a typed, ref-counted reference to a loaded asset. //! //! A handle is the unit of *ownership* in the asset system. It is cheap to clone //! (an `Arc` bump), and the asset behind it lives exactly as long as at least //! one handle does — drop the last handle and the asset is freed. The //! [`AssetServer`](super::AssetServer) keeps only a [`Weak`] reference in its //! dedup cache, so it never keeps an otherwise-unused asset alive. use std::fmt; use std::path::{Path, PathBuf}; use std::sync::{Arc, Condvar, Mutex}; /// A process-unique identifier assigned to every asset slot. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct AssetId(pub(crate) u64); impl AssetId { /// The raw numeric id. pub fn value(self) -> u64 { self.0 } } /// The lifecycle state of an asset behind a [`Handle`]. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LoadState { /// A background load is in progress; the value is not ready yet. Loading, /// The asset loaded successfully and can be read with [`Handle::get`]. Loaded, /// Loading failed; see [`Handle::error`] for why. Failed, } /// The interior of an asset slot: its current state and (once ready) the value. /// /// The value is stored as an `Arc` so it can be cloned out cheaply and so a /// live reload can swap in fresh contents without disturbing readers that /// already hold the previous `Arc`. pub(crate) enum CellState { Loading, Loaded(Arc), Failed(Arc), } /// The shared, reference-counted storage for one asset. /// /// Handles hold an `Arc>`; the server's cache holds a /// `Weak` to the same allocation for deduplication only. pub(crate) struct AssetCell { id: AssetId, source: Option, state: Mutex>, ready: Condvar, } impl AssetCell { pub(crate) fn new_loading(id: AssetId, source: Option) -> Arc { Arc::new(Self { id, source, state: Mutex::new(CellState::Loading), ready: Condvar::new(), }) } pub(crate) fn new_loaded(id: AssetId, source: Option, value: T) -> Arc { Arc::new(Self { id, source, state: Mutex::new(CellState::Loaded(Arc::new(value))), ready: Condvar::new(), }) } pub(crate) fn new_failed(id: AssetId, source: Option, message: String) -> Arc { Arc::new(Self { id, source, state: Mutex::new(CellState::Failed(Arc::from(message))), ready: Condvar::new(), }) } pub(crate) fn set_loaded(&self, value: T) { *self.state.lock().unwrap() = CellState::Loaded(Arc::new(value)); self.ready.notify_all(); } pub(crate) fn set_failed(&self, message: String) { *self.state.lock().unwrap() = CellState::Failed(Arc::from(message)); self.ready.notify_all(); } } /// A typed, reference-counted handle to an asset of type `T`. /// /// Clone it freely to share ownership; the asset is freed when the last handle /// is dropped. Read the value with [`get`](Self::get) (returns `None` until the /// asset is loaded) or block for it with [`wait`](Self::wait). pub struct Handle { cell: Arc>, } impl Handle { pub(crate) fn from_cell(cell: Arc>) -> Self { Self { cell } } /// This asset's process-unique id. pub fn id(&self) -> AssetId { self.cell.id } /// The source path the asset was loaded from, if any (in-memory assets added /// with [`AssetServer::add`](super::AssetServer::add) have none). pub fn source(&self) -> Option<&Path> { self.cell.source.as_deref() } /// The current lifecycle state. pub fn state(&self) -> LoadState { match &*self.cell.state.lock().unwrap() { CellState::Loading => LoadState::Loading, CellState::Loaded(_) => LoadState::Loaded, CellState::Failed(_) => LoadState::Failed, } } /// Whether the asset has finished loading successfully. pub fn is_loaded(&self) -> bool { matches!(&*self.cell.state.lock().unwrap(), CellState::Loaded(_)) } /// The loaded value as a cheap `Arc` clone, or `None` if it is still /// loading or failed. pub fn get(&self) -> Option> { match &*self.cell.state.lock().unwrap() { CellState::Loaded(value) => Some(value.clone()), _ => None, } } /// The error message if loading failed, else `None`. pub fn error(&self) -> Option { match &*self.cell.state.lock().unwrap() { CellState::Failed(message) => Some(message.to_string()), _ => None, } } /// Blocks until the asset is no longer [`Loading`](LoadState::Loading), /// returning the value on success or `None` if it failed. pub fn wait(&self) -> Option> { let mut guard = self.cell.state.lock().unwrap(); loop { match &*guard { CellState::Loading => guard = self.cell.ready.wait(guard).unwrap(), CellState::Loaded(value) => return Some(value.clone()), CellState::Failed(_) => return None, } } } /// The number of live handles to this asset (including this one). The /// server holds only a weak reference, so this counts handles alone. pub fn ref_count(&self) -> usize { Arc::strong_count(&self.cell) } /// Replaces the asset's contents in place; every existing handle observes /// the new value on its next [`get`](Self::get). Used by live reload. pub(crate) fn set_loaded(&self, value: T) { self.cell.set_loaded(value); } /// Marks the asset as failed in place. pub(crate) fn set_failed(&self, message: String) { self.cell.set_failed(message); } } impl Clone for Handle { fn clone(&self) -> Self { Self { cell: self.cell.clone(), } } } impl fmt::Debug for Handle { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Handle") .field("id", &self.cell.id.0) .field("state", &self.state()) .field("source", &self.cell.source) .finish() } }