use crate::{
Asset,
AssetError,
lock::{RwLock, rwlock, CacheEntry, AssetRefLock},
};
use std::{
any::TypeId,
borrow::Borrow,
fmt,
fs,
path::PathBuf,
};
#[cfg(feature = "hashbrown")]
use hashbrown::HashMap;
#[cfg(not(feature = "hashbrown"))]
use std::collections::HashMap;
#[derive(Debug, Hash, PartialEq, Eq)]
#[repr(C)]
struct Key {
id: Box<str>,
type_id: TypeId,
}
impl Key {
#[inline]
fn new<T: 'static>(id: Box<str>) -> Self {
Self {
id,
type_id: TypeId::of::<T>(),
}
}
}
impl<'a> AccessKey<'a> {
#[inline]
fn new<T: 'static>(id: &'a str) -> Self {
Self {
id,
type_id: TypeId::of::<T>(),
}
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
#[repr(C)]
struct AccessKey<'a> {
id: &'a str,
type_id: TypeId,
}
impl<'a> Borrow<AccessKey<'a>> for Key {
#[inline]
fn borrow(&self) -> &AccessKey<'a> {
unsafe {
let ptr = self as *const Key as *const AccessKey;
&*ptr
}
}
}
pub struct AssetCache<'a> {
assets: RwLock<HashMap<Key, CacheEntry>>,
path: &'a str,
}
impl<'a> AssetCache<'a> {
#[inline]
pub fn new(path: &str) -> AssetCache {
AssetCache {
assets: RwLock::new(HashMap::new()),
path,
}
}
pub(crate) fn add_asset<A: Asset>(&self, id: String, asset: A) -> AssetRefLock<A> {
let entry = CacheEntry::new(asset);
let asset = unsafe { entry.get_ref() };
let key = Key::new::<A>(id.into());
let mut cache = rwlock::write(&self.assets);
cache.insert(key, entry);
asset
}
pub fn load<A: Asset>(&self, id: &str) -> Result<AssetRefLock<A>, AssetError> {
if let Some(asset) = self.load_cached(id) {
return Ok(asset);
}
let asset = self.load_from_path(id)?;
Ok(self.add_asset(id.to_string(), asset))
}
pub fn load_cached<A: Asset>(&self, id: &str) -> Option<AssetRefLock<A>> {
let key = AccessKey::new::<A>(id);
let cache = rwlock::read(&self.assets);
cache.get(&key).map(|asset| unsafe { asset.get_ref() })
}
#[inline]
pub fn load_expect<A: Asset>(&self, id: &str) -> AssetRefLock<A> {
self.load(id).expect("Could not load essential asset")
}
pub fn reload<A: Asset>(&self, id: &str) -> Result<AssetRefLock<A>, AssetError> {
let asset = self.load_from_path(id)?;
let cache = rwlock::read(&self.assets);
if let Some(cached) = cache.get(&AccessKey::new::<A>(id)) {
return unsafe { Ok(cached.write(asset)) };
}
drop(cache);
Ok(self.add_asset(id.to_string(), asset))
}
fn load_from_path<A: Asset>(&self, id: &str) -> Result<A, AssetError> {
let mut path = PathBuf::from(self.path);
path.push(id.replace(".", "/"));
path.set_extension(A::EXT);
let content = fs::read(&path)?;
A::load_from_raw(content)
}
#[inline]
pub fn remove<A: Asset>(&mut self, id: &str) {
let key = AccessKey::new::<A>(id);
let cache = rwlock::get_mut(&mut self.assets);
cache.remove(&key);
}
pub fn take<A: Asset>(&mut self, id: &str) -> Option<A> {
let key = AccessKey::new::<A>(id);
let cache = rwlock::get_mut(&mut self.assets);
cache.remove(&key).map(|entry| unsafe { entry.into_inner() })
}
#[inline]
pub fn clear(&mut self) {
rwlock::get_mut(&mut self.assets).clear();
}
}
impl fmt::Debug for AssetCache<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AssetCache")
.field("path", &self.path)
.field("assets", &self.assets.read())
.finish()
}
}