use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct AssetId(u64);
static NEXT_ASSET_ID: AtomicU64 = AtomicU64::new(1);
impl AssetId {
fn next() -> Self {
Self(NEXT_ASSET_ID.fetch_add(1, Ordering::Relaxed))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LoadState {
NotLoaded,
Loading,
Loaded,
Failed,
}
impl LoadState {
pub fn is_loaded(&self) -> bool {
matches!(self, LoadState::Loaded)
}
pub fn is_failed(&self) -> bool {
matches!(self, LoadState::Failed)
}
}
#[derive(Debug)]
pub struct AssetHandle<T> {
inner: Arc<AssetHandleInner<T>>,
}
impl AssetId {
pub fn from_raw(id: u64) -> Self {
Self(id)
}
}
#[derive(Debug)]
struct AssetHandleInner<T> {
id: AssetId,
path: PathBuf,
_marker: std::marker::PhantomData<T>,
}
impl<T> AssetHandle<T> {
pub fn new(id: AssetId, path: PathBuf) -> Self {
Self {
inner: Arc::new(AssetHandleInner {
id,
path,
_marker: std::marker::PhantomData,
}),
}
}
pub fn id(&self) -> AssetId {
self.inner.id
}
pub fn path(&self) -> &Path {
&self.inner.path
}
pub fn ref_count(&self) -> usize {
Arc::strong_count(&self.inner)
}
}
impl<T> Clone for AssetHandle<T> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<T> PartialEq for AssetHandle<T> {
fn eq(&self, other: &Self) -> bool {
self.inner.id == other.inner.id
}
}
impl<T> Eq for AssetHandle<T> {}
impl<T> std::hash::Hash for AssetHandle<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.id.hash(state);
}
}
pub struct AssetStorage<T> {
assets: HashMap<AssetId, T>,
states: HashMap<AssetId, LoadState>,
}
impl<T> Default for AssetStorage<T> {
fn default() -> Self {
Self {
assets: HashMap::new(),
states: HashMap::new(),
}
}
}
impl<T> AssetStorage<T> {
pub fn new() -> Self {
Self::default()
}
pub fn insert(&mut self, id: AssetId, asset: T) {
self.assets.insert(id, asset);
self.states.insert(id, LoadState::Loaded);
}
pub fn get(&self, id: AssetId) -> Option<&T> {
self.assets.get(&id)
}
pub fn load_state(&self, id: AssetId) -> LoadState {
self.states.get(&id).copied().unwrap_or(LoadState::NotLoaded)
}
pub fn set_state(&mut self, id: AssetId, state: LoadState) {
self.states.insert(id, state);
}
pub fn remove(&mut self, id: AssetId) -> Option<T> {
self.states.remove(&id);
self.assets.remove(&id)
}
pub fn len(&self) -> usize {
self.assets.len()
}
pub fn is_empty(&self) -> bool {
self.assets.is_empty()
}
}
pub struct AssetServer {
asset_root: PathBuf,
path_to_id: HashMap<PathBuf, AssetId>,
states: HashMap<AssetId, LoadState>,
}
impl AssetServer {
pub fn new(asset_root: impl Into<PathBuf>) -> Self {
Self {
asset_root: asset_root.into(),
path_to_id: HashMap::new(),
states: HashMap::new(),
}
}
pub fn load<T>(&mut self, path: impl AsRef<Path>) -> AssetHandle<T> {
let full_path = self.asset_root.join(path.as_ref());
let id = *self.path_to_id.entry(full_path.clone()).or_insert_with(|| {
let id = AssetId::next();
id
});
if !self.states.contains_key(&id) {
self.states.insert(id, LoadState::Loading);
}
AssetHandle::new(id, full_path)
}
pub fn load_state<T>(&self, handle: &AssetHandle<T>) -> LoadState {
self.states.get(&handle.id()).copied().unwrap_or(LoadState::NotLoaded)
}
pub fn mark_loaded(&mut self, id: AssetId) {
self.states.insert(id, LoadState::Loaded);
}
pub fn mark_failed(&mut self, id: AssetId) {
self.states.insert(id, LoadState::Failed);
}
pub fn asset_root(&self) -> &Path {
&self.asset_root
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_load_state() {
assert!(LoadState::Loaded.is_loaded());
assert!(!LoadState::Loading.is_loaded());
assert!(LoadState::Failed.is_failed());
assert!(!LoadState::Loaded.is_failed());
}
#[test]
fn test_asset_handle_refcount() {
let handle: AssetHandle<String> = AssetHandle::new(
AssetId::from_raw(1),
"test.txt".into(),
);
assert_eq!(handle.ref_count(), 1);
let handle2 = handle.clone();
assert_eq!(handle.ref_count(), 2);
assert_eq!(handle2.ref_count(), 2);
drop(handle2);
assert_eq!(handle.ref_count(), 1);
}
#[test]
fn test_asset_storage() {
let mut storage = AssetStorage::new();
let id = AssetId::from_raw(1);
assert!(storage.is_empty());
assert_eq!(storage.load_state(id), LoadState::NotLoaded);
storage.insert(id, "hello".to_string());
assert_eq!(storage.len(), 1);
assert_eq!(storage.get(id), Some(&"hello".to_string()));
assert_eq!(storage.load_state(id), LoadState::Loaded);
}
#[test]
fn test_asset_server_dedup() {
let mut server = AssetServer::new("assets");
let h1: AssetHandle<String> = server.load("test.txt");
let h2: AssetHandle<String> = server.load("test.txt");
assert_eq!(h1.id(), h2.id());
}
#[test]
fn test_asset_server_load_state() {
let mut server = AssetServer::new("assets");
let handle: AssetHandle<String> = server.load("test.txt");
assert_eq!(server.load_state(&handle), LoadState::Loading);
server.mark_loaded(handle.id());
assert_eq!(server.load_state(&handle), LoadState::Loaded);
}
}