use downcast_rs::{impl_downcast, Downcast};
use fxhash::FxHashMap;
use legion_core::borrow::{AtomicRefCell, Ref, RefMut};
use legion_core::query::{Read, ReadOnly, Write};
use std::{
any::TypeId,
marker::PhantomData,
ops::{Deref, DerefMut},
};
#[cfg(not(feature = "ffi"))]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ResourceTypeId(TypeId);
#[cfg(not(feature = "ffi"))]
impl ResourceTypeId {
pub fn of<T: Resource>() -> Self { Self(TypeId::of::<T>()) }
}
#[cfg(feature = "ffi")]
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ResourceTypeId(TypeId, u32);
#[cfg(feature = "ffi")]
impl ResourceTypeId {
pub fn of<T: Resource>() -> Self { Self(TypeId::of::<T>(), 0) }
}
pub trait ResourceSet: Send + Sync {
type PreparedResources;
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources;
fn fetch_mut(resources: &mut Resources) -> Self::PreparedResources {
unsafe { Self::fetch_unchecked(resources) }
}
fn fetch(resources: &Resources) -> Self::PreparedResources
where
Self: ReadOnly,
{
unsafe { Self::fetch_unchecked(resources) }
}
}
pub trait Resource: 'static + Downcast + Send + Sync {}
impl<T> Resource for T where T: 'static + Send + Sync {}
impl_downcast!(Resource);
pub struct PreparedRead<T: Resource> {
resource: *const T,
}
impl<T: Resource> PreparedRead<T> {
pub(crate) unsafe fn new(resource: *const T) -> Self { Self { resource } }
}
impl<T: Resource> Deref for PreparedRead<T> {
type Target = T;
fn deref(&self) -> &Self::Target { unsafe { &*self.resource } }
}
unsafe impl<T: Resource> Send for PreparedRead<T> {}
unsafe impl<T: Resource> Sync for PreparedRead<T> {}
pub struct PreparedWrite<T: Resource> {
resource: *mut T,
}
impl<T: Resource> Deref for PreparedWrite<T> {
type Target = T;
fn deref(&self) -> &Self::Target { unsafe { &*self.resource } }
}
impl<T: Resource> DerefMut for PreparedWrite<T> {
fn deref_mut(&mut self) -> &mut T { unsafe { &mut *self.resource } }
}
impl<T: Resource> PreparedWrite<T> {
pub(crate) unsafe fn new(resource: *mut T) -> Self { Self { resource } }
}
unsafe impl<T: Resource> Send for PreparedWrite<T> {}
unsafe impl<T: Resource> Sync for PreparedWrite<T> {}
pub struct Fetch<'a, T: 'a + Resource> {
inner: Ref<'a, Box<dyn Resource>>,
_marker: PhantomData<T>,
}
impl<'a, T: Resource> Deref for Fetch<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.inner.downcast_ref::<T>().unwrap_or_else(|| {
panic!(
"Unable to downcast the resource!: {}",
std::any::type_name::<T>()
)
})
}
}
impl<'a, T: 'a + Resource + std::fmt::Debug> std::fmt::Debug for Fetch<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.deref())
}
}
pub struct FetchMut<'a, T: Resource> {
inner: RefMut<'a, Box<dyn Resource>>,
_marker: PhantomData<T>,
}
impl<'a, T: 'a + Resource> Deref for FetchMut<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.inner.downcast_ref::<T>().unwrap_or_else(|| {
panic!(
"Unable to downcast the resource!: {}",
std::any::type_name::<T>()
)
})
}
}
impl<'a, T: 'a + Resource> DerefMut for FetchMut<'a, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
self.inner.downcast_mut::<T>().unwrap_or_else(|| {
panic!(
"Unable to downcast the resource!: {}",
std::any::type_name::<T>()
)
})
}
}
impl<'a, T: 'a + Resource + std::fmt::Debug> std::fmt::Debug for FetchMut<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.deref())
}
}
#[derive(Default)]
pub struct Resources {
storage: FxHashMap<ResourceTypeId, AtomicRefCell<Box<dyn Resource>>>,
}
impl Resources {
pub fn contains<T: Resource>(&self) -> bool {
self.storage.contains_key(&ResourceTypeId::of::<T>())
}
pub fn insert<T: Resource>(&mut self, value: T) {
self.storage.insert(
ResourceTypeId::of::<T>(),
AtomicRefCell::new(Box::new(value)),
);
}
pub fn remove<T: Resource>(&mut self) -> Option<T> {
Some(
*self
.storage
.remove(&ResourceTypeId::of::<T>())?
.into_inner()
.downcast::<T>()
.ok()?,
)
}
pub fn get<T: Resource>(&self) -> Option<Fetch<'_, T>> {
Some(Fetch {
inner: self.storage.get(&ResourceTypeId::of::<T>())?.get(),
_marker: Default::default(),
})
}
pub fn get_mut<T: Resource>(&self) -> Option<FetchMut<'_, T>> {
Some(FetchMut {
inner: self.storage.get(&ResourceTypeId::of::<T>())?.get_mut(),
_marker: Default::default(),
})
}
pub fn get_or_insert_with<T: Resource, F: FnOnce() -> T>(
&mut self,
f: F,
) -> Option<Fetch<'_, T>> {
self.get_or_insert((f)())
}
pub fn get_mut_or_insert_with<T: Resource, F: FnOnce() -> T>(
&mut self,
f: F,
) -> Option<FetchMut<'_, T>> {
self.get_mut_or_insert((f)())
}
pub fn get_or_insert<T: Resource>(&mut self, value: T) -> Option<Fetch<'_, T>> {
Some(Fetch {
inner: self
.storage
.entry(ResourceTypeId::of::<T>())
.or_insert_with(|| AtomicRefCell::new(Box::new(value)))
.get(),
_marker: Default::default(),
})
}
pub fn get_mut_or_insert<T: Resource>(&mut self, value: T) -> Option<FetchMut<'_, T>> {
Some(FetchMut {
inner: self
.storage
.entry(ResourceTypeId::of::<T>())
.or_insert_with(|| AtomicRefCell::new(Box::new(value)))
.get_mut(),
_marker: Default::default(),
})
}
pub fn get_or_default<T: Resource + Default>(&mut self) -> Option<Fetch<'_, T>> {
Some(Fetch {
inner: self
.storage
.entry(ResourceTypeId::of::<T>())
.or_insert_with(|| AtomicRefCell::new(Box::new(T::default())))
.get(),
_marker: Default::default(),
})
}
pub fn get_mut_or_default<T: Resource + Default>(&mut self) -> Option<FetchMut<'_, T>> {
Some(FetchMut {
inner: self
.storage
.entry(ResourceTypeId::of::<T>())
.or_insert_with(|| AtomicRefCell::new(Box::new(T::default())))
.get_mut(),
_marker: Default::default(),
})
}
pub fn merge(&mut self, mut other: Resources) {
for resource in other.storage.drain() {
self.storage.entry(resource.0).or_insert(resource.1);
}
}
}
impl ResourceSet for () {
type PreparedResources = ();
unsafe fn fetch_unchecked(_: &Resources) {}
}
impl<T: Resource> ResourceSet for Read<T> {
type PreparedResources = PreparedRead<T>;
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
let resource = resources
.get::<T>()
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedRead::new(resource.deref() as *const T)
}
}
impl<T: Resource> ResourceSet for Write<T> {
type PreparedResources = PreparedWrite<T>;
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
let mut resource = resources
.get_mut::<T>()
.unwrap_or_else(|| panic!("Failed to fetch resource!: {}", std::any::type_name::<T>()));
PreparedWrite::new(resource.deref_mut() as *mut T)
}
}
macro_rules! impl_resource_tuple {
( $( $ty: ident ),* ) => {
#[allow(unused_parens, non_snake_case)]
impl<$( $ty: ResourceSet ),*> ResourceSet for ($( $ty, )*)
{
type PreparedResources = ($( $ty::PreparedResources, )*);
unsafe fn fetch_unchecked(resources: &Resources) -> Self::PreparedResources {
($( $ty::fetch_unchecked(resources), )*)
}
}
};
}
impl_resource_tuple!(A);
impl_resource_tuple!(A, B);
impl_resource_tuple!(A, B, C);
impl_resource_tuple!(A, B, C, D);
impl_resource_tuple!(A, B, C, D, E);
impl_resource_tuple!(A, B, C, D, E, F);
impl_resource_tuple!(A, B, C, D, E, F, G);
impl_resource_tuple!(A, B, C, D, E, F, G, H);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y);
impl_resource_tuple!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_read_write_test() {
let _ = tracing_subscriber::fmt::try_init();
struct TestOne {
value: String,
}
struct TestTwo {
value: String,
}
let mut resources = Resources::default();
resources.insert(TestOne {
value: "poop".to_string(),
});
resources.insert(TestTwo {
value: "balls".to_string(),
});
assert_eq!(resources.get::<TestOne>().unwrap().value, "poop");
assert_eq!(resources.get::<TestTwo>().unwrap().value, "balls");
let owned = resources.remove::<TestTwo>();
assert_eq!(owned.unwrap().value, "balls")
}
}