use crate::internals::{
hash::ComponentTypeIdHasher,
query::view::{read::Read, write::Write, ReadOnly},
};
use downcast_rs::{impl_downcast, Downcast};
use std::{
any::TypeId,
cell::UnsafeCell,
collections::{hash_map::Entry, HashMap},
fmt::{Display, Formatter},
hash::{BuildHasherDefault, Hasher},
marker::PhantomData,
ops::{Deref, DerefMut},
sync::atomic::AtomicIsize,
};
#[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 = Fetch<'a, T>;
unsafe fn fetch_unchecked(resources: &'a UnsafeResources) -> Self::Result {
let type_id = &ResourceTypeId::of::<T>();
resources.get(&type_id).unwrap().get::<T>().unwrap()
}
}
impl<'a, T: Resource> ResourceSet<'a> for Write<T> {
type Result = FetchMut<'a, T>;
unsafe fn fetch_unchecked(resources: &'a UnsafeResources) -> Self::Result {
let type_id = &ResourceTypeId::of::<T>();
resources.get(&type_id).unwrap().get_mut::<T>().unwrap()
}
}
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 Fetch<'a, T: Resource> {
state: &'a AtomicIsize,
inner: &'a T,
}
impl<'a, T: Resource> Deref for Fetch<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
self.inner
}
}
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())
}
}
impl<'a, T: Resource> Drop for Fetch<'a, T> {
fn drop(&mut self) {
self.state
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
}
}
pub struct FetchMut<'a, T: Resource> {
state: &'a AtomicIsize,
inner: &'a mut T,
}
impl<'a, T: 'a + Resource> Deref for FetchMut<'a, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&*self.inner
}
}
impl<'a, T: 'a + Resource> DerefMut for FetchMut<'a, T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
&mut *self.inner
}
}
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())
}
}
impl<'a, T: Resource> Drop for FetchMut<'a, T> {
fn drop(&mut self) {
self.state
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
}
}
pub struct ResourceCell {
data: UnsafeCell<Box<dyn Resource>>,
borrow_state: AtomicIsize,
}
impl ResourceCell {
fn new(resource: Box<dyn Resource>) -> Self {
Self {
data: UnsafeCell::new(resource),
borrow_state: AtomicIsize::new(0),
}
}
fn into_inner(self) -> Box<dyn Resource> {
self.data.into_inner()
}
pub unsafe fn get<T: Resource>(&self) -> Option<Fetch<'_, T>> {
loop {
let read = self.borrow_state.load(std::sync::atomic::Ordering::SeqCst);
if read < 0 {
panic!(
"resource already borrowed as mutable: {}",
std::any::type_name::<T>()
);
}
if self.borrow_state.compare_and_swap(
read,
read + 1,
std::sync::atomic::Ordering::SeqCst,
) == read
{
break;
}
}
let resource = self.data.get().as_ref().and_then(|r| r.downcast_ref::<T>());
if let Some(resource) = resource {
Some(Fetch {
state: &self.borrow_state,
inner: resource,
})
} else {
self.borrow_state
.fetch_sub(1, std::sync::atomic::Ordering::Relaxed);
None
}
}
pub unsafe fn get_mut<T: Resource>(&self) -> Option<FetchMut<'_, T>> {
let borrowed =
self.borrow_state
.compare_and_swap(0, -1, std::sync::atomic::Ordering::SeqCst);
match borrowed {
0 => {
let resource = self.data.get().as_mut().and_then(|r| r.downcast_mut::<T>());
if let Some(resource) = resource {
Some(FetchMut {
state: &self.borrow_state,
inner: resource,
})
} else {
self.borrow_state
.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
None
}
}
x if x < 0 => panic!(
"resource already borrowed as mutable: {}",
std::any::type_name::<T>()
),
_ => panic!(
"resource already borrowed as immutable: {}",
std::any::type_name::<T>()
),
}
}
}
#[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<Fetch<'_, T>> {
let type_id = &ResourceTypeId::of::<T>();
unsafe { self.internal.get(&type_id)?.get::<T>() }
}
pub fn get_mut<T: Resource>(&self) -> Option<FetchMut<'_, T>> {
let type_id = &ResourceTypeId::of::<T>();
unsafe { self.internal.get(&type_id)?.get_mut::<T>() }
}
pub fn get_or_insert_with<T: Resource, F: FnOnce() -> T>(&mut self, f: F) -> Fetch<'_, T> {
let type_id = ResourceTypeId::of::<T>();
unsafe {
self.internal
.entry(type_id)
.or_insert_with(|| ResourceCell::new(Box::new((f)())))
.get()
.unwrap()
}
}
pub fn get_mut_or_insert_with<T: Resource, F: FnOnce() -> T>(
&mut self,
f: F,
) -> FetchMut<'_, T> {
let type_id = ResourceTypeId::of::<T>();
unsafe {
self.internal
.entry(type_id)
.or_insert_with(|| ResourceCell::new(Box::new((f)())))
.get_mut()
.unwrap()
}
}
pub fn get_or_insert<T: Resource>(&mut self, value: T) -> Fetch<'_, T> {
self.get_or_insert_with(|| value)
}
pub fn get_mut_or_insert<T: Resource>(&mut self, value: T) -> FetchMut<'_, T> {
self.get_mut_or_insert_with(|| value)
}
pub fn get_or_default<T: Resource + Default>(&mut self) -> Fetch<'_, T> {
self.get_or_insert_with(T::default)
}
pub fn get_mut_or_default<T: Resource + Default>(&mut self) -> FetchMut<'_, 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<Fetch<'_, T>> {
let type_id = &ResourceTypeId::of::<T>();
unsafe { self.internal.get(&type_id)?.get::<T>() }
}
pub fn get_mut<T: Resource + Send>(&self) -> Option<FetchMut<'_, T>> {
let type_id = &ResourceTypeId::of::<T>();
unsafe { self.internal.get(&type_id)?.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");
}
}