f56a1eea3b
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
200 lines
6.3 KiB
Rust
200 lines
6.3 KiB
Rust
//! [`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<T>` 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<T> {
|
|
Loading,
|
|
Loaded(Arc<T>),
|
|
Failed(Arc<str>),
|
|
}
|
|
|
|
/// The shared, reference-counted storage for one asset.
|
|
///
|
|
/// Handles hold an `Arc<AssetCell<T>>`; the server's cache holds a
|
|
/// `Weak<dyn Any>` to the same allocation for deduplication only.
|
|
pub(crate) struct AssetCell<T> {
|
|
id: AssetId,
|
|
source: Option<PathBuf>,
|
|
state: Mutex<CellState<T>>,
|
|
ready: Condvar,
|
|
}
|
|
|
|
impl<T> AssetCell<T> {
|
|
pub(crate) fn new_loading(id: AssetId, source: Option<PathBuf>) -> Arc<Self> {
|
|
Arc::new(Self {
|
|
id,
|
|
source,
|
|
state: Mutex::new(CellState::Loading),
|
|
ready: Condvar::new(),
|
|
})
|
|
}
|
|
|
|
pub(crate) fn new_loaded(id: AssetId, source: Option<PathBuf>, value: T) -> Arc<Self> {
|
|
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<PathBuf>, message: String) -> Arc<Self> {
|
|
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<T> {
|
|
cell: Arc<AssetCell<T>>,
|
|
}
|
|
|
|
impl<T> Handle<T> {
|
|
pub(crate) fn from_cell(cell: Arc<AssetCell<T>>) -> 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<T>` clone, or `None` if it is still
|
|
/// loading or failed.
|
|
pub fn get(&self) -> Option<Arc<T>> {
|
|
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<String> {
|
|
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<Arc<T>> {
|
|
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<T> Clone for Handle<T> {
|
|
fn clone(&self) -> Self {
|
|
Self {
|
|
cell: self.cell.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T> fmt::Debug for Handle<T> {
|
|
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()
|
|
}
|
|
}
|