use crate::loader::{Asset, ErasedAssetLoader};
use crate::{ResourceError, ResourceResult};
use log::debug;
use specs::Component;
use specs::VecStorage;
use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::fs::File;
use std::io::Read;
use std::marker::PhantomData;
use std::ops::Deref;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
#[derive(Debug, Default)]
pub struct AssetServer {
root: Option<PathBuf>,
loaders: HashMap<TypeId, Box<dyn ErasedAssetLoader>>,
caches: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
metas: HashMap<UntypedAssetId, Metadata>,
}
#[derive(Clone, Component, Debug)]
#[storage(VecStorage)]
pub struct Handle<A: Asset + ?Sized> {
id: AssetId<A>,
inner: Arc<RwLock<A>>,
}
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct AssetId<A: Asset + ?Sized> {
raw: u32,
generation: u32,
_marker: PhantomData<fn() -> A>,
}
impl<A: Asset + ?Sized> Copy for AssetId<A> {}
impl<A: Asset + ?Sized> Clone for AssetId<A> {
fn clone(&self) -> Self {
*self
}
}
impl<A: Asset + ?Sized> AssetId<A> {
pub(crate) fn new(raw: u32, generation: u32) -> Self {
Self {
raw,
generation,
_marker: PhantomData,
}
}
pub(crate) fn index(&self) -> usize {
self.raw as usize
}
#[allow(unused)]
pub(crate) fn raw(&self) -> u32 {
self.raw
}
pub(crate) fn generation(&self) -> u32 {
self.generation
}
}
impl<A: Asset> AssetId<A> {
pub(crate) fn untyped(self) -> UntypedAssetId {
UntypedAssetId {
type_id: TypeId::of::<A>(),
raw: self.raw,
generation: self.generation,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct UntypedAssetId {
type_id: TypeId,
raw: u32,
generation: u32,
}
#[derive(Debug, Clone)]
pub(crate) struct Metadata {
source: AssetSource,
_type_id: TypeId,
}
#[derive(Debug, Clone)]
#[allow(unused)]
pub(crate) enum AssetSource {
File {
path: PathBuf,
modified: std::time::SystemTime,
},
Reader,
Internal,
}
#[derive(Debug)]
struct AssetSlot<A: Asset> {
generation: u32,
asset: Option<Arc<RwLock<A>>>,
}
#[derive(Debug)]
struct AssetCache<A: Asset> {
assets: Vec<AssetSlot<A>>,
names: HashMap<String, AssetId<A>>,
}
impl<A: Asset> AssetCache<A> {
fn new() -> Self {
Self {
assets: Vec::new(),
names: HashMap::new(),
}
}
fn insert(&mut self, asset: A) -> Handle<A> {
let raw = self.assets.len() as u32;
let generation = 0;
let id = AssetId::new(raw, generation);
let handle = Handle::new(id, asset);
self.assets.push(AssetSlot {
generation,
asset: Some(handle.inner()),
});
handle
}
fn insert_named(&mut self, name: String, asset: A) -> Handle<A> {
let handle = self.insert(asset);
self.names.insert(name, handle.id());
handle
}
fn get(&self, id: AssetId<A>) -> Option<Handle<A>> {
let slot = self.assets.get(id.index())?;
if slot.generation != id.generation() {
return None;
}
let inner = slot.asset.as_ref()?.clone();
Some(Handle::from_inner(id, inner))
}
fn get_named(&self, name: &str) -> Option<Handle<A>> {
let id = self.names.get(name)?;
self.get(*id)
}
fn id_named(&self, name: &str) -> Option<AssetId<A>> {
self.names.get(name).copied()
}
fn reload(&mut self, id: AssetId<A>, new_asset: A) -> ResourceResult<()> {
let slot = self
.assets
.get_mut(id.index())
.ok_or(ResourceError::ResourceDoesNotExist)?;
if slot.generation != id.generation() {
return Err(ResourceError::ResourceDoesNotExist);
}
let existing = slot
.asset
.as_ref()
.ok_or(ResourceError::ResourceDoesNotExist)?;
*existing.write().expect("asset lock poisoned") = new_asset;
Ok(())
}
}
impl<A: Asset> Handle<A> {
pub fn id(&self) -> AssetId<A> {
self.id
}
pub(crate) fn new(id: AssetId<A>, asset: A) -> Self {
Self {
id,
inner: Arc::new(RwLock::new(asset)),
}
}
pub(crate) fn from_inner(id: AssetId<A>, inner: Arc<RwLock<A>>) -> Self {
Self { id, inner }
}
pub(crate) fn inner(&self) -> Arc<RwLock<A>> {
self.inner.clone()
}
pub fn read(&self) -> AssetRead<'_, A> {
AssetRead {
guard: self.inner.read().expect("asset lock poisoned"),
}
}
pub fn write(&self) -> AssetWrite<'_, A> {
AssetWrite {
guard: self.inner.write().expect("asset lock poisoned"),
}
}
}
impl AssetServer {
pub fn new() -> Self {
Self::default()
}
pub fn with_root(root: impl Into<PathBuf>) -> Self {
Self {
root: Some(root.into()),
..Self::default()
}
}
pub fn register_asset_type<A: Asset>(&mut self) {
self.caches
.entry(TypeId::of::<A>())
.or_insert_with(|| Box::new(AssetCache::<A>::new()));
}
pub fn register_loader<A: Asset>(&mut self, loader: Box<dyn ErasedAssetLoader>) {
self.register_asset_type::<A>();
self.loaders.insert(TypeId::of::<A>(), loader);
}
pub fn load_reader<A: Asset>(
&mut self,
name: &str,
reader: &mut dyn Read,
) -> ResourceResult<Handle<A>> {
let asset = self.load_asset_from_reader::<A>(reader)?;
let handle = self
.cache_mut::<A>()
.expect("asset cache was not registered")
.insert_named(name.to_string(), asset);
self.metas.insert(
handle.id().untyped(),
Metadata {
source: AssetSource::Reader,
_type_id: TypeId::of::<A>(),
},
);
Ok(handle)
}
pub fn insert<A: Asset>(&mut self, asset: A) -> ResourceResult<Handle<A>> {
let handle = self
.cache_mut::<A>()
.expect("asset cache was not registered")
.insert(asset);
self.metas.insert(
handle.id().untyped(),
Metadata {
source: AssetSource::Internal,
_type_id: TypeId::of::<A>(),
},
);
Ok(handle)
}
pub fn insert_named<A: Asset>(
&mut self,
name: impl Into<String>,
asset: A,
) -> ResourceResult<Handle<A>> {
let handle = self
.cache_mut::<A>()
.expect("asset cache was not registered")
.insert_named(name.into(), asset);
self.metas.insert(
handle.id().untyped(),
Metadata {
source: AssetSource::Internal,
_type_id: TypeId::of::<A>(),
},
);
Ok(handle)
}
pub fn load<A: Asset>(&mut self, path: impl Into<PathBuf>) -> ResourceResult<Handle<A>> {
let root = self
.root
.as_ref()
.ok_or(ResourceError::AssetServerUnsupported)?;
let path = path.into();
let name = asset_name(&path)?;
let full_path = root.join(&path);
debug!("loading asset from {:?}", full_path);
let modified = std::fs::metadata(&full_path)?.modified()?;
let mut reader = File::open(&full_path)?;
let asset = self.load_asset_from_reader::<A>(&mut reader)?;
let handle = self
.cache_mut::<A>()
.expect("asset cache was not registered")
.insert_named(name, asset);
self.metas.insert(
handle.id().untyped(),
Metadata {
source: AssetSource::File {
path: full_path,
modified,
},
_type_id: TypeId::of::<A>(),
},
);
Ok(handle)
}
pub fn get<A: Asset>(&self, id: AssetId<A>) -> Option<Handle<A>> {
self.cache::<A>()?.get(id)
}
pub fn get_named<A: Asset>(&self, name: &str) -> Option<Handle<A>> {
self.cache::<A>()?.get_named(name)
}
pub fn id_named<A: Asset>(&self, name: &str) -> Option<AssetId<A>> {
self.cache::<A>()?.id_named(name)
}
pub fn reload<A: Asset>(&mut self, id: AssetId<A>) -> ResourceResult<()> {
self.root
.as_ref()
.ok_or(ResourceError::AssetServerUnsupported)?;
let source = self
.metas
.get(&id.untyped())
.ok_or(ResourceError::ResourceDoesNotExist)?
.source
.clone();
let AssetSource::File { path, .. } = source else {
return Err(ResourceError::ResourceDoesNotExist);
};
let mut reader = File::open(&path)?;
let new_asset = self.load_asset_from_reader::<A>(&mut reader)?;
self.cache_mut::<A>()
.expect("asset cache was not registered")
.reload(id, new_asset)?;
Ok(())
}
pub fn reload_named<A: Asset>(&mut self, name: &str) -> ResourceResult<()> {
let id = self
.id_named::<A>(name)
.ok_or(ResourceError::ResourceDoesNotExist)?;
self.reload(id)
}
fn load_asset_from_reader<A: Asset>(&self, reader: &mut dyn Read) -> ResourceResult<A> {
let loader = self
.loaders
.get(&TypeId::of::<A>())
.ok_or(ResourceError::LoaderNotFound)?;
loader
.load_erased(reader)?
.downcast::<A>()
.map(|asset| *asset)
.map_err(|_| ResourceError::LoaderReturnedWrongType)
}
fn cache<A: Asset>(&self) -> Option<&AssetCache<A>> {
self.caches
.get(&TypeId::of::<A>())?
.downcast_ref::<AssetCache<A>>()
}
fn cache_mut<A: Asset>(&mut self) -> Option<&mut AssetCache<A>> {
self.caches
.get_mut(&TypeId::of::<A>())?
.downcast_mut::<AssetCache<A>>()
}
}
fn asset_name(path: &Path) -> ResourceResult<String> {
path.with_extension("")
.to_str()
.map(ToOwned::to_owned)
.ok_or(ResourceError::NotFound(path.to_path_buf()))
}
pub struct AssetRead<'a, A> {
guard: RwLockReadGuard<'a, A>,
}
impl<A> Deref for AssetRead<'_, A> {
type Target = A;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
pub struct AssetWrite<'a, A> {
guard: RwLockWriteGuard<'a, A>,
}
impl<A> Deref for AssetWrite<'_, A> {
type Target = A;
fn deref(&self) -> &Self::Target {
&self.guard
}
}
impl<A> std::ops::DerefMut for AssetWrite<'_, A> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.guard
}
}