diff options
| author | Dominik Kaiser | 2026-06-02 23:23:57 +0200 |
|---|---|---|
| committer | Dominik Kaiser | 2026-06-02 23:23:57 +0200 |
| commit | 4632dd8e9e95a377993ef677a157202fda8072f9 (patch) | |
| tree | 60990f769556109c98e697c72dac1ad92f774d0f /src | |
| download | ecs-4632dd8e9e95a377993ef677a157202fda8072f9.tar.gz ecs-4632dd8e9e95a377993ef677a157202fda8072f9.zip | |
Diffstat (limited to 'src')
| -rw-r--r-- | src/bundle.rs | 34 | ||||
| -rw-r--r-- | src/component.rs | 52 | ||||
| -rw-r--r-- | src/lib.rs | 110 | ||||
| -rw-r--r-- | src/query.rs | 444 | ||||
| -rw-r--r-- | src/sparse_set.rs | 100 |
5 files changed, 740 insertions, 0 deletions
diff --git a/src/bundle.rs b/src/bundle.rs new file mode 100644 index 0000000..08753a7 --- /dev/null +++ b/src/bundle.rs @@ -0,0 +1,34 @@ +use super::Entity; +use super::component::{Component, ComponentStorage}; + +pub trait ComponentBundle<W> { + fn insert(self, world: &mut W, entity: Entity); +} + +macro_rules! impl_bundle_tuple { + ($($T:ident),+) => { + impl<W, $($T),+> ComponentBundle<W> for ($($T,)+) + where + $( + $T: Component, + W: ComponentStorage<$T>, + )+ + { + #[allow(non_snake_case)] + fn insert(self, world: &mut W, entity: Entity) { + let ($($T,)+) = self; + + $( + world.insert(entity, $T); + )+ + } + } + }; +} + +impl_bundle_tuple!(A); +impl_bundle_tuple!(A, B); +impl_bundle_tuple!(A, B, C); +impl_bundle_tuple!(A, B, C, D); +impl_bundle_tuple!(A, B, C, D, E); +impl_bundle_tuple!(A, B, C, D, E, F); diff --git a/src/component.rs b/src/component.rs new file mode 100644 index 0000000..1b36c3a --- /dev/null +++ b/src/component.rs @@ -0,0 +1,52 @@ +use super::Entity; +use super::sparse_set::SparseSet; + +pub trait Component {} + +#[macro_export] +macro_rules! component { + ($name:ident) => { + #[derive(Clone, Copy, Debug)] + pub struct $name; + impl Component for $name {} + }; + + ($name:ident, $ty:ty) => { + #[derive(Clone, Copy, Debug)] + pub struct $name(pub $ty); + + impl std::ops::Deref for $name { + type Target = $ty; + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl std::ops::DerefMut for $name { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } + } + + impl Component for $name {} + }; + + ($name:ident, { $($field:ident : $ty:ty),+ $(,)? }) => { + #[derive(Clone, Debug)] + pub struct $name { + $(pub $field: $ty),+ + } + + impl Component for $name {} + }; +} + +pub trait ComponentStorage<T: Component> { + fn storage(&self) -> &SparseSet<T>; + fn storage_mut(&mut self) -> &mut SparseSet<T>; + fn storage_ptr(&mut self) -> *mut SparseSet<T>; + + fn insert(&mut self, entity: Entity, component: T) { + self.storage_mut().insert(entity, component); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..be4c2bf --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,110 @@ +pub mod bundle; +pub mod component; +pub mod query; +pub mod sparse_set; + +pub mod prelude { + pub use super::component::Component; + pub use super::component::ComponentStorage; + pub use super::sparse_set::SparseSet; + pub use super::Entity; + pub use super::bundle::ComponentBundle; + pub use super::query::Query; + pub use super::query::QueryMut; +} + +pub type Entity = usize; + +#[macro_export] +macro_rules! ecs { + ($name:ident { + context: $ctx:ty, + components: { + $($comp:ident),* $(,)? + }, + systems: { + $($sys:path),* $(,)? + }$(,)? + }) => { + #[allow(non_snake_case)] + struct $name { + next_entity: Entity, + $($comp: SparseSet<$comp>),* + } + + impl $name { + pub fn new() -> Self { + Self { + next_entity: 0, + $($comp: SparseSet::new()),* + } + } + + pub fn update(&mut self, ctx: &mut $ctx) { + $($sys(self, ctx));* + } + + pub fn insert<T: ComponentBundle<Self>>(&mut self, entity: Entity, bundle: T) { + bundle.insert(self, entity); + } + + pub fn spawn<T: ComponentBundle<Self>>(&mut self, bundle:T) -> Entity { + let e = self.next_entity; + self.next_entity += 1; + bundle.insert(self, e); + e + } + + pub fn remove<T: Component>(&mut self, entity: Entity) + where + Self: ComponentStorage<T>, + { + self.storage_mut().remove(entity); + } + + pub fn delete(&mut self, entity: Entity) { + $(self.$comp.remove(entity);)* + } + + pub fn get<T: Component>(&self, entity: Entity) -> Option<&T> + where + Self: ComponentStorage<T>, + { + self.storage().get(entity) + } + + pub fn get_mut<T: Component>(&mut self, entity: Entity) -> Option<&mut T> + where + Self: ComponentStorage<T>, + { + self.storage_mut().get_mut(entity) + } + + } + + impl<'a> $name { + pub fn query<Q: Query<'a, Self>>(&'a self) -> impl Iterator<Item = Q::Item> { + Q::run(self) + } + + pub fn query_mut<Q: QueryMut<'a, Self>>(&'a mut self) + -> impl Iterator<Item = Q::Item> { + Q::run_mut(self) + } + } + + $(impl ComponentStorage<$comp> for $name { + fn storage(&self) -> &SparseSet<$comp> { + &self.$comp + } + + fn storage_mut(&mut self) -> &mut SparseSet<$comp> { + &mut self.$comp + } + + fn storage_ptr(&mut self) -> *mut SparseSet<$comp> { + &mut self.$comp + } + })+ + }; +} diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 0000000..658185b --- /dev/null +++ b/src/query.rs @@ -0,0 +1,444 @@ +use super::Entity; +use super::component::{Component, ComponentStorage}; + +pub trait Query<'a, W> { + type Item; + fn run(world: &'a W) -> impl Iterator<Item = Self::Item>; +} + +pub trait QueryMut<'a, W> { + type Item; + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item>; +} + +impl<'a, W, A> Query<'a, W> for A +where + W: ComponentStorage<A>, + A: Component + 'a, +{ + type Item = &'a A; + fn run(world: &'a W) -> impl Iterator<Item = Self::Item> { + ComponentStorage::<A>::storage(world).iter().map(|(_, a)| a) + } +} + +impl<'a, W, A> QueryMut<'a, W> for A +where + W: ComponentStorage<A>, + A: Component + 'a, +{ + type Item = &'a mut A; + + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item> { + let a_ptr = <W as ComponentStorage<A>>::storage_ptr(world); + + unsafe { + let a_ref = &mut *a_ptr; + a_ref.iter_mut().map(|(_, a_comp)| a_comp) + } + } +} + +impl<'a, W, A> Query<'a, W> for (A,) +where + W: ComponentStorage<A>, + A: Component + 'a, +{ + type Item = &'a A; + fn run(world: &'a W) -> impl Iterator<Item = Self::Item> { + ComponentStorage::<A>::storage(world).iter().map(|(_, a)| a) + } +} + +impl<'a, W, A> QueryMut<'a, W> for (A,) +where + W: ComponentStorage<A>, + A: Component + 'a, +{ + type Item = &'a mut A; + + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item> { + let a_ptr = <W as ComponentStorage<A>>::storage_ptr(world); + + unsafe { + let a_ref = &mut *a_ptr; + a_ref.iter_mut().map(|(_, a_comp)| a_comp) + } + } +} + +impl<'a, W, A> Query<'a, W> for (Entity, A) +where + W: ComponentStorage<A>, + A: Component + 'a, +{ + type Item = (Entity, &'a A); + fn run(world: &'a W) -> impl Iterator<Item = Self::Item> { + ComponentStorage::<A>::storage(world).iter() + } +} + +macro_rules! impl_query { + ( + $first:ident + $(, $rest:ident)+ + ) => { + #[allow(non_snake_case)] + impl<'a, W, $first, $($rest),+> Query<'a, W> + for ($first, $($rest),+) + where + $first: Component + 'a, + $( + $rest: Component + 'a, + )+ + W: ComponentStorage<$first> + $(+ ComponentStorage<$rest>)+, + { + type Item = ( + &'a $first, + $( + &'a $rest, + )+ + ); + + fn run(world: &'a W) -> impl Iterator<Item = Self::Item> { + let first_storage = + <W as ComponentStorage<$first>>::storage(world); + + $( + let $rest = + <W as ComponentStorage<$rest>>::storage(world); + )+ + + first_storage.iter().filter_map(move |(entity, first_comp)| { + $( + let $rest = $rest.get(entity)?; + )+ + + Some(( + first_comp, + $( + $rest, + )+ + )) + }) + } + } + + #[allow(non_snake_case)] + impl<'a, W, $first, $($rest),+> Query<'a, W> + for (Entity, $first, $($rest),+) + where + $first: Component + 'a, + $( + $rest: Component + 'a, + )+ + W: ComponentStorage<$first> + $(+ ComponentStorage<$rest>)+, + { + type Item = ( + Entity, + &'a $first, + $( + &'a $rest, + )+ + ); + + fn run(world: &'a W) -> impl Iterator<Item = Self::Item> { + let first_storage = + <W as ComponentStorage<$first>>::storage(world); + + $( + let $rest = + <W as ComponentStorage<$rest>>::storage(world); + )+ + + first_storage.iter().filter_map(move |(entity, first_comp)| { + $( + let $rest = $rest.get(entity)?; + )+ + + Some(( + entity, + first_comp, + $( + $rest, + )+ + )) + }) + } + } + }; +} + +macro_rules! impl_query_mut { + ( + $first:ident + $(, $rest:ident)* + ) => { + #[allow(non_snake_case)] + impl<'a, W, $first, $($rest),*> QueryMut<'a, W> + for ($first, $($rest),*) + where + $first: Component + 'a, + $( + $rest: Component + 'a, + )* + W: ComponentStorage<$first> + $(+ ComponentStorage<$rest>)*, + { + type Item = ( + &'a mut $first, + $( + &'a mut $rest, + )* + ); + + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item> { + let first_ptr = + <W as ComponentStorage<$first>>::storage_ptr(world); + + $( + let $rest = + <W as ComponentStorage<$rest>>::storage_ptr(world); + )* + + unsafe { + let first_ref = &mut *first_ptr; + + first_ref.iter_mut().filter_map(move |(entity, first_comp)| { + $( + let $rest = + (*$rest).get_mut(entity)?; + )* + + Some(( + first_comp, + $( + $rest, + )* + )) + }) + } + } + } + + #[allow(non_snake_case)] + impl<'a, W, $first, $($rest),*> QueryMut<'a, W> + for (Entity, $first, $($rest),*) + where + $first: Component + 'a, + $( + $rest: Component + 'a, + )* + W: ComponentStorage<$first> + $(+ ComponentStorage<$rest>)*, + { + type Item = ( + Entity, + &'a mut $first, + $( + &'a mut $rest, + )* + ); + + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item> { + let first_ptr = + <W as ComponentStorage<$first>>::storage_ptr(world); + + $( + let $rest = + <W as ComponentStorage<$rest>>::storage_ptr(world); + )* + + unsafe { + let first_ref = &mut *first_ptr; + + first_ref.iter_mut().filter_map(move |(entity, first_comp)| { + $( + let $rest = + (*$rest).get_mut(entity)?; + )* + + Some(( + entity, + first_comp, + $( + $rest, + )* + )) + }) + } + } + } + }; +} + +macro_rules! impl_query_mut_opt { + ( + $first:ident + $(, $req:ident)* + => + $($opt:ident ?),* $(,)? + ) => { + #[allow(non_snake_case)] + impl<'a, W, $first, $($req,)* $($opt,)*> QueryMut<'a, W> + for ( + $first, + $($req,)* + $(Option<$opt>,)* + ) + where + W: ComponentStorage<$first> + $(+ ComponentStorage<$req>)* + $(+ ComponentStorage<$opt>)*, + $first: Component + 'a, + $( + $req: Component + 'a, + )* + $( + $opt: Component + 'a, + )* + { + type Item = ( + &'a mut $first, + $( + &'a mut $req, + )* + $( + Option<&'a mut $opt>, + )* + ); + + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item> { + let first_ptr = + <W as ComponentStorage<$first>>::storage_ptr(world); + + $( + let $req = + <W as ComponentStorage<$req>>::storage_ptr(world); + )* + + $( + let $opt = + <W as ComponentStorage<$opt>>::storage_ptr(world); + )* + + unsafe { + let first_ref = &mut *first_ptr; + + first_ref.iter_mut().filter_map(move |(entity, first_comp)| { + $( + let $req = + (*$req).get_mut(entity)?; + )* + + Some(( + first_comp, + $( + $req, + )* + $( + (*$opt).get_mut(entity), + )* + )) + }) + } + } + } + + #[allow(non_snake_case)] + impl<'a, W, $first, $($req,)* $($opt,)*> QueryMut<'a, W> + for ( + Entity, + $first, + $($req,)* + $(Option<$opt>,)* + ) + where + W: ComponentStorage<$first> + $(+ ComponentStorage<$req>)* + $(+ ComponentStorage<$opt>)*, + $first: Component + 'a, + $( + $req: Component + 'a, + )* + $( + $opt: Component + 'a, + )* + { + type Item = ( + Entity, + &'a mut $first, + $( + &'a mut $req, + )* + $( + Option<&'a mut $opt>, + )* + ); + + fn run_mut(world: &'a mut W) -> impl Iterator<Item = Self::Item> { + let first_ptr = + <W as ComponentStorage<$first>>::storage_ptr(world); + + $( + let $req = + <W as ComponentStorage<$req>>::storage_ptr(world); + )* + + $( + let $opt = + <W as ComponentStorage<$opt>>::storage_ptr(world); + )* + + unsafe { + let first_ref = &mut *first_ptr; + + first_ref.iter_mut().filter_map(move |(entity, first_comp)| { + $( + let $req = + (*$req).get_mut(entity)?; + )* + + Some(( + entity, + first_comp, + $( + $req, + )* + $( + (*$opt).get_mut(entity), + )* + )) + }) + } + } + } + }; +} + +impl_query!(A, B); +impl_query!(A, B, C); +impl_query!(A, B, C, D); +impl_query!(A, B, C, D, E); +impl_query!(A, B, C, D, E, F); + +impl_query_mut!(A, B); +impl_query_mut!(A, B, C); +impl_query_mut!(A, B, C, D); +impl_query_mut!(A, B, C, D, E); +impl_query_mut!(A, B, C, D, E, F); + +impl_query_mut_opt!(A => B?); +impl_query_mut_opt!(A, B => C?); +impl_query_mut_opt!(A, B, C => D?); +impl_query_mut_opt!(A, B, C, D => E?); +impl_query_mut_opt!(A, B, C, D, E => F?); +impl_query_mut_opt!(A => B?, C?); +impl_query_mut_opt!(A, B => C?, D?); +impl_query_mut_opt!(A, B, C => D?, E?); +impl_query_mut_opt!(A, B, C, D => E?, F?); +impl_query_mut_opt!(A => B?, C?, D?); +impl_query_mut_opt!(A, B => C?, D?, E?); +impl_query_mut_opt!(A, B, C => D?, E?, F?); diff --git a/src/sparse_set.rs b/src/sparse_set.rs new file mode 100644 index 0000000..d7a69eb --- /dev/null +++ b/src/sparse_set.rs @@ -0,0 +1,100 @@ +use super::Entity; + +pub struct SparseSet<T> { + pub dense: Vec<T>, + pub entities: Vec<Entity>, + pub sparse: Vec<Option<usize>>, +} + +impl<T> SparseSet<T> { + pub fn new() -> Self { + Self { + dense: Vec::new(), + entities: Vec::new(), + sparse: Vec::new(), + } + } + + pub fn insert(&mut self, entity: Entity, component: T) { + if self.sparse.len() <= entity { + self.sparse.resize(entity + 1, None); + } + + self.dense.push(component); + self.entities.push(entity); + + let id = self.dense.len() - 1; + self.sparse[entity] = Some(id); + } + + pub fn remove(&mut self, entity: Entity) { + let Some(id) = self.sparse.get(entity).copied().flatten() else { + return; + }; + let Some(last) = self.entities.last().copied() else { + return; + }; + self.dense.swap_remove(id); + self.entities.swap_remove(id); + self.sparse[last] = Some(id); + self.sparse[entity] = None; + } + + pub fn iter(&self) -> impl Iterator<Item = (Entity, &T)> { + self.entities.iter().copied().zip(self.dense.iter()) + } + + pub fn iter_mut(&mut self) -> impl Iterator<Item = (Entity, &mut T)> { + self.entities.iter().copied().zip(self.dense.iter_mut()) + } + + pub fn get(&self, entity: Entity) -> Option<&T> { + if let Some(id) = self.sparse.get(entity) { + match id { + Some(id) => self.dense.get(*id), + None => None, + } + } else { + None + } + } + + pub fn get_mut(&mut self, entity: Entity) -> Option<&mut T> { + if let Some(id) = self.sparse.get(entity) { + match id { + Some(id) => self.dense.get_mut(*id), + None => None, + } + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_remove() { + let mut set = SparseSet::<i32>::new(); + set.insert(3, 300); + set.insert(2, 200); + set.insert(4, 400); + set.insert(7, 700); + + set.remove(4); + + assert!(set.get(7).copied() == Some(700)); + assert!(set.get(3).copied() == Some(300)); + assert!(set.get(2).copied() == Some(200)); + assert!(set.get(4).copied().is_none()); + + set.remove(7); + assert!(set.get(7).copied().is_none()); + assert!(set.get(3).copied() == Some(300)); + assert!(set.get(2).copied() == Some(200)); + assert!(set.get(4).copied().is_none()); + } +} |
