use std::{
any::TypeId,
collections::{hash_map::Entry, HashMap},
fmt::{Display, Formatter},
hash::{BuildHasherDefault, Hasher},
marker::PhantomData,
};
use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
use downcast_rs::{impl_downcast, Downcast};
use crate::internals::{
hash::ComponentTypeIdHasher,
query::view::{read::Read, write::Write, ReadOnly},
};
#[derive(Copy, Clone, Debug, Eq, PartialOrd, Ord)]
pub struct ResourceTypeId {
type_id: TypeId,
#[cfg(debug_assertions)]
name: &'static str,
}
impl ResourceTypeId {
pub fn of<T: Resource>() -> Self {
Self {
type_id: TypeId::of::<T>(),
#[cfg(debug_assertions)]
name: std::any::type_name::<T>(),
}
}
}
impl std::hash::Hash for ResourceTypeId {
fn hash<H: Hasher>(&self, state: &mut H) {
self.type_id.hash(state);
}
}
impl PartialEq for ResourceTypeId {
fn eq(&self, other: &Self) -> bool {
self.type_id.eq(&other.type_id)
}
}
impl Display for ResourceTypeId {
#[cfg(debug_assertions)]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.name)
}
#[cfg(not(debug_assertions))]
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.type_id)
}
}
pub trait Resource: 'static + Downcast {}
impl<T> Resource for T where T: 'static {}
impl_downcast!(Resource);
pub trait ResourceSet<'a> {
type Result: 'a;
unsafe fn fetch_unchecked(resources: &'a UnsafeResources) -> Self::Result;
fn fetch_mut(resources: &'a mut Resources) -> Self::Result {
unsafe { Self::fetch_unchecked(&resources.internal) }
}
fn fetch(resources: &'a Resources) -> Self::Result
where
Self: ReadOnly,
{
unsafe { Self::fetch_unchecked(&resources.internal) }
}
}
impl<'a> ResourceSet<'a> for () {
type Result = ();
unsafe fn fetch_unchecked(_: &UnsafeResources) -> Self::Result {}
}
impl<'a, T: Resource> ResourceSet<'a> for Read<T> {
type Result = AtomicRef<'a, T>;
unsafe fn fetch_unchecked(resources: &'a UnsafeResources) -> Self::Result {
let type_id = &ResourceTypeId::of::<T>();
resources
.get(&type_id)
.map(|x| x.get::<T>())
.unwrap_or_else(|| panic_nonexistent_resource(type_id))
}
}
impl<'a, T: Resource> ResourceSet<'a> for Write<T> {
type Result = AtomicRefMut<'a, T>;
unsafe fn fetch_unchecked(resources: &'a UnsafeResources) -> Self::Result {
let type_id = &ResourceTypeId::of::<T>();
resources
.get(&type_id)
.map(|x| x.get_mut::<T>())
.unwrap_or_else(|| panic_nonexistent_resource(type_id))
}
}
fn panic_nonexistent_resource(type_id: &ResourceTypeId) -> ! {
#[cfg(debug_assertions)]
panic!("resource {} does not exist", type_id.name);
#[cfg(not(debug_assertions))]
panic!("some resource does not exist");
}
macro_rules! resource_tuple {
($head_ty:ident) => {
impl_resource_tuple!($head_ty);
};
($head_ty:ident, $( $tail_ty:ident ),*) => (
impl_resource_tuple!($head_ty, $( $tail_ty ),*);
resource_tuple!($( $tail_ty ),*);
);
}
macro_rules! impl_resource_tuple {
( $( $ty: ident ),* ) => {
#[allow(unused_parens, non_snake_case)]
impl<'a, $( $ty: ResourceSet<'a> ),*> ResourceSet<'a> for ($( $ty, )*)
{
type Result = ($( $ty::Result, )*);
unsafe fn fetch_unchecked(resources: &'a UnsafeResources) -> Self::Result {
($( $ty::fetch_unchecked(resources), )*)
}
}
};
}
#[cfg(feature = "extended-tuple-impls")]
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(not(feature = "extended-tuple-impls"))]
resource_tuple!(A, B, C, D, E, F, G, H);
pub struct ResourceCell {
data: AtomicRefCell<Box<dyn Resource>>,
}
impl ResourceCell {
fn new(resource: Box<dyn Resource>) -> Self {
Self {
data: AtomicRefCell::new(resource),
}
}
fn into_inner(self) -> Box<dyn Resource> {
self.data.into_inner()
}
pub fn get<T: Resource>(&self) -> AtomicRef<T> {
let borrow = self.data.borrow();
AtomicRef::map(borrow, |inner| inner.downcast_ref::<T>().unwrap())
}
pub fn get_mut<T: Resource>(&self) -> AtomicRefMut<T> {
let borrow = self.data.borrow_mut();
AtomicRefMut::map(borrow, |inner| inner.downcast_mut::<T>().unwrap())
}
}
#[derive(Default)]
pub struct UnsafeResources {
map: HashMap<ResourceTypeId, ResourceCell, BuildHasherDefault<ComponentTypeIdHasher>>,
}
unsafe impl Send for UnsafeResources {}
unsafe impl Sync for UnsafeResources {}
impl UnsafeResources {
fn contains(&self, type_id: &ResourceTypeId) -> bool {
self.map.contains_key(type_id)
}
unsafe fn entry(&mut self, type_id: ResourceTypeId) -> Entry<ResourceTypeId, ResourceCell> {
self.map.entry(type_id)
}
unsafe fn insert<T: Resource>(&mut self, resource: T) {
self.map.insert(
ResourceTypeId::of::<T>(),
ResourceCell::new(Box::new(resource)),
);
}
unsafe fn remove(&mut self, type_id: &ResourceTypeId) -> Option<Box<dyn Resource>> {
self.map.remove(type_id).map(|cell| cell.into_inner())
}
fn get(&self, type_id: &ResourceTypeId) -> Option<&ResourceCell> {
self.map.get(type_id)
}
unsafe fn merge(&mut self, mut other: Self) {
for resource in other.map.drain() {
self.map.entry(resource.0).or_insert(resource.1);
}
}
}
#[derive(Default)]
pub struct Resources {
internal: UnsafeResources,
_not_send_sync: PhantomData<*const u8>,
}
impl Resources {
pub(crate) fn internal(&self) -> &UnsafeResources {
&self.internal
}
pub fn sync(&mut self) -> SyncResources {
SyncResources {
internal: &self.internal,
}
}
pub fn contains<T: Resource>(&self) -> bool {
self.internal.contains(&ResourceTypeId::of::<T>())
}
pub fn insert<T: Resource>(&mut self, value: T) {
unsafe {
self.internal.insert(value);
}
}
pub fn remove<T: Resource>(&mut self) -> Option<T> {
unsafe {
let resource = self
.internal
.remove(&ResourceTypeId::of::<T>())?
.downcast::<T>()
.ok()?;
Some(*resource)
}
}
pub fn get<T: Resource>(&self) -> Option<AtomicRef<T>> {
let type_id = &ResourceTypeId::of::<T>();
self.internal.get(&type_id).map(|x| x.get::<T>())
}
pub fn get_mut<T: Resource>(&self) -> Option<AtomicRefMut<T>> {
let type_id = &ResourceTypeId::of::<T>();
self.internal.get(&type_id).map(|x| x.get_mut::<T>())
}
pub fn get_or_insert_with<T: Resource, F: FnOnce() -> T>(&mut self, f: F) -> AtomicRef<T> {
let type_id = ResourceTypeId::of::<T>();
unsafe {
self.internal
.entry(type_id)
.or_insert_with(|| ResourceCell::new(Box::new((f)())))
.get()
}
}
pub fn get_mut_or_insert_with<T: Resource, F: FnOnce() -> T>(
&mut self,
f: F,
) -> AtomicRefMut<T> {
let type_id = ResourceTypeId::of::<T>();
unsafe {
self.internal
.entry(type_id)
.or_insert_with(|| ResourceCell::new(Box::new((f)())))
.get_mut()
}
}
pub fn get_or_insert<T: Resource>(&mut self, value: T) -> AtomicRef<T> {
self.get_or_insert_with(|| value)
}
pub fn get_mut_or_insert<T: Resource>(&mut self, value: T) -> AtomicRefMut<T> {
self.get_mut_or_insert_with(|| value)
}
pub fn get_or_default<T: Resource + Default>(&mut self) -> AtomicRef<T> {
self.get_or_insert_with(T::default)
}
pub fn get_mut_or_default<T: Resource + Default>(&mut self) -> AtomicRefMut<T> {
self.get_mut_or_insert_with(T::default)
}
pub fn merge(&mut self, other: Resources) {
unsafe {
self.internal.merge(other.internal);
}
}
}
pub struct SyncResources<'a> {
internal: &'a UnsafeResources,
}
impl<'a> SyncResources<'a> {
pub fn get<T: Resource + Sync>(&self) -> Option<AtomicRef<T>> {
let type_id = &ResourceTypeId::of::<T>();
self.internal.get(&type_id).map(|x| x.get::<T>())
}
pub fn get_mut<T: Resource + Send>(&self) -> Option<AtomicRefMut<T>> {
let type_id = &ResourceTypeId::of::<T>();
self.internal.get(&type_id).map(|x| x.get_mut::<T>())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_read_write_test() {
struct TestOne {
value: String,
}
struct TestTwo {
value: String,
}
struct NotSync {
ptr: *const u8,
}
let mut resources = Resources::default();
resources.insert(TestOne {
value: "one".to_string(),
});
resources.insert(TestTwo {
value: "two".to_string(),
});
resources.insert(NotSync {
ptr: std::ptr::null(),
});
assert_eq!(resources.get::<TestOne>().unwrap().value, "one");
assert_eq!(resources.get::<TestTwo>().unwrap().value, "two");
assert_eq!(resources.get::<NotSync>().unwrap().ptr, std::ptr::null());
let owned = resources.remove::<TestTwo>();
assert_eq!(owned.unwrap().value, "two");
}
}