From de5c3ff1320ea0f0452afde4c1f42676d9eeab52 Mon Sep 17 00:00:00 2001 From: HampusM Date: Fri, 16 Feb 2024 19:58:53 +0100 Subject: feat: add entity component system library --- ecs/Cargo.toml | 7 ++ ecs/examples/simple.rs | 32 ++++++++ ecs/src/component.rs | 114 +++++++++++++++++++++++++++++ ecs/src/lib.rs | 194 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 347 insertions(+) create mode 100644 ecs/Cargo.toml create mode 100644 ecs/examples/simple.rs create mode 100644 ecs/src/component.rs create mode 100644 ecs/src/lib.rs (limited to 'ecs') 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::::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 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(&mut self) -> Option<&mut Real> + { + self.as_any_mut().downcast_mut() + } + + pub fn is(&self) -> bool + { + self.as_any().is::() + } +} + +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>; + + fn type_ids() -> Vec; + + fn from_components<'components>( + components: &'components mut [Box], + ) -> 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> { + Vec::from_iter([#(Box::new(self.I) as Box,)*]) + } + + fn type_ids() -> Vec { + vec![ + #( + TypeId::of::(), + )* + ] + } + + fn from_components<'components>( + components: &'components mut [Box], + ) -> Self::MutRefs<'components> + { + #( + let mut comp_~I = None; + )* + + for comp in components.iter_mut() { + #( + if comp.is::() { + comp_~I = Some(comp); + continue; + } + + )* + } + + + (#( + comp_~I.unwrap().downcast_mut::().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>, +} + +#[derive(Debug)] +pub struct World +{ + systems: Vec>, + events: HashMap>, + extra: WorldExtra, +} + +#[derive(Debug)] +struct WorldExtra +{ + entities: Vec, +} + +impl World +{ + #[must_use] + pub fn new() -> Self + { + Self { + systems: Vec::new(), + extra: WorldExtra { entities: Vec::new() }, + events: HashMap::new(), + } + } + + pub fn create_entity(&mut self, components: Comps) + where + Comps: ComponentSequence, + { + self.extra + .entities + .push(Entity { components: components.into_vec() }); + } + + pub fn register_system(&mut self, event: Event, system: System) + 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(&mut self) -> Query + where + Comps: ComponentSequence, + { + Query::new(&mut self.extra) + } +} + +impl Default for World +{ + fn default() -> Self + { + Self::new() + } +} + +pub type System = fn(Query); + +trait AnySystem +{ + fn call(&self, world: &mut WorldExtra); +} + +impl AnySystem for System +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, +} + +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 + { + 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, + comps_pd: PhantomData, +} + +impl<'world, Comps> Iterator for QueryComponentIter<'world, Comps> +where + Comps: ComponentSequence + 'world, +{ + type Item = Comps::MutRefs<'world>; + + fn next(&mut self) -> Option + { + // 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)) + } +} -- cgit v1.2.3-18-g5258