summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock19
-rw-r--r--Cargo.toml2
-rw-r--r--ecs/Cargo.toml7
-rw-r--r--ecs/examples/simple.rs32
-rw-r--r--ecs/src/component.rs114
-rw-r--r--ecs/src/lib.rs194
6 files changed, 361 insertions, 7 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 93ec3d0..884fa3f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -119,6 +119,13 @@ dependencies = [
]
[[package]]
+name = "ecs"
+version = "0.1.0"
+dependencies = [
+ "seq-macro",
+]
+
+[[package]]
name = "engine"
version = "0.1.0"
dependencies = [
@@ -360,18 +367,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.68"
+version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b1106fec09662ec6dd98ccac0f81cef56984d0b49f75c92d8cbad76e20c005c"
+checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.33"
+version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
+checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
@@ -446,9 +453,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
[[package]]
name = "syn"
-version = "2.0.38"
+version = "2.0.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
+checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f"
dependencies = [
"proc-macro2",
"quote",
diff --git a/Cargo.toml b/Cargo.toml
index 9baf278..373c4ab 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
edition = "2021"
[workspace]
-members = ["glfw", "engine"]
+members = ["glfw", "engine", "ecs"]
[dependencies]
engine = { path = "./engine", features = ["debug"] }
diff --git a/ecs/Cargo.toml b/ecs/Cargo.toml
new file mode 100644
index 0000000..cd25673
--- /dev/null
+++ b/ecs/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "ecs"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+seq-macro = "0.3.5"
diff --git a/ecs/examples/simple.rs b/ecs/examples/simple.rs
new file mode 100644
index 0000000..b58d2ba
--- /dev/null
+++ b/ecs/examples/simple.rs
@@ -0,0 +1,32 @@
+use ecs::{Query, World};
+
+struct SomeData
+{
+ num: u64,
+}
+
+fn say_hello(mut query: Query<(SomeData, String)>)
+{
+ for (data, text) in query.iter_mut() {
+ println!("Hello {}: {}", text, data.num);
+ }
+}
+
+#[derive(Debug, PartialEq, Eq, Hash)]
+enum Event
+{
+ Start,
+}
+
+fn main()
+{
+ let mut world = World::<Event>::new();
+
+ world.register_system(Event::Start, say_hello);
+
+ world.create_entity((SomeData { num: 987_654 }, "Yoo".to_string()));
+
+ world.create_entity((SomeData { num: 345 }, "Haha".to_string()));
+
+ world.emit(&Event::Start);
+}
diff --git a/ecs/src/component.rs b/ecs/src/component.rs
new file mode 100644
index 0000000..4829050
--- /dev/null
+++ b/ecs/src/component.rs
@@ -0,0 +1,114 @@
+use std::any::{Any, TypeId};
+use std::fmt::Debug;
+
+use seq_macro::seq;
+
+pub trait Component: Any
+{
+ #[doc(hidden)]
+ fn as_any_mut(&mut self) -> &mut dyn Any;
+
+ #[doc(hidden)]
+ fn as_any(&self) -> &dyn Any;
+}
+
+impl<Value: Any> Component for Value
+{
+ fn as_any_mut(&mut self) -> &mut dyn Any
+ {
+ self
+ }
+
+ fn as_any(&self) -> &dyn Any
+ {
+ self
+ }
+}
+
+impl dyn Component
+{
+ pub fn downcast_mut<Real: 'static>(&mut self) -> Option<&mut Real>
+ {
+ self.as_any_mut().downcast_mut()
+ }
+
+ pub fn is<Other: 'static>(&self) -> bool
+ {
+ self.as_any().is::<Other>()
+ }
+}
+
+impl Debug for dyn Component
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ formatter.debug_struct("Component").finish_non_exhaustive()
+ }
+}
+
+/// A sequence of components.
+pub trait Sequence
+{
+ type MutRefs<'component>
+ where
+ Self: 'component;
+
+ fn into_vec(self) -> Vec<Box<dyn Component>>;
+
+ fn type_ids() -> Vec<TypeId>;
+
+ fn from_components<'components>(
+ components: &'components mut [Box<dyn Component>],
+ ) -> Self::MutRefs<'components>;
+}
+
+macro_rules! inner {
+ ($c: tt) => {
+ seq!(I in 0..=$c {
+ impl<#(Comp~I: Component,)*> Sequence for (#(Comp~I,)*) {
+ type MutRefs<'component> = (#(&'component mut Comp~I,)*)
+ where Self: 'component;
+
+ fn into_vec(self) -> Vec<Box<dyn Component>> {
+ Vec::from_iter([#(Box::new(self.I) as Box<dyn Component>,)*])
+ }
+
+ fn type_ids() -> Vec<TypeId> {
+ vec![
+ #(
+ TypeId::of::<Comp~I>(),
+ )*
+ ]
+ }
+
+ fn from_components<'components>(
+ components: &'components mut [Box<dyn Component>],
+ ) -> Self::MutRefs<'components>
+ {
+ #(
+ let mut comp_~I = None;
+ )*
+
+ for comp in components.iter_mut() {
+ #(
+ if comp.is::<Comp~I>() {
+ comp_~I = Some(comp);
+ continue;
+ }
+
+ )*
+ }
+
+
+ (#(
+ comp_~I.unwrap().downcast_mut::<Comp~I>().unwrap(),
+ )*)
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..=64 {
+ inner!(C);
+});
diff --git a/ecs/src/lib.rs b/ecs/src/lib.rs
new file mode 100644
index 0000000..5b8942b
--- /dev/null
+++ b/ecs/src/lib.rs
@@ -0,0 +1,194 @@
+#![deny(clippy::all, clippy::pedantic)]
+
+use std::any::TypeId;
+use std::collections::{HashMap, HashSet};
+use std::fmt::Debug;
+use std::hash::Hash;
+use std::marker::PhantomData;
+use std::slice::IterMut as SliceIterMut;
+
+use crate::component::{Component, Sequence as ComponentSequence};
+
+pub mod component;
+
+#[derive(Debug)]
+struct Entity
+{
+ components: Vec<Box<dyn Component>>,
+}
+
+#[derive(Debug)]
+pub struct World<Event>
+{
+ systems: Vec<Box<dyn AnySystem>>,
+ events: HashMap<Event, Vec<usize>>,
+ extra: WorldExtra,
+}
+
+#[derive(Debug)]
+struct WorldExtra
+{
+ entities: Vec<Entity>,
+}
+
+impl<Event> World<Event>
+{
+ #[must_use]
+ pub fn new() -> Self
+ {
+ Self {
+ systems: Vec::new(),
+ extra: WorldExtra { entities: Vec::new() },
+ events: HashMap::new(),
+ }
+ }
+
+ pub fn create_entity<Comps>(&mut self, components: Comps)
+ where
+ Comps: ComponentSequence,
+ {
+ self.extra
+ .entities
+ .push(Entity { components: components.into_vec() });
+ }
+
+ pub fn register_system<Comps>(&mut self, event: Event, system: System<Comps>)
+ where
+ Event: Hash + PartialEq + Eq,
+ Comps: ComponentSequence + 'static,
+ {
+ self.systems.push(Box::new(system));
+
+ self.events
+ .entry(event)
+ .or_default()
+ .push(self.systems.len() - 1);
+ }
+
+ /// Emits a event, running all systems listening to the event for each compatible
+ /// entity.
+ ///
+ /// # Panics
+ /// Will panic if a system has dissapeared.
+ pub fn emit(&mut self, event: &Event)
+ where
+ Event: Hash + PartialEq + Eq,
+ {
+ let Some(system_indices) = self.events.get(event).cloned() else {
+ return;
+ };
+
+ for system_index in system_indices {
+ let system = self.systems.get_mut(system_index).unwrap();
+
+ system.call(&mut self.extra);
+ }
+ }
+
+ pub fn query<Comps>(&mut self) -> Query<Comps>
+ where
+ Comps: ComponentSequence,
+ {
+ Query::new(&mut self.extra)
+ }
+}
+
+impl<Event> Default for World<Event>
+{
+ fn default() -> Self
+ {
+ Self::new()
+ }
+}
+
+pub type System<Comps> = fn(Query<Comps>);
+
+trait AnySystem
+{
+ fn call(&self, world: &mut WorldExtra);
+}
+
+impl<Comps> AnySystem for System<Comps>
+where
+ Comps: ComponentSequence,
+{
+ fn call(&self, world: &mut WorldExtra)
+ {
+ self(Query::new(world));
+ }
+}
+
+impl Debug for dyn AnySystem
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ formatter.debug_struct("AnySystem").finish_non_exhaustive()
+ }
+}
+
+#[derive(Debug)]
+pub struct Query<'world, Comps>
+{
+ world: &'world mut WorldExtra,
+ comps_pd: PhantomData<Comps>,
+}
+
+impl<'world, Comps> Query<'world, Comps>
+{
+ fn new(world: &'world mut WorldExtra) -> Self
+ {
+ Self { world, comps_pd: PhantomData }
+ }
+}
+
+impl<'world, Comps> Query<'world, Comps>
+where
+ Comps: ComponentSequence,
+{
+ pub fn iter_mut(&mut self) -> QueryComponentIter<Comps>
+ {
+ QueryComponentIter {
+ entity_iter: self.world.entities.iter_mut(),
+ component_type_ids: Comps::type_ids(),
+ comps_pd: PhantomData,
+ }
+ }
+}
+
+pub struct QueryComponentIter<'world, Comps>
+{
+ entity_iter: SliceIterMut<'world, Entity>,
+ component_type_ids: Vec<TypeId>,
+ comps_pd: PhantomData<Comps>,
+}
+
+impl<'world, Comps> Iterator for QueryComponentIter<'world, Comps>
+where
+ Comps: ComponentSequence + 'world,
+{
+ type Item = Comps::MutRefs<'world>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ // TODO: This is a really dumb and slow way to do this. Refactor the world
+ // to store components in archetypes
+ let entity =
+ self.entity_iter.find(|entity| {
+ let entity_components: HashSet<_> = entity
+ .components
+ .iter()
+ .map(|component| component.as_ref().type_id())
+ .collect();
+
+ if self.component_type_ids.iter().all(|component_type_id| {
+ entity_components.contains(component_type_id)
+ }) {
+ return true;
+ }
+
+ false
+ })?;
+
+ Some(Comps::from_components(&mut entity.components))
+ }
+}