use std::{fmt::Debug, marker::PhantomData, sync::Arc};
use once_map::OnceMap;
use crate::prelude::*;
pub type AtomicUntypedResource = Arc<UntypedResource>;
pub struct UntypedResource {
cell: AtomicCell<Option<SchemaBox>>,
schema: &'static Schema,
}
impl std::fmt::Debug for UntypedResource {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UntypedResource").finish_non_exhaustive()
}
}
impl UntypedResource {
pub fn empty(schema: &'static Schema) -> Self {
Self {
cell: AtomicCell::new(None),
schema,
}
}
pub fn new(resource: SchemaBox) -> Self {
Self {
schema: resource.schema(),
cell: AtomicCell::new(Some(resource)),
}
}
pub fn from_default(schema: &'static Schema) -> Self {
Self {
cell: AtomicCell::new(Some(SchemaBox::default(schema))),
schema,
}
}
pub fn clone_data(&self) -> Option<SchemaBox> {
(*self.cell.borrow()).clone()
}
pub fn insert(&self, data: SchemaBox) -> Result<Option<SchemaBox>, SchemaMismatchError> {
self.schema.ensure_match(data.schema())?;
let mut data = Some(data);
std::mem::swap(&mut data, &mut *self.cell.borrow_mut());
Ok(data)
}
pub fn remove(&self) -> Option<SchemaBox> {
let mut data = None;
std::mem::swap(&mut data, &mut *self.cell.borrow_mut());
data
}
#[track_caller]
pub fn borrow(&self) -> Ref<Option<SchemaBox>> {
self.cell.borrow()
}
#[track_caller]
pub fn borrow_mut(&self) -> RefMut<Option<SchemaBox>> {
self.cell.borrow_mut()
}
pub fn schema(&self) -> &'static Schema {
self.schema
}
}
#[derive(Default)]
pub struct UntypedResources {
resources: OnceMap<SchemaId, AtomicUntypedResource>,
shared_resources: OnceMap<SchemaId, Box<()>>,
}
impl Clone for UntypedResources {
fn clone(&self) -> Self {
let binding = self.resources.read_only_view();
let resources = binding.iter().map(|(_, v)| (v.schema, v));
let new_resources = OnceMap::default();
let new_shared_resources = OnceMap::default();
for (schema, resource_cell) in resources {
let is_shared = self.shared_resources.contains_key(&schema.id());
if !is_shared {
let resource = resource_cell.clone_data();
new_resources.map_insert(
schema.id(),
|_| Arc::new(UntypedResource::empty(schema)),
|_, cell| {
if let Some(resource) = resource {
cell.insert(resource).unwrap();
}
},
);
} else {
new_shared_resources.insert(schema.id(), |_| Box::new(()));
new_resources.insert(schema.id(), |_| resource_cell.clone());
}
}
Self {
resources: new_resources,
shared_resources: new_shared_resources,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct CellAlreadyPresentError;
impl std::fmt::Display for CellAlreadyPresentError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Resource cell already present")
}
}
impl std::error::Error for CellAlreadyPresentError {}
impl UntypedResources {
pub fn new() -> Self {
Self::default()
}
pub fn contains_cell(&self, id: SchemaId) -> bool {
self.resources.contains_key(&id)
}
pub fn contains(&self, id: SchemaId) -> bool {
self.resources
.map_get(&id, |_, cell| cell.borrow().is_some())
.unwrap_or_default()
}
pub fn insert_cell(&self, cell: AtomicUntypedResource) -> Result<(), CellAlreadyPresentError> {
let schema = cell.schema;
if self.resources.contains_key(&schema.id()) {
Err(CellAlreadyPresentError)
} else {
self.resources.insert(schema.id(), |_| cell);
self.shared_resources.insert(schema.id(), |_| Box::new(()));
Ok(())
}
}
pub fn get(&self, schema: &'static Schema) -> &UntypedResource {
self.resources
.insert(schema.id(), |_| Arc::new(UntypedResource::empty(schema)))
}
pub fn get_cell(&self, schema: &'static Schema) -> AtomicUntypedResource {
self.resources.map_insert(
schema.id(),
|_| Arc::new(UntypedResource::empty(schema)),
|_, cell| cell.clone(),
)
}
}
#[derive(Clone, Default)]
pub struct Resources {
untyped: UntypedResources,
}
impl Resources {
pub fn new() -> Self {
Self::default()
}
pub fn insert<T: HasSchema>(&self, resource: T) -> Option<T> {
self.untyped
.get(T::schema())
.insert(SchemaBox::new(resource))
.unwrap()
.map(|x| x.cast_into())
}
pub fn contains<T: HasSchema>(&self) -> bool {
self.untyped.resources.contains_key(&T::schema().id())
}
pub fn remove<T: HasSchema>(&self) -> Option<T> {
self.untyped
.get(T::schema())
.remove()
.map(|x| unsafe { x.cast_into_unchecked() })
}
#[track_caller]
pub fn get<T: HasSchema>(&self) -> Option<Ref<T>> {
let b = self.untyped.get(T::schema()).borrow();
if b.is_some() {
Some(Ref::map(b, |b| unsafe {
b.as_ref().unwrap().as_ref().cast_into_unchecked()
}))
} else {
None
}
}
#[track_caller]
pub fn get_mut<T: HasSchema>(&self) -> Option<RefMut<T>> {
let b = self.untyped.get(T::schema()).borrow_mut();
if b.is_some() {
Some(RefMut::map(b, |b| unsafe {
b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
}))
} else {
None
}
}
pub fn get_cell<T: HasSchema>(&self) -> AtomicResource<T> {
let untyped = self.untyped.get_cell(T::schema()).clone();
AtomicResource {
untyped,
_phantom: PhantomData,
}
}
pub fn untyped(&self) -> &UntypedResources {
&self.untyped
}
pub fn into_untyped(self) -> UntypedResources {
self.untyped
}
}
#[derive(Clone)]
pub struct AtomicResource<T: HasSchema> {
untyped: AtomicUntypedResource,
_phantom: PhantomData<T>,
}
impl<T: HasSchema + Debug> Debug for AtomicResource<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("AtomicResource(")?;
self.untyped
.cell
.borrow()
.as_ref()
.map(|x| x.cast_ref::<T>())
.fmt(f)?;
f.write_str(")")?;
Ok(())
}
}
impl<T: HasSchema + Default> Default for AtomicResource<T> {
fn default() -> Self {
Self {
untyped: Arc::new(UntypedResource::new(SchemaBox::new(T::default()))),
_phantom: Default::default(),
}
}
}
impl<T: HasSchema> AtomicResource<T> {
pub fn empty() -> Self {
Self {
untyped: Arc::new(UntypedResource::empty(T::schema())),
_phantom: PhantomData,
}
}
pub fn new(data: T) -> Self {
AtomicResource {
untyped: Arc::new(UntypedResource::new(SchemaBox::new(data))),
_phantom: PhantomData,
}
}
pub fn from_untyped(untyped: AtomicUntypedResource) -> Result<Self, SchemaMismatchError> {
T::schema().ensure_match(untyped.schema)?;
Ok(AtomicResource {
untyped,
_phantom: PhantomData,
})
}
pub fn remove(&self) -> Option<T> {
self.untyped
.remove()
.map(|x| unsafe { x.cast_into_unchecked() })
}
pub fn borrow(&self) -> Option<Ref<T>> {
let borrow = self.untyped.borrow();
if borrow.is_some() {
Some(Ref::map(borrow, |r| unsafe {
r.as_ref().unwrap().as_ref().cast_into_unchecked()
}))
} else {
None
}
}
pub fn borrow_mut(&self) -> Option<RefMut<T>> {
let borrow = self.untyped.borrow_mut();
if borrow.is_some() {
Some(RefMut::map(borrow, |r| unsafe {
r.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
}))
} else {
None
}
}
pub fn into_untyped(self) -> AtomicUntypedResource {
self.untyped
}
}
impl<T: HasSchema + FromWorld> AtomicResource<T> {
pub fn init(&self, world: &World) {
let mut borrow = self.untyped.borrow_mut();
if unlikely(borrow.is_none()) {
*borrow = Some(SchemaBox::new(T::from_world(world)))
}
}
#[track_caller]
pub fn init_borrow(&self, world: &World) -> Ref<T> {
let map_borrow = |borrow| {
Ref::map(borrow, |b: &Option<SchemaBox>| unsafe {
b.as_ref().unwrap().as_ref().cast_into_unchecked()
})
};
let borrow = self.untyped.borrow();
if unlikely(borrow.is_none()) {
drop(borrow);
{
let mut borrow_mut = self.untyped.borrow_mut();
*borrow_mut = Some(SchemaBox::new(T::from_world(world)));
}
map_borrow(self.untyped.borrow())
} else {
map_borrow(borrow)
}
}
#[track_caller]
pub fn init_borrow_mut(&self, world: &World) -> RefMut<T> {
let mut borrow = self.untyped.borrow_mut();
if unlikely(borrow.is_none()) {
*borrow = Some(SchemaBox::new(T::from_world(world)));
}
RefMut::map(borrow, |b| unsafe {
b.as_mut().unwrap().as_mut().cast_into_mut_unchecked()
})
}
}
#[cfg(test)]
mod test {
use crate::prelude::*;
#[test]
fn sanity_check() {
#[derive(HasSchema, Clone, Debug, Default)]
#[repr(C)]
struct A(String);
#[derive(HasSchema, Clone, Debug, Default)]
#[repr(C)]
struct B(u32);
let r1 = Resources::new();
r1.insert(A(String::from("hi")));
let r1a = r1.get_cell::<A>();
assert_eq!(r1a.borrow().unwrap().0, "hi");
let r2 = r1.clone();
r1.insert(A(String::from("bye")));
r1.insert(A(String::from("world")));
assert_eq!(r1a.borrow().unwrap().0, "world");
let r2a = r2.get_cell::<A>();
assert_eq!(r2a.borrow().unwrap().0, "hi");
r1.insert(B(1));
let r1b = r1.get_cell::<B>();
assert_eq!(r1b.borrow().unwrap().0, 1);
r1.insert(B(2));
assert_eq!(r1b.borrow().unwrap().0, 2);
assert_eq!(r1a.borrow().unwrap().0, "world");
}
}