#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(all(doc, feature = "unstable-doc"), feature(doc_cfg))]
#![doc(
html_logo_url = "https://raw.githubusercontent.com/zakarumych/goods/master/logo/goods.logo.png"
)]
extern crate alloc;
mod asset;
mod bytes;
mod channel;
mod formats;
mod handle;
mod process;
mod registry;
mod source;
mod spawn;
pub use self::{asset::*, formats::*, handle::*, registry::*, registry::*, source::*, spawn::*};
use {
crate::{
channel::{slot, Sender},
process::{AnyProcess, Process, Processor},
},
alloc::{boxed::Box, vec::Vec},
core::{
any::TypeId,
borrow::Borrow,
fmt::{self, Debug, Display},
future::Future,
hash::{BuildHasher, Hash, Hasher},
pin::Pin,
task::{Context, Poll},
},
hashbrown::hash_map::{Entry, HashMap, RawEntryMut},
maybe_sync::{dyn_maybe_send_sync, BoxFuture, MaybeSend, MaybeSync, Mutex, Rc},
};
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct Ready<T>(Option<T>);
impl<T> Unpin for Ready<T> {}
impl<T> Future for Ready<T> {
type Output = T;
#[inline]
fn poll(mut self: Pin<&mut Self>, _ctx: &mut Context<'_>) -> Poll<T> {
Poll::Ready(self.0.take().expect("Ready polled after completion"))
}
}
pub const fn ready<T>(value: T) -> Ready<T> {
Ready(Some(value))
}
pub enum Error<A: Asset> {
NotFound,
SpawnError,
Asset(Rc<A::Error>),
#[cfg(not(feature = "std"))]
Format(Rc<dyn_maybe_send_sync!(Display)>),
#[cfg(feature = "std")]
Format(Rc<dyn_maybe_send_sync!(std::error::Error)>),
#[cfg(not(feature = "std"))]
Source(Rc<dyn_maybe_send_sync!(Display)>),
#[cfg(feature = "std")]
Source(Rc<dyn_maybe_send_sync!(std::error::Error)>),
}
impl<A> From<SourceError> for Error<A>
where
A: Asset,
{
fn from(err: SourceError) -> Self {
match err {
SourceError::NotFound => Error::NotFound,
SourceError::Error(err) => Error::Source(err),
}
}
}
impl<A> Clone for Error<A>
where
A: Asset,
{
fn clone(&self) -> Self {
match self {
Error::NotFound => Error::NotFound,
Error::SpawnError => Error::SpawnError,
Error::Asset(err) => Error::Asset(err.clone()),
Error::Format(err) => Error::Format(err.clone()),
Error::Source(err) => Error::Source(err.clone()),
}
}
}
impl<A> Debug for Error<A>
where
A: Asset,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::NotFound => fmt.write_str("Error::NotFound"),
Error::SpawnError => fmt.write_str("Error::SpawnError"),
Error::Asset(err) => write!(fmt, "Error::Asset({})", err),
Error::Format(err) => write!(fmt, "Error::Format({})", err),
Error::Source(err) => write!(fmt, "Error::Source({})", err),
}
}
}
impl<A> Display for Error<A>
where
A: Asset,
{
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::NotFound => fmt.write_str("Asset not found"),
Error::SpawnError => fmt.write_str("Failed to spawn loading task"),
Error::Asset(err) => write!(fmt, "Asset error: {}", err),
Error::Format(err) => write!(fmt, "Format error: {}", err),
Error::Source(err) => write!(fmt, "Source error: {}", err),
}
}
}
#[cfg(feature = "std")]
impl<A> std::error::Error for Error<A>
where
A: Asset,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::NotFound => None,
Error::SpawnError => None,
Error::Asset(err) => Some(&**err),
Error::Format(err) => Some(&**err),
Error::Source(err) => Some(&**err),
}
}
}
pub trait Key: Eq + Hash + Clone + MaybeSend + MaybeSync + 'static {}
impl<T> Key for T where T: Eq + Hash + Clone + MaybeSend + MaybeSync + 'static {}
pub struct Cache<K> {
registry: Registry<K>,
inner: Rc<Inner<K, dyn_maybe_send_sync!(Spawn)>>,
}
struct Inner<K, S: ?Sized> {
cache: Mutex<HashMap<(TypeId, K), AnyHandle>>,
processor: Processor,
spawn: S,
}
impl<K> Debug for Cache<K> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_struct("goods::Cache")
.field("registry", &self.registry)
.field("spawn", &&self.inner.spawn)
.finish()
}
}
impl<K> Clone for Cache<K> {
fn clone(&self) -> Self {
Cache {
registry: self.registry.clone(),
inner: self.inner.clone(),
}
}
}
impl<K> Cache<K> {
pub fn new<S>(registry: Registry<K>, spawn: S) -> Self
where
S: Spawn + MaybeSend + MaybeSync + 'static,
{
Cache {
registry,
inner: Rc::new(Inner {
cache: Mutex::default(),
processor: Processor::new(),
spawn,
}),
}
}
pub fn load<A>(&self, key: K) -> Handle<A>
where
K: Key,
A: AssetDefaultFormat<K>,
{
self.load_with_format(key, A::DefaultFormat::default())
}
pub fn load_with_format<A, F>(&self, key: K, format: F) -> Handle<A>
where
K: Key,
A: Asset,
F: Format<A, K>,
{
let tid = TypeId::of::<A>();
let mut lock = self.inner.cache.lock();
match lock.entry((tid, key.clone())) {
Entry::Occupied(entry) => {
let any = entry.get().clone();
drop(lock);
any.downcast::<A>().unwrap()
}
Entry::Vacant(entry) => {
let handle = Handle::new();
entry.insert(handle.clone().into());
drop(lock);
let task: BoxFuture<'_, _> =
if TypeId::of::<A::Context>() == TypeId::of::<PhantomContext>() {
Box::pin(load_asset_with_phantom_context(
self.registry.clone().read(key),
format,
self.clone(),
handle.clone(),
))
} else {
Box::pin(load_asset(
self.registry.clone().read(key),
format,
self.clone(),
self.inner.processor.sender::<A>(),
handle.clone(),
))
};
if let Err(SpawnError) = self.inner.spawn.spawn(task) {
handle.set(Err(Error::SpawnError));
}
handle
}
}
}
pub fn remove<A, Q>(&self, key: &Q) -> bool
where
A: 'static,
K: Key + Borrow<Q>,
Q: Hash + Eq,
{
let mut lock = self.inner.cache.lock();
let mut hasher = lock.hasher().build_hasher();
(TypeId::of::<A>(), key).hash(&mut hasher);
let hash = hasher.finish();
let entry = lock.raw_entry_mut().from_hash(hash, |(tid, k)| {
*tid == TypeId::of::<A>() && k.borrow() == key
});
match entry {
RawEntryMut::Occupied(entry) => {
entry.remove();
true
}
_ => false,
}
}
pub fn process<C: 'static>(&self, ctx: &mut C)
where
K: 'static,
{
self.inner.processor.run(ctx);
}
}
#[cfg(feature = "sync")]
#[allow(dead_code)]
fn test_loader_send_sync<K: MaybeSend>() {
fn is_send<T: MaybeSend>() {}
fn is_sync<T: MaybeSync>() {}
is_send::<Cache<K>>();
is_sync::<Cache<K>>();
}
pub(crate) async fn load_asset<A, F, K, L>(
loading: L,
format: F,
cache: Cache<K>,
process_sender: Sender<Box<dyn AnyProcess<A::Context>>>,
handle: Handle<A>,
) where
A: Asset,
F: Format<A, K>,
L: Future<Output = Result<Vec<u8>, SourceError>> + MaybeSend + 'static,
{
handle.set(
async move {
let bytes = loading.await?;
let decode = format.decode(bytes, &cache);
drop(cache);
let repr = decode.await.map_err(|err| Error::Format(Rc::new(err)))?;
let (slot, setter) = slot::<A::BuildFuture>();
process_sender.send(Box::new(Process::<A> { repr, setter }));
slot.await.await.map_err(|err| Error::Asset(Rc::new(err)))
}
.await,
)
}
pub(crate) async fn load_asset_with_phantom_context<A, F, K, L>(
loading: L,
format: F,
cache: Cache<K>,
handle: Handle<A>,
) where
A: Asset,
F: Format<A, K>,
L: Future<Output = Result<Vec<u8>, SourceError>> + MaybeSend + 'static,
{
debug_assert_eq!(TypeId::of::<A::Context>(), TypeId::of::<PhantomContext>());
handle.set(
async move {
let bytes = loading.await?;
let decode = format.decode(bytes, &cache);
drop(cache);
let repr = decode.await.map_err(|err| Error::Format(Rc::new(err)))?;
let build = A::build(repr, unsafe {
&mut *{ &mut PhantomContext as *mut _ as *mut A::Context }
});
build.await.map_err(|err| Error::Asset(Rc::new(err)))
}
.await,
)
}