From 887d8c99d7e4491fed095e7bff0624e9f30d24e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tudor=20Lechin=C8=9Ban?= Date: Wed, 3 Jan 2024 14:16:22 +0200 Subject: [PATCH] Add new resources and clean up component storages --- .rustfmt.toml | 1 - src/lib.rs | 1 + src/query/component_view.rs | 10 +- src/query/query_part.rs | 8 +- src/resource/borrow.rs | 50 +++++++++ src/resource/mod.rs | 79 +++++++++++++ src/resource/storage.rs | 105 ++++++++++++++++++ src/resources/resource.rs | 6 +- src/storage/component_storage.rs | 29 +++-- src/storage/entity.rs | 80 ++++--------- src/storage/entity_allocator.rs | 8 +- src/storage/entity_sparse_set.rs | 19 ++-- src/storage/mod.rs | 6 +- .../{sparse_array.rs => sparse_vec.rs} | 14 +-- src/world/comp.rs | 6 +- 15 files changed, 314 insertions(+), 108 deletions(-) create mode 100644 src/resource/borrow.rs create mode 100644 src/resource/mod.rs create mode 100644 src/resource/storage.rs rename src/storage/{sparse_array.rs => sparse_vec.rs} (90%) diff --git a/.rustfmt.toml b/.rustfmt.toml index e3ee032..197db9b 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,6 +1,5 @@ comment_width = 100 force_multiline_blocks = true -group_imports = "One" imports_granularity = "Module" max_width = 100 newline_style = "Unix" diff --git a/src/lib.rs b/src/lib.rs index 90eb991..386b7bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ pub mod components; pub mod layout; pub mod query; +pub mod resource; pub mod resources; pub mod storage; pub mod systems; diff --git a/src/query/component_view.rs b/src/query/component_view.rs index fa696fc..8759045 100644 --- a/src/query/component_view.rs +++ b/src/query/component_view.rs @@ -1,5 +1,5 @@ use crate::components::GroupInfo; -use crate::storage::{Component, Entity, SparseArray}; +use crate::storage::{Component, Entity, SparseVec}; use crate::world::{Comp, CompMut}; use std::ops::RangeBounds; @@ -36,7 +36,7 @@ pub trait ComponentView { Self: 'a; /// Splits the view for iteration. - fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseArray, Self::Ptr) + fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseVec, Self::Ptr) where Self: 'a; @@ -100,7 +100,7 @@ where Comp::group_info(self) } - fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseArray, Self::Ptr) + fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseVec, Self::Ptr) where Self: 'a, { @@ -184,7 +184,7 @@ where CompMut::group_info(self) } - fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseArray, Self::Ptr) + fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseVec, Self::Ptr) where Self: 'a, { @@ -268,7 +268,7 @@ where CompMut::group_info(self) } - fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseArray, Self::Ptr) + fn split_for_iteration<'a>(self) -> (&'a [Entity], &'a SparseVec, Self::Ptr) where Self: 'a, { diff --git a/src/query/query_part.rs b/src/query/query_part.rs index 369242b..106e72c 100644 --- a/src/query/query_part.rs +++ b/src/query/query_part.rs @@ -1,5 +1,5 @@ use crate::query::{ComponentView, QueryGroupInfo}; -use crate::storage::{Entity, SparseArray}; +use crate::storage::{Entity, SparseVec}; use std::ops::RangeBounds; /// Allows getting, including or excluding components in a query. @@ -249,7 +249,7 @@ where C: ComponentView, { type Refs<'a> = C::Ref<'a> where Self: 'a; - type Sparse<'a> = &'a SparseArray where Self: 'a; + type Sparse<'a> = &'a SparseVec where Self: 'a; type Ptrs = C::Ptr; type Slices<'a> = C::Slice<'a> where Self: 'a; @@ -380,7 +380,7 @@ where C: ComponentView, { type Refs<'a> = (C::Ref<'a>,) where Self: 'a; - type Sparse<'a> = (&'a SparseArray,) where Self: 'a; + type Sparse<'a> = (&'a SparseVec,) where Self: 'a; type Ptrs = (C::Ptr,); type Slices<'a> = (C::Slice<'a>,) where Self: 'a; @@ -511,7 +511,7 @@ where macro_rules! replace_with_sparse_array_ref { ($old:tt) => { - &'a SparseArray + &'a SparseVec }; } diff --git a/src/resource/borrow.rs b/src/resource/borrow.rs new file mode 100644 index 0000000..413ccc0 --- /dev/null +++ b/src/resource/borrow.rs @@ -0,0 +1,50 @@ +use atomic_refcell::{AtomicRef, AtomicRefMut}; +use std::fmt; +use std::ops::{Deref, DerefMut}; + +pub struct Res<'a, T>(pub(crate) AtomicRef<'a, T>); + +pub struct ResMut<'a, T>(pub(crate) AtomicRefMut<'a, T>); + +impl DerefMut for ResMut<'_, T> { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +macro_rules! impl_res_common { + ($Res:ident) => { + impl Deref for $Res<'_, T> { + type Target = T; + + #[must_use] + fn deref(&self) -> &T { + &self.0 + } + } + + impl fmt::Debug for $Res<'_, T> + where + T: fmt::Debug, + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } + } + + impl fmt::Display for $Res<'_, T> + where + T: fmt::Display, + { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } + } + }; +} + +impl_res_common!(Res); +impl_res_common!(ResMut); diff --git a/src/resource/mod.rs b/src/resource/mod.rs new file mode 100644 index 0000000..e81598c --- /dev/null +++ b/src/resource/mod.rs @@ -0,0 +1,79 @@ +mod borrow; +mod storage; + +pub use self::borrow::*; +pub use self::storage::*; + +use std::any::Any; + +pub trait Resource: Send + Sync + 'static { + /// Returns `self` as a type-erased box. + #[must_use] + fn into_any(self: Box) -> Box; + + /// Returns `self` as a type-erased reference. + #[must_use] + fn as_any(&self) -> &dyn Any; + + /// Returns `self` as a type-erased mutable reference. + #[must_use] + fn as_any_mut(&mut self) -> &mut dyn Any; +} + +impl Resource for T +where + T: Send + Sync + 'static, +{ + fn into_any(self: Box) -> Box { + self + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } +} + +impl dyn Resource { + /// Returns whether the resource is of type `T`. + #[must_use] + pub fn is(&self) -> bool + where + T: Resource, + { + self.as_any().is::() + } + + /// Tries to downcast `self` to a box of type `T`. If the conversion fails, the original box is + /// returned. + pub fn downcast(self: Box) -> Result, Box> + where + T: Resource, + { + if self.is::() { + Ok(self.into_any().downcast().unwrap()) + } else { + Err(self) + } + } + + /// Tries to downcast `self` to a reference of type `T`. + #[must_use] + pub fn downcast_ref(&self) -> Option<&T> + where + T: Resource, + { + self.as_any().downcast_ref() + } + + /// Tries to downcast `self` to a mutable reference of type `T`. + pub fn downcast_mut(&mut self) -> Option<&mut T> + where + T: Resource, + { + self.as_any_mut().downcast_mut() + } +} diff --git a/src/resource/storage.rs b/src/resource/storage.rs new file mode 100644 index 0000000..16efda7 --- /dev/null +++ b/src/resource/storage.rs @@ -0,0 +1,105 @@ +use crate::resource::{Res, ResMut, Resource}; +use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; +use rustc_hash::FxHashMap; +use std::any::TypeId; +use std::collections::hash_map::Entry; +use std::mem; + +#[derive(Default)] +pub struct ResourceStorage { + resources: FxHashMap>>, +} + +impl ResourceStorage { + pub fn insert(&mut self, resource: T) -> Option + where + T: Resource, + { + match self.resources.entry(TypeId::of::()) { + Entry::Vacant(entry) => { + entry.insert(AtomicRefCell::new(Box::new(resource))); + None + } + Entry::Occupied(mut entry) => { + let old_resource = unsafe { + entry + .get_mut() + .get_mut() + .as_mut() + .downcast_mut::() + .unwrap_unchecked() + }; + + Some(mem::replace(old_resource, resource)) + } + } + } + + pub fn remove(&mut self) -> Option + where + T: Resource, + { + self.resources + .remove(&TypeId::of::()) + .map(|cell| unsafe { *cell.into_inner().downcast().unwrap_unchecked() }) + } + + #[must_use] + pub fn contains(&self) -> bool + where + T: Resource, + { + self.resources.contains_key(&TypeId::of::()) + } + + #[must_use] + pub fn get_mut(&mut self) -> Option<&mut T> + where + T: Resource, + { + self.resources + .get_mut(&TypeId::of::()) + .map(|cell| unsafe { cell.get_mut().downcast_mut().unwrap_unchecked() }) + } + + #[inline] + pub fn borrow(&self) -> Option> + where + T: Resource, + { + self.resources.get(&TypeId::of::()).map(|cell| { + Res(AtomicRef::map(cell.borrow(), |cell| unsafe { + cell.downcast_ref().unwrap_unchecked() + })) + }) + } + + #[inline] + pub fn borrow_mut(&self) -> Option> + where + T: Resource, + { + self.resources.get(&TypeId::of::()).map(|cell| { + ResMut(AtomicRefMut::map(cell.borrow_mut(), |cell| unsafe { + cell.downcast_mut().unwrap_unchecked() + })) + }) + } + + #[inline] + #[must_use] + pub fn is_empty(&self) -> bool { + self.resources.is_empty() + } + + #[inline] + #[must_use] + pub fn len(&self) -> usize { + self.resources.len() + } + + #[inline] + pub fn clear(&mut self) { + self.resources.clear(); + } +} diff --git a/src/resources/resource.rs b/src/resources/resource.rs index cb01c0d..26c1da6 100644 --- a/src/resources/resource.rs +++ b/src/resources/resource.rs @@ -37,6 +37,7 @@ impl dyn Resource { } /// Tries to downcast `self` to a reference of type `T`. + #[must_use] pub fn downcast_ref(&self) -> Option<&T> where T: Resource, @@ -55,13 +56,16 @@ impl dyn Resource { /// Helper trait for implementing downcasting operations. pub trait Downcast: 'static { - /// Returns `self` as a type-erases box. + /// Returns `self` as a type-erased box. + #[must_use] fn into_any(self: Box) -> Box; /// Returns `self` as a type-erased reference. + #[must_use] fn as_any(&self) -> &dyn Any; /// Returns `self` as a type-erased mutable reference. + #[must_use] fn as_any_mut(&mut self) -> &mut dyn Any; } diff --git a/src/storage/component_storage.rs b/src/storage/component_storage.rs index 7481a68..89f4382 100644 --- a/src/storage/component_storage.rs +++ b/src/storage/component_storage.rs @@ -1,10 +1,10 @@ -use crate::storage::{Component, DenseEntity, Entity, SparseArray}; +use crate::storage::{Component, DenseEntity, Entity, SparseVec}; use std::alloc::{alloc, dealloc, handle_alloc_error, Layout, LayoutError}; use std::ptr::NonNull; use std::{mem, ptr, slice}; pub struct ComponentStorage { - sparse: SparseArray, + sparse: SparseVec, entities: NonNull, components: NonNull, len: usize, @@ -18,7 +18,7 @@ impl ComponentStorage { T: Component, { Self { - sparse: SparseArray::default(), + sparse: SparseVec::default(), entities: NonNull::dangling(), components: NonNull::::dangling().cast(), len: 0, @@ -48,7 +48,10 @@ impl ComponentStorage { ) } None => { - *dense_entity = Some(DenseEntity::new(self.len as u32, entity.version())); + *dense_entity = Some(DenseEntity { + index: self.len as u32, + version: entity.version, + }); if self.len == self.cap { self.grow(); @@ -79,8 +82,10 @@ impl ComponentStorage { *self.entities.as_ptr().add(index) = last_entity; if index < self.len { - let dense_entity = DenseEntity::new(index as u32, last_entity.version()); - *self.sparse.get_unchecked_mut(last_entity.sparse()) = Some(dense_entity); + *self.sparse.get_unchecked_mut(last_entity.sparse()) = Some(DenseEntity { + index: index as u32, + version: last_entity.version, + }); } let removed_ptr = self.components.cast::().as_ptr().add(index); @@ -106,8 +111,10 @@ impl ComponentStorage { *self.entities.as_ptr().add(index) = last_entity; if index < self.len { - let dense_entity = DenseEntity::new(index as u32, last_entity.version()); - *self.sparse.get_unchecked_mut(last_entity.sparse()) = Some(dense_entity); + *self.sparse.get_unchecked_mut(last_entity.sparse()) = Some(DenseEntity { + index: index as u32, + version: last_entity.version, + }); } let dropped_ptr = self.components.cast::().as_ptr().add(index); @@ -152,7 +159,7 @@ impl ComponentStorage { #[inline] #[must_use] - pub fn sparse(&self) -> &SparseArray { + pub fn sparse(&self) -> &SparseVec { &self.sparse } @@ -182,7 +189,7 @@ impl ComponentStorage { #[inline] #[must_use] - pub unsafe fn split(&self) -> (&[Entity], &SparseArray, &[T]) + pub unsafe fn split(&self) -> (&[Entity], &SparseVec, &[T]) where T: Component, { @@ -195,7 +202,7 @@ impl ComponentStorage { #[inline] #[must_use] - pub unsafe fn split_mut(&mut self) -> (&[Entity], &SparseArray, &mut [T]) + pub unsafe fn split_mut(&mut self) -> (&[Entity], &SparseVec, &mut [T]) where T: Component, { diff --git a/src/storage/entity.rs b/src/storage/entity.rs index bce685f..8ccd241 100644 --- a/src/storage/entity.rs +++ b/src/storage/entity.rs @@ -1,49 +1,36 @@ use std::cmp::{Ord, Ordering, PartialOrd}; -use std::fmt; use std::num::NonZeroU32; /// Type used to tell apart entities with the same index. Entities with the same index and /// different versions are considered different. #[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)] -pub struct Version(NonZeroU32); +pub struct Version(pub NonZeroU32); impl Version { - /// Default version of an [`Entity`]. - pub const DEFAULT: Version = unsafe { Self(NonZeroU32::new_unchecked(1)) }; + pub const FIRST: Self = unsafe { Self(NonZeroU32::new_unchecked(1)) }; - /// Creates a new version with the given `indexd`. - #[inline] - pub const fn new(index: NonZeroU32) -> Self { - Self(index) - } - - /// Returns the index of the version. - #[inline] - #[must_use] - pub const fn index(&self) -> u32 { - self.0.get() - } - - /// Returns the version after the current one, if any. #[inline] #[must_use] - pub fn next(&self) -> Option { - self.0.checked_add(1).map(Self) + pub const fn next(&self) -> Option { + match self.0.checked_add(1) { + Some(version) => Some(Self(version)), + None => None, + } } } impl Default for Version { #[inline] fn default() -> Self { - Self::DEFAULT + Self::FIRST } } /// Uniquely identifies a set of components in a [`World`](crate::world::World). -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct Entity { - index: u32, - version: Version, + pub index: u32, + pub version: Version, } impl Entity { @@ -51,15 +38,15 @@ impl Entity { #[inline] #[must_use] pub const fn sparse(&self) -> usize { - self.index as _ + self.index as usize } } /// Used internally by `SparseArray` to store versioned dense indexes. -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct DenseEntity { - index: u32, - version: Version, + pub index: u32, + pub version: Version, } impl DenseEntity { @@ -67,41 +54,21 @@ impl DenseEntity { #[inline] #[must_use] pub const fn dense(&self) -> usize { - self.index as _ + self.index as usize } } macro_rules! impl_entity_common { ($Entity:ident) => { impl $Entity { - /// Creates a new entity with the given `index` and `version`. - #[inline] - pub const fn new(index: u32, version: Version) -> Self { - Self { index, version } - } - - /// Creates a new entity with the given `index` and default `version`. #[inline] - pub const fn with_index(index: u32) -> Self { + #[must_use] + pub fn with_index(index: u32) -> Self { Self { index, - version: Version::DEFAULT, + version: Version::FIRST, } } - - /// Returns the index of the entity. - #[inline] - #[must_use] - pub const fn index(&self) -> u32 { - self.index - } - - /// Returns the version of the entity. - #[inline] - #[must_use] - pub const fn version(&self) -> Version { - self.version - } } impl PartialOrd for $Entity { @@ -119,15 +86,6 @@ macro_rules! impl_entity_common { .then(self.index.cmp(&other.index)) } } - - impl fmt::Debug for $Entity { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct(stringify!($Entity)) - .field("index", &self.index) - .field("version", &self.version.0) - .finish() - } - } }; } diff --git a/src/storage/entity_allocator.rs b/src/storage/entity_allocator.rs index c8066f1..ce5a030 100644 --- a/src/storage/entity_allocator.rs +++ b/src/storage/entity_allocator.rs @@ -49,9 +49,11 @@ impl EntityAllocator { /// Deallocates the entity and attempts to recycle its index. pub fn recycle(&mut self, entity: Entity) { - if let Some(next_version) = entity.version().next() { - self.recycled - .push_front(Entity::new(entity.index(), next_version)); + if let Some(next_version) = entity.version.next() { + self.recycled.push_front(Entity { + index: entity.index, + version: next_version, + }); } } diff --git a/src/storage/entity_sparse_set.rs b/src/storage/entity_sparse_set.rs index 1a42a30..f8f8bf3 100644 --- a/src/storage/entity_sparse_set.rs +++ b/src/storage/entity_sparse_set.rs @@ -1,10 +1,10 @@ -use crate::storage::{DenseEntity, Entity, SparseArray}; +use crate::storage::{DenseEntity, Entity, SparseVec}; use std::convert::AsRef; /// Sparse set-based storage for entities. #[derive(Clone, Default, Debug)] pub struct EntitySparseSet { - sparse: SparseArray, + sparse: SparseVec, entities: Vec, } @@ -21,10 +21,10 @@ impl EntitySparseSet { )) }, None => { - *dense_entity = Some(DenseEntity::new( - self.entities.len() as u32, - entity.version(), - )); + *dense_entity = Some(DenseEntity { + index: self.entities.len() as u32, + version: entity.version, + }); self.entities.push(entity); None @@ -42,10 +42,11 @@ impl EntitySparseSet { self.entities.swap_remove(dense_index); if let Some(entity) = self.entities.get(dense_index) { - let new_dense_entity = DenseEntity::new(dense_index as u32, entity.version()); - unsafe { - *self.sparse.get_unchecked_mut(entity.sparse()) = Some(new_dense_entity); + *self.sparse.get_unchecked_mut(entity.sparse()) = Some(DenseEntity { + index: dense_index as u32, + version: entity.version, + }); } } diff --git a/src/storage/mod.rs b/src/storage/mod.rs index 26b7cfd..a9d454e 100644 --- a/src/storage/mod.rs +++ b/src/storage/mod.rs @@ -6,12 +6,12 @@ mod entity; mod entity_allocator; mod entity_sparse_set; mod entity_storage; -mod sparse_array; +mod sparse_vec; pub use self::component::*; -pub(crate) use self::component_storage::*; +pub use self::component_storage::*; pub use self::entity::*; pub use self::entity_allocator::*; pub use self::entity_sparse_set::*; pub use self::entity_storage::*; -pub use self::sparse_array::*; +pub use self::sparse_vec::*; diff --git a/src/storage/sparse_array.rs b/src/storage/sparse_vec.rs similarity index 90% rename from src/storage/sparse_array.rs rename to src/storage/sparse_vec.rs index 954f744..dbd7c92 100644 --- a/src/storage/sparse_array.rs +++ b/src/storage/sparse_vec.rs @@ -1,20 +1,21 @@ use crate::storage::{DenseEntity, Entity}; +use std::iter; /// Maps versioned sparse indexes (entities) to dense indexes. /// Used internally by `ComponentStorage`. #[derive(Clone, Debug, Default)] -pub struct SparseArray { +pub struct SparseVec { entities: Vec>, } -impl SparseArray { +impl SparseVec { /// Returns the index entity mapped to `entity`, if any. #[inline] #[must_use] pub fn get(&self, entity: Entity) -> Option { self.entities .get(entity.sparse())? - .filter(|dense_entity| dense_entity.version() == entity.version()) + .filter(|dense_entity| dense_entity.version == entity.version) } /// Returns `true` if the array contains `entity`. @@ -24,7 +25,7 @@ impl SparseArray { self.entities .get(entity.sparse()) .and_then(Option::as_ref) - .is_some_and(|dense_entity| dense_entity.version() == entity.version()) + .is_some_and(|dense_entity| dense_entity.version == entity.version) } /// Removes the entity from the array. @@ -32,7 +33,7 @@ impl SparseArray { pub fn remove(&mut self, entity: Entity) -> Option { let entity_slot = self.entities.get_mut(entity.sparse())?; - if entity_slot.is_some_and(|dense_entity| dense_entity.version() == entity.version()) { + if entity_slot.is_some_and(|dense_entity| dense_entity.version == entity.version) { entity_slot.take() } else { None @@ -82,8 +83,7 @@ impl SparseArray { let extra_len = index.checked_next_power_of_two().unwrap_or(index) - self.entities.len() + 1; - self.entities - .extend(std::iter::repeat(None).take(extra_len)); + self.entities.extend(iter::repeat(None).take(extra_len)); } &mut self.entities[index] diff --git a/src/world/comp.rs b/src/world/comp.rs index 04783ba..13e30c9 100644 --- a/src/world/comp.rs +++ b/src/world/comp.rs @@ -1,5 +1,5 @@ use crate::components::GroupInfo; -use crate::storage::{Component, ComponentStorage, Entity, SparseArray}; +use crate::storage::{Component, ComponentStorage, Entity, SparseVec}; use atomic_refcell::{AtomicRef, AtomicRefMut}; use std::fmt; use std::marker::PhantomData; @@ -62,7 +62,7 @@ where } #[must_use] - pub(crate) fn split_mut(&mut self) -> (&[Entity], &SparseArray, &mut [T]) { + pub(crate) fn split_mut(&mut self) -> (&[Entity], &SparseVec, &mut [T]) { unsafe { self.storage.split_mut::() } } } @@ -115,7 +115,7 @@ macro_rules! impl_comp_common { } #[must_use] - pub(crate) fn split(&self) -> (&[Entity], &SparseArray, &[T]) { + pub(crate) fn split(&self) -> (&[Entity], &SparseVec, &[T]) { unsafe { self.storage.split::() } } }