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> = RwLock::new(BTreeMap::new()); + +pub fn create_component_id() -> ComponentId +where + T: 'static + Sized, +{ + let type_id = core::any::TypeId::of::(); + { + // 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::().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 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 for u64 { + fn from(value: Entity) -> Self { + value.to_int() + } +} diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index 4069d90..a3e2248 100644 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -1,147 +1,19 @@ -use core::{any::TypeId, num::NonZeroU64, sync::atomic::AtomicU64}; -use std::{collections::BTreeMap, sync::RwLock}; +mod component; +pub use component::{Component, ComponentAllocator, ComponentId}; +mod entity; +pub use entity::Entity; +mod storage; -use allocator_api2::alloc::Allocator; -use bytemuck::Contiguous; - -use crate::collections::SparseSet; +use self::storage::ComponentSet; pub type ECS = EntityComponentSystem; -#[repr(transparent)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Entity(u64); - -static COMPONENT_ID_CREATOR: AtomicU64 = AtomicU64::new(1); -static COMPONENT_IDS: RwLock> = RwLock::new(BTreeMap::new()); - -fn create_component_id() -> ComponentId -where - T: 'static + Sized, -{ - let type_id = core::any::TypeId::of::(); - { - // 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; -} - -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::().0.into_integer(); - COMPONENT_ID.store(current_id, core::sync::atomic::Ordering::Relaxed); - } - ComponentId(NonZeroU64::new(current_id).unwrap()) - } -} - -pub trait ComponentStorage: core::fmt::Debug + 'static {} - -impl ComponentStorage for SparseSet -where - T: Component, - SparseAlloc: Allocator + Clone + 'static, -{ -} - -#[repr(transparent)] -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ComponentId(NonZeroU64); - -#[derive(Debug)] -pub struct ComponentSet { - sets: SparseSet>, - cold_alloc: A, -} - pub struct EntityComponentSystem { components: ComponentSet, - next_id: AtomicU64, } impl EntityComponentSystem { fn spawn(&mut self) -> Entity { - let entity_id = self - .next_id - .fetch_add(1, core::sync::atomic::Ordering::Relaxed); - Entity(entity_id) - } -} - -impl ComponentSet { - fn new() -> Self { - Self { - sets: SparseSet::new(), - cold_alloc: allocator_api2::alloc::Global, - } - } -} - -impl ComponentSet -where - A: Allocator + Clone + 'static, -{ - fn new_in(alloc: A) -> Self { - Self { - sets: SparseSet::new(), - cold_alloc: alloc, - } - } - - fn get_component_set(&self) -> Option<&SparseSet> { - let set = self.sets.get(T::id().0.into_integer() as usize)?; - (set as &dyn core::any::Any).downcast_ref() - } - - fn get_component_set_mut( - &mut self, - ) -> Option<&mut SparseSet> { - let set = self.sets.get_mut(T::id().0.into_integer() as usize)?; - (set as &mut dyn core::any::Any).downcast_mut() - } - - fn insert_component_set(&mut self) -> &mut SparseSet { - if self.sets.contains(T::id().0.into_integer() as usize) { - self.get_component_set_mut::().unwrap() - } else { - let set = SparseSet::::new_in(T::Allocator::new(), self.cold_alloc.clone()); - self.sets - .insert(T::id().0.into_integer() as usize, Box::new(set) as Box<_>); - self.get_component_set_mut::().unwrap() - } - } - - fn add_to_entity(&mut self, entity: Entity, data: T) -> Option { - let set = self.insert_component_set::(); - set.insert(entity.0 as usize, data) - } -} - -impl Default for ComponentSet { - fn default() -> Self { - Self::new() + 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 ComponentStorage for SparseSet +where + T: Component, + SparseAlloc: Allocator + Clone + 'static, +{ +} + +#[derive(Debug)] +pub struct ComponentSet { + sets: SparseSet>, + cold_alloc: A, +} + +impl ComponentSet { + fn new() -> Self { + Self { + sets: SparseSet::new(), + cold_alloc: allocator_api2::alloc::Global, + } + } +} + +impl ComponentSet +where + A: Allocator + Clone + 'static, +{ + fn new_in(alloc: A) -> Self { + Self { + sets: SparseSet::new(), + cold_alloc: alloc, + } + } + + fn get_component_set(&self) -> Option<&SparseSet> { + let set = self.sets.get(T::id().to_int() as usize)?; + (set as &dyn core::any::Any).downcast_ref() + } + + fn get_component_set_mut( + &mut self, + ) -> Option<&mut SparseSet> { + 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(&mut self) -> &mut SparseSet { + if self.sets.contains(T::id().to_int() as usize) { + self.get_component_set_mut::().unwrap() + } else { + let set = SparseSet::::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::().unwrap() + } + } + + fn add_to_entity(&mut self, entity: Entity, data: T) -> Option { + let set = self.insert_component_set::(); + set.insert(entity.to_int() as usize, data) + } +} + +impl Default for ComponentSet { + fn default() -> Self { + Self::new() + } +}