summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock7
-rw-r--r--Cargo.toml6
-rw-r--r--src/bundle.rs34
-rw-r--r--src/component.rs52
-rw-r--r--src/lib.rs110
-rw-r--r--src/query.rs444
-rw-r--r--src/sparse_set.rs100
8 files changed, 754 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ea8c4bf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/target
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..0252c13
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "ecs"
+version = "0.1.0"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..70148ae
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "ecs"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
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());
+ }
+}