Import Oxide engine (Stages 0–10) under MIT license
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>
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
//! [`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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user