diff --git a/src/collections/mod.rs b/src/collections/mod.rs index 0802f59..fa842ed 100644 --- a/src/collections/mod.rs +++ b/src/collections/mod.rs @@ -1,6 +1,3 @@ -/// Collections types for Zenyx -/// -/// - [`SparseSet`] mod sparse_set; pub use sparse_set::SparseSet; diff --git a/src/collections/sparse_set.rs b/src/collections/sparse_set.rs index a23117c..75b04ea 100644 --- a/src/collections/sparse_set.rs +++ b/src/collections/sparse_set.rs @@ -10,51 +10,16 @@ use bytemuck::Contiguous; const SPARSE_PAGESIZE: usize = (1 << 10) * 4; type SparsePage<A> = Option<(Box<[Option<NonZeroUsize>; SPARSE_PAGESIZE], A>, usize)>; -/// A sparse set for fast lookup of large indices. -/// -/// The sparse allocator is mainly used for bulk allocations in the system's page size -/// for the lookup array. It will also be used for the array of pointers into those -/// bulk allocations. Additionally it will be used for the reverse map that generates keys -/// from the value in the internal packed array. -/// -/// The packed allocator will exclusively be used to store the values of type `T`. -/// -/// All operations on this datastructure, meaning insertion, lookup, and deletion, are `O(1)`. -/// -/// This data structure does not in any way guarantee ordering of the values on -/// its own. -#[derive(Hash)] pub struct SparseSet<T, PackedAlloc = Global, SparseAlloc = Global> where PackedAlloc: Allocator, SparseAlloc: Allocator, { - /// The paginated array of keys. The value at the key is an index into the dense array minus - /// one where the value corresponding to that key is stored. sparse: Vec<SparsePage<SparseAlloc>, SparseAlloc>, - /// The dense array where the values corresponding to the keys are stored. dense: Vec<T, PackedAlloc>, - /// The reverse map to get the index in the sparse array from the index in the dense array. dense_to_id: Vec<usize, SparseAlloc>, } -impl<T> SparseSet<T> { - /// Creates a new [`SparseSet`] with the global allocator. - pub const fn new() -> Self { - Self { - sparse: Vec::new(), - dense: Vec::new(), - dense_to_id: Vec::new(), - } - } -} - -impl<T> Default for SparseSet<T> { - fn default() -> Self { - Self::new() - } -} - impl<T, PackedAlloc, SparseAlloc> core::fmt::Debug for SparseSet<T, PackedAlloc, SparseAlloc> where T: core::fmt::Debug, @@ -68,26 +33,21 @@ where } } +impl<T> SparseSet<T> { + pub const fn new() -> Self { + Self { + sparse: Vec::new(), + dense: Vec::new(), + dense_to_id: Vec::new(), + } + } +} + impl<T, PackedAlloc, SparseAlloc> SparseSet<T, PackedAlloc, SparseAlloc> where PackedAlloc: Allocator, SparseAlloc: Allocator + Clone, { - /// Inserts an element into the sparse set with the key `id`. This will - /// return the previous value if it already exists. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// - /// sparse_set.insert(10, 123); - /// assert_eq!(sparse_set.get(10), Some(&123)); - /// - /// let prev = sparse_set.insert(10, 9); - /// assert_eq!(prev, Some(123)); - /// assert_eq!(sparse_set.get(10), Some(&9)); - /// ``` pub fn insert(&mut self, id: usize, value: T) -> Option<T> { match self.get_dense_idx(id) { Some(idx) => { @@ -105,39 +65,15 @@ where } } - /// Gets the value with the key `id`. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// - /// sparse_set.insert(10, 123); - /// assert_eq!(sparse_set.get(10), Some(&123)); - /// ``` pub fn get(&self, id: usize) -> Option<&T> { self.dense.get(self.get_dense_idx(id)?) } - /// Gets the value with the key `id` mutably. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// - /// sparse_set.insert(10, 123); - /// let value = sparse_set.get_mut(10).unwrap(); - /// *value = 0; - /// assert_eq!(sparse_set.get(10), Some(&0)); - /// ``` pub fn get_mut(&mut self, id: usize) -> Option<&mut T> { let idx = self.get_dense_idx(id)?; self.dense.get_mut(idx) } - /// Sets the dense index of an `key` to `idx`. This will remove said index - /// if it is [`None`]. fn set_dense_idx(&mut self, id: usize, idx: Option<usize>) { let page = id / SPARSE_PAGESIZE; let sparse_index = id % SPARSE_PAGESIZE; @@ -161,15 +97,6 @@ where } } - /// Gets the index in the dense array for a key `id`. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// sparse_set.insert(10, 123); - /// assert_eq!(sparse_set.values()[sparse_set.get_dense_idx(10).unwrap()], 123); - /// ``` pub fn get_dense_idx(&self, id: usize) -> Option<usize> { let page = id / SPARSE_PAGESIZE; let sparse_index = id % SPARSE_PAGESIZE; @@ -177,8 +104,6 @@ where page.0[sparse_index].map(|idx| idx.into_integer() - 1) } - /// This reduces the usage count for a page in the sparse array, deallocating - /// it if it is not used anymore. fn reduce_page_usage_count(&mut self, id: usize) { let page = id / SPARSE_PAGESIZE; let Some(usage) = &mut self.sparse[page] else { @@ -191,7 +116,6 @@ where } } - /// Increase the page usage count for a page in the sparse array. fn increase_page_usage_count(&mut self, id: usize) { let page = id / SPARSE_PAGESIZE; if page >= self.sparse.len() { @@ -203,18 +127,6 @@ where usage.1 += 1; } - /// Removes the value with the key `id` from the sparse set, returning the - /// value if it existed. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// - /// sparse_set.insert(10, 123); - /// assert_eq!(sparse_set.remove(10), Some(123)); - /// assert_eq!(sparse_set.remove(10), None); - /// ``` pub fn remove(&mut self, id: usize) -> Option<T> { let index = self.get_dense_idx(id)?; if self.dense.is_empty() { @@ -230,126 +142,26 @@ where Some(previous) } - /// Returns if there are values in this sparse set. - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// assert!(sparse_set.is_empty()); - /// sparse_set.insert(10, 123); - /// assert!(!sparse_set.is_empty()); - /// ``` pub fn is_empty(&self) -> bool { self.len() == 0 } - /// Returns the number of values in this sparse set. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// sparse_set.insert(10, 123); - /// sparse_set.insert(10, 9); - /// sparse_set.insert(11, 10); - /// assert_eq!(sparse_set.len(), 2); - /// ``` pub fn len(&self) -> usize { self.dense.len() } - /// Checks if the sparse set contains a value with key `id`. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// assert!(!sparse_set.contains(10)); - /// sparse_set.insert(10, 123); - /// assert!(sparse_set.contains(10)); - /// ``` pub fn contains(&self, id: usize) -> bool { self.get_dense_idx(id).is_some() } - /// Gets the keys of all values in the sparse set. This method does not provide - /// any ordering guarantees other than the keys contained corresponding to - /// the values with the same index returned by [`Self::values`]. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// assert!(sparse_set.keys().is_empty()); - /// sparse_set.insert(10, 10); - /// sparse_set.insert(9, 10); - /// sparse_set.insert(11, 10); - /// - /// assert_eq!(sparse_set.keys(), &[10, 9, 11]); - /// sparse_set.remove(10); - /// assert_eq!(sparse_set.keys(), &[11, 9]); - /// ``` pub fn keys(&self) -> &[usize] { &self.dense_to_id } - /// Gets all values in the sparse set, the corresponding `key` is at the same - /// position in the slice returned by [`Self::keys`]. - /// - /// Otherwise there are no ordering guarantees. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// assert!(sparse_set.values().is_empty()); - /// sparse_set.insert(10, 10); - /// sparse_set.insert(9, 9); - /// sparse_set.insert(11, 11); - /// - /// assert_eq!(sparse_set.values(), &[10, 9, 11]); - /// sparse_set.remove(10); - /// assert_eq!(sparse_set.values(), &[11, 9]); - /// ``` pub fn values(&self) -> &[T] { &self.dense } - /// Mutable version of [`Self::keys`]. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// - /// let mut sparse_set: SparseSet<u32> = SparseSet::new(); - /// assert!(sparse_set.values().is_empty()); - /// sparse_set.insert(10, 10); - /// sparse_set.insert(9, 9); - /// sparse_set.insert(11, 11); - /// - /// let dense_of_9 = sparse_set.get_dense_idx(9).unwrap(); - /// let dense_of_10 = sparse_set.get_dense_idx(10).unwrap(); - /// - /// let values = sparse_set.values_mut(); - /// values[dense_of_10] = 9; - /// values[dense_of_9] = 10; - /// - /// assert_eq!(sparse_set.get(10), Some(&9)); - /// assert_eq!(sparse_set.get(9), Some(&10)); - /// ``` - pub fn values_mut(&mut self) -> &mut [T] { - &mut self.dense - } - - /// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc` - /// and everything else, as described in the top level documentation for [`SparseSet`] - /// in the `sparse_alloc`. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// use allocator_api2::alloc::Global; - /// - /// let sparse_set = SparseSet::<u32>::new_in(Global, Global); - /// ``` pub fn new_in(packed_alloc: PackedAlloc, sparse_alloc: SparseAlloc) -> Self { Self { dense: Vec::new_in(packed_alloc), @@ -363,16 +175,6 @@ impl<T, PackedAlloc> SparseSet<T, PackedAlloc> where PackedAlloc: Allocator, { - /// Creates a new [`SparseSet`] with the values allocated by the `packed_alloc` - /// Everything else, as described in the top level documentation for [`SparseSet`] - /// is allocated using the global allocator. - /// - /// ``` - /// use zenyx::collections::SparseSet; - /// use allocator_api2::alloc::Global; - /// - /// let sparse_set = SparseSet::<u32>::new_in_packed(Global); - /// ``` pub const fn new_in_packed(packed_alloc: PackedAlloc) -> Self { Self { sparse: Vec::new(), diff --git a/src/ecs/component.rs b/src/ecs/component.rs new file mode 100644 index 0000000..0fce562 --- /dev/null +++ b/src/ecs/component.rs @@ -0,0 +1,73 @@ +use core::{any::TypeId, marker::PhantomData, num::NonZeroU64, sync::atomic::AtomicU64}; +use std::{collections::BTreeMap, sync::RwLock}; + +use allocator_api2::alloc::{Allocator, Global}; +use bytemuck::Contiguous; + +static COMPONENT_ID_CREATOR: AtomicU64 = AtomicU64::new(1); +static COMPONENT_IDS: RwLock<BTreeMap<TypeId, ComponentId>> = RwLock::new(BTreeMap::new()); + +pub fn create_component_id<T>() -> ComponentId +where + T: 'static + Sized, +{ + let type_id = core::any::TypeId::of::<T>(); + { + // unwrap-justification: this only errors if RwLock is poisoned + let component_read = COMPONENT_IDS.read().unwrap(); + if let Some(id) = component_read.get(&type_id) { + return *id; + } + } + + let new_id = ComponentId( + NonZeroU64::new(COMPONENT_ID_CREATOR.fetch_add(1, core::sync::atomic::Ordering::Relaxed)) + .unwrap(), + ); + { + // unwrap-justification: see above + let mut component_write = COMPONENT_IDS.write().unwrap(); + component_write.insert(type_id, new_id); + } + new_id +} + +pub trait ComponentAllocator: Allocator { + fn new() -> Self; +} + +impl ComponentAllocator for Global { + fn new() -> Self { + Self + } +} + +pub trait Component: core::fmt::Debug + Send + Sized + 'static { + type Allocator: ComponentAllocator; + + fn id() -> ComponentId { + static COMPONENT_ID: AtomicU64 = AtomicU64::new(0); + let mut current_id = COMPONENT_ID.load(core::sync::atomic::Ordering::Relaxed); + if current_id == 0 { + current_id = create_component_id::<Self>().to_int(); + COMPONENT_ID.store(current_id, core::sync::atomic::Ordering::Relaxed); + } + ComponentId(NonZeroU64::new(current_id).unwrap()) + } +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ComponentId(NonZeroU64); + +impl ComponentId { + pub fn to_int(self) -> u64 { + self.0.into_integer() + } +} + +impl From<ComponentId> for u64 { + fn from(value: ComponentId) -> Self { + value.to_int() + } +} diff --git a/src/ecs/entity.rs b/src/ecs/entity.rs new file mode 100644 index 0000000..ce67bcd --- /dev/null +++ b/src/ecs/entity.rs @@ -0,0 +1,26 @@ +use core::sync::atomic::{AtomicU64, Ordering}; + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Entity(u64); + +fn create_entity() -> Entity { + static ENTITY_ID: AtomicU64 = AtomicU64::new(0); + Entity(ENTITY_ID.fetch_add(1, Ordering::Relaxed)) +} + +impl Entity { + pub fn new() -> Self { + create_entity() + } + + pub fn to_int(self) -> u64 { + self.0 + } +} + +impl From<Entity> for u64 { + fn from(value: Entity) -> Self { + value.to_int() + } +} diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs new file mode 100644 index 0000000..a3e2248 --- /dev/null +++ b/src/ecs/mod.rs @@ -0,0 +1,19 @@ +mod component; +pub use component::{Component, ComponentAllocator, ComponentId}; +mod entity; +pub use entity::Entity; +mod storage; + +use self::storage::ComponentSet; + +pub type ECS = EntityComponentSystem; + +pub struct EntityComponentSystem { + components: ComponentSet, +} + +impl EntityComponentSystem { + fn spawn(&mut self) -> Entity { + Entity::new() + } +} diff --git a/src/ecs/storage.rs b/src/ecs/storage.rs new file mode 100644 index 0000000..19d9d8e --- /dev/null +++ b/src/ecs/storage.rs @@ -0,0 +1,75 @@ +use allocator_api2::alloc::Allocator; + +use crate::collections::SparseSet; + +use super::{Component, ComponentAllocator, Entity}; + +pub trait ComponentStorage: core::fmt::Debug + 'static {} + +impl<T, SparseAlloc> ComponentStorage for SparseSet<T, T::Allocator, SparseAlloc> +where + T: Component, + SparseAlloc: Allocator + Clone + 'static, +{ +} + +#[derive(Debug)] +pub struct ComponentSet<A = allocator_api2::alloc::Global> { + sets: SparseSet<Box<dyn ComponentStorage>>, + cold_alloc: A, +} + +impl ComponentSet { + fn new() -> Self { + Self { + sets: SparseSet::new(), + cold_alloc: allocator_api2::alloc::Global, + } + } +} + +impl<A> ComponentSet<A> +where + A: Allocator + Clone + 'static, +{ + fn new_in(alloc: A) -> Self { + Self { + sets: SparseSet::new(), + cold_alloc: alloc, + } + } + + fn get_component_set<T: Component>(&self) -> Option<&SparseSet<T, T::Allocator, A>> { + let set = self.sets.get(T::id().to_int() as usize)?; + (set as &dyn core::any::Any).downcast_ref() + } + + fn get_component_set_mut<T: Component>( + &mut self, + ) -> Option<&mut SparseSet<T, T::Allocator, A>> { + let set = self.sets.get_mut(T::id().to_int() as usize)?; + (set as &mut dyn core::any::Any).downcast_mut() + } + + fn insert_component_set<T: Component>(&mut self) -> &mut SparseSet<T, T::Allocator, A> { + if self.sets.contains(T::id().to_int() as usize) { + self.get_component_set_mut::<T>().unwrap() + } else { + let set = SparseSet::<T, _, _>::new_in(T::Allocator::new(), self.cold_alloc.clone()); + self.sets + .insert(T::id().to_int() as usize, Box::new(set) as Box<_>); + self.get_component_set_mut::<T>().unwrap() + } + } + + fn add_to_entity<T: Component>(&mut self, entity: Entity, data: T) -> Option<T> { + let set = self.insert_component_set::<T>(); + set.insert(entity.to_int() as usize, data) + } +} + +impl Default for ComponentSet { + fn default() -> Self { + Self::new() + } +} diff --git a/src/main.rs b/src/main.rs index 0105e37..6786044 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ use zlog::config::LoggerConfig; pub mod camera; pub mod collections; +pub mod ecs; pub mod model; pub mod texture;