summaryrefslogtreecommitdiff
path: root/engine-ecs/src/system
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2026-05-21 17:55:20 +0200
committerHampusM <hampus@hampusmat.com>2026-05-21 17:55:20 +0200
commit8022e8998290b067b8aa0cb9cba8ba410826bdab (patch)
tree7171e79ce530e03079046ee8fd12167160c45480 /engine-ecs/src/system
parent412cee02c252f91bcf0b70a3f5cc5fca6d2b4c62 (diff)
chore: rename ecs* crates to engine-ecs*HEADmaster
Diffstat (limited to 'engine-ecs/src/system')
-rw-r--r--engine-ecs/src/system/initializable.rs131
-rw-r--r--engine-ecs/src/system/observer.rs278
-rw-r--r--engine-ecs/src/system/stateful.rs269
3 files changed, 678 insertions, 0 deletions
diff --git a/engine-ecs/src/system/initializable.rs b/engine-ecs/src/system/initializable.rs
new file mode 100644
index 0000000..b6ec8e8
--- /dev/null
+++ b/engine-ecs/src/system/initializable.rs
@@ -0,0 +1,131 @@
+use std::marker::PhantomData;
+
+use seq_macro::seq;
+
+use crate::system::{Input, Param as SystemParam, System};
+use crate::tuple::{Reduce as TupleReduce, ReduceElement as TupleReduceElement, Tuple};
+
+/// A initializable system.
+pub trait Initializable<'world, Impl>: System<'world, Impl>
+{
+ type Inputs;
+
+ #[must_use]
+ fn initialize(self, inputs: Self::Inputs) -> Self;
+}
+
+pub trait Param<'world, SystemT>: SystemParam<'world>
+{
+ fn initialize(system: &mut SystemT, input: Self::Input);
+}
+
+pub struct ParamTupleFilter<'world, SystemT>
+{
+ _pd: PhantomData<(&'world (), SystemT)>,
+}
+
+impl<'world, SystemT, ParamT, Accumulator>
+ TupleReduceElement<Accumulator, ParamTupleFilter<'world, SystemT>> for ParamT
+where
+ ParamT: SystemParam<
+ 'world,
+ Input: AppendInitializableParam<'world, Accumulator, ParamT, SystemT>,
+ >,
+ Accumulator: Tuple,
+{
+ type Return = <ParamT::Input as AppendInitializableParam<
+ 'world,
+ Accumulator,
+ ParamT,
+ SystemT,
+ >>::Return;
+}
+
+pub trait AppendInitializableParam<'world, Accumulator, ParamT, SystemT>
+{
+ type Return;
+}
+
+impl<'world, InputT, ParamT, Accumulator, SystemT>
+ AppendInitializableParam<'world, Accumulator, ParamT, SystemT> for InputT
+where
+ InputT: Input,
+ Accumulator: Tuple,
+ ParamT: Param<'world, SystemT>,
+{
+ type Return = Accumulator::WithElementAtEnd<ParamT>;
+}
+
+impl<ParamT, Accumulator, SystemT>
+ AppendInitializableParam<'_, Accumulator, ParamT, SystemT> for ()
+where
+ Accumulator: Tuple,
+{
+ type Return = Accumulator;
+}
+
+pub trait ParamTuple<'world, SystemT>
+{
+ type Inputs;
+
+ fn initialize_all(system: &mut SystemT, inputs: Self::Inputs);
+}
+
+macro_rules! impl_initializable_param_tuple {
+ ($c: tt) => {
+ seq!(I in 0..$c {
+ impl<'world, SystemT, #(Param~I,)*> ParamTuple<'world, SystemT>
+ for (#(Param~I,)*)
+ where
+ #(Param~I: Param<'world, SystemT>,)*
+ {
+ type Inputs = (#(Param~I::Input,)*);
+
+ fn initialize_all(
+ system: &mut SystemT,
+ inputs: Self::Inputs,
+ ) {
+ #(
+ <Param~I as Param<'world, SystemT>>::initialize(
+ system,
+ inputs.I
+ );
+ )*
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 1..16 {
+ impl_initializable_param_tuple!(C);
+});
+
+impl<SystemT> ParamTuple<'_, SystemT> for ()
+{
+ type Inputs = ();
+
+ fn initialize_all(_system: &mut SystemT, _inputs: Self::Inputs) {}
+}
+
+/// A tuple of system parameters that may or may not be initializable.
+pub trait MaybeInitializableParamTuple<'world, SystemT>
+{
+ /// A tuple of the inputs of the initializable system parameters in this tuple.
+ type Inputs;
+
+ fn init_initializable(system: &mut SystemT, inputs: Self::Inputs);
+}
+
+impl<'world, SystemT, Params> MaybeInitializableParamTuple<'world, SystemT> for Params
+where
+ Params:
+ TupleReduce<ParamTupleFilter<'world, SystemT>, Out: ParamTuple<'world, SystemT>>,
+{
+ type Inputs = <Params::Out as ParamTuple<'world, SystemT>>::Inputs;
+
+ fn init_initializable(system: &mut SystemT, inputs: Self::Inputs)
+ {
+ Params::Out::initialize_all(system, inputs);
+ }
+}
diff --git a/engine-ecs/src/system/observer.rs b/engine-ecs/src/system/observer.rs
new file mode 100644
index 0000000..1ad7496
--- /dev/null
+++ b/engine-ecs/src/system/observer.rs
@@ -0,0 +1,278 @@
+use std::any::type_name;
+use std::fmt::Debug;
+use std::marker::PhantomData;
+use std::mem::transmute;
+use std::slice::Iter as SliceIter;
+
+use seq_macro::seq;
+
+use crate::entity::Handle as EntityHandle;
+use crate::error::Error;
+use crate::event::Emitted as EmittedEvent;
+use crate::pair::Pair;
+use crate::system::{
+ Metadata,
+ NoCallbacks,
+ Param,
+ ReturnValue as SystemReturnValue,
+ System,
+ TypeErased as TypeErasedSystem,
+};
+use crate::uid::Uid;
+use crate::util::Array;
+use crate::{Component, World};
+
+pub trait Observed
+{
+ type Events: Array<Pair<Uid, Uid>>;
+
+ fn events() -> Self::Events;
+}
+
+impl<Relation, Target> Observed for Pair<Relation, Target>
+where
+ Relation: Component,
+ Target: Component,
+{
+ type Events = [Pair<Uid, Uid>; 1];
+
+ fn events() -> Self::Events
+ {
+ [Pair::builder()
+ .relation::<Relation>()
+ .target::<Target>()
+ .build()]
+ }
+}
+
+/// Observer system.
+pub trait Observer<'world, Impl>: System<'world, Impl>
+{
+ type ObservedEvents: Array<Pair<Uid, Uid>>;
+
+ fn observed_events() -> Self::ObservedEvents;
+
+ fn finish_observer(self) -> (WrapperComponent, Self::Callbacks);
+}
+
+pub struct Observe<'world, ObservedT: Observed>
+{
+ _pd: PhantomData<ObservedT>,
+ world: &'world World,
+ emitted_event: EmittedEvent<'world>,
+}
+
+impl<'world, ObservedT: Observed> Observe<'world, ObservedT>
+{
+ pub fn new(world: &'world World, emitted_event: EmittedEvent<'world>) -> Self
+ {
+ Self {
+ _pd: PhantomData,
+ world,
+ emitted_event,
+ }
+ }
+
+ #[must_use]
+ pub fn event(&self) -> Uid
+ {
+ self.emitted_event.event
+ }
+}
+
+impl<ObservedT: Observed> Observe<'_, ObservedT>
+{
+ #[must_use]
+ pub fn iter(&self) -> ObserveIter<'_, ObservedT>
+ {
+ ObserveIter {
+ world: self.world,
+ inner: self.emitted_event.match_ids.iter(),
+ _pd: PhantomData,
+ }
+ }
+}
+
+impl<'a, ObservedT: Observed> IntoIterator for &'a Observe<'_, ObservedT>
+{
+ type IntoIter = ObserveIter<'a, ObservedT>;
+ type Item = <Self::IntoIter as Iterator>::Item;
+
+ fn into_iter(self) -> Self::IntoIter
+ {
+ self.iter()
+ }
+}
+
+pub struct ObserveIter<'observe, ObservedT: Observed>
+{
+ world: &'observe World,
+ inner: SliceIter<'observe, Uid>,
+ _pd: PhantomData<ObservedT>,
+}
+
+impl<'observe, ObservedT: Observed> Iterator for ObserveIter<'observe, ObservedT>
+{
+ type Item = EventMatch<'observe, ObservedT>;
+
+ fn next(&mut self) -> Option<Self::Item>
+ {
+ let match_id = *self.inner.next()?;
+
+ Some(EventMatch {
+ world: self.world,
+ id: match_id,
+ _pd: PhantomData,
+ })
+ }
+}
+
+/// A event match.
+#[derive(Debug)]
+pub struct EventMatch<'world, ObservedT: Observed>
+{
+ world: &'world World,
+ id: Uid,
+ _pd: PhantomData<ObservedT>,
+}
+
+impl<'world, ObservedT: Observed> EventMatch<'world, ObservedT>
+{
+ #[must_use]
+ pub fn entity_id(&self) -> Uid
+ {
+ self.id
+ }
+
+ /// Attempts to get the entity with the id of this match.
+ #[must_use]
+ pub fn try_get_entity(&self) -> Option<EntityHandle<'world>>
+ {
+ self.world.get_entity(self.id)
+ }
+}
+
+macro_rules! impl_observer {
+ ($c: tt) => {
+ seq!(I in 0..$c {
+ impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> System<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret
+ > for Func
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static,
+ Ret: SystemReturnValue,
+ #(TParam~I: Param<'world, Input = ()>,)*
+ {
+ type Callbacks = NoCallbacks;
+
+ fn finish(self) -> (TypeErasedSystem, NoCallbacks)
+ {
+ const {
+ panic!("Observers cannot be used as regular systems");
+ }
+ }
+ }
+
+ impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Observer<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret
+ > for Func
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static,
+ Ret: SystemReturnValue,
+ #(TParam~I: Param<'world, Input = ()>,)*
+ {
+ type ObservedEvents = ObservedT::Events;
+
+ fn observed_events() -> Self::ObservedEvents
+ {
+ ObservedT::events()
+ }
+
+ fn finish_observer(self) -> (WrapperComponent, NoCallbacks)
+ {
+ #[allow(unused)]
+
+ let wrapper_comp = WrapperComponent::new(
+ move |world, metadata, emitted_event| {
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let world = unsafe { &*std::ptr::from_ref(world) };
+
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let emitted_event = unsafe {
+ transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(
+ emitted_event
+ )
+ };
+
+ self(Observe::new(world, emitted_event), #({
+ TParam~I::new(world, &metadata)
+ },)*).into_result()
+ },
+ type_name::<Func>()
+ );
+
+ (wrapper_comp, NoCallbacks)
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..16 {
+ impl_observer!(C);
+});
+
+#[derive(Component)]
+pub struct WrapperComponent
+{
+ run: Box<RunFn>,
+ name: &'static str,
+}
+
+impl WrapperComponent
+{
+ pub fn new(
+ run: impl Fn(&World, Metadata, EmittedEvent<'_>) -> Result<(), Error> + 'static,
+ name: &'static str,
+ ) -> Self
+ {
+ Self { run: Box::new(run), name }
+ }
+
+ /// Runs the observer system.
+ ///
+ /// # Safety
+ /// `world` must live at least as long as the [`World`] the system belongs to.
+ pub unsafe fn run(
+ &self,
+ world: &World,
+ metadata: Metadata,
+ emitted_event: EmittedEvent<'_>,
+ ) -> Result<(), Error>
+ {
+ (self.run)(world, metadata, emitted_event)
+ }
+
+ pub fn name(&self) -> &'static str
+ {
+ self.name
+ }
+}
+
+impl Debug for WrapperComponent
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ formatter
+ .debug_struct("WrapperComponent")
+ .finish_non_exhaustive()
+ }
+}
+
+type RunFn = dyn Fn(&World, Metadata, EmittedEvent<'_>) -> Result<(), Error>;
diff --git a/engine-ecs/src/system/stateful.rs b/engine-ecs/src/system/stateful.rs
new file mode 100644
index 0000000..3e0076a
--- /dev/null
+++ b/engine-ecs/src/system/stateful.rs
@@ -0,0 +1,269 @@
+use std::any::type_name;
+use std::mem::transmute;
+
+use seq_macro::seq;
+
+use crate::World;
+use crate::component::Parts as ComponentParts;
+use crate::component::local::SystemWithLocalComponents;
+use crate::event::Emitted as EmittedEvent;
+use crate::system::initializable::{Initializable, MaybeInitializableParamTuple};
+use crate::system::observer::{
+ Observe,
+ Observed,
+ Observer,
+ WrapperComponent as ObserverWrapperComponent,
+};
+use crate::system::{
+ Into as IntoSystem,
+ Metadata,
+ Param,
+ ReturnValue,
+ System,
+ TypeErased,
+};
+
+/// A stateful system.
+pub struct Stateful<Func>
+{
+ func: Func,
+ local_components: Vec<ComponentParts>,
+}
+
+macro_rules! impl_system {
+ ($c: tt) => {
+ seq!(I in 0..$c {
+ impl<'world, Func, Ret, #(TParam~I,)*>
+ System<'world, fn(#(TParam~I,)*) -> Ret> for Stateful<Func>
+ where
+ Func: Fn(#(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world, Input: 'static>,)*
+ {
+ type Callbacks = Callbacks;
+
+ fn finish(self) -> (TypeErased, Self::Callbacks)
+ {
+ let Self { func, local_components } = self;
+
+ let callbacks = Callbacks { local_components };
+
+ let type_erased = TypeErased {
+ run: Box::new(move |world, metadata| {
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let world = unsafe { &*std::ptr::from_ref(world) };
+
+ func(#({
+ TParam~I::new(&world, &metadata)
+ },)*);
+
+ Ok(())
+ }),
+ name: type_name::<Func>()
+ };
+
+
+ (type_erased, callbacks)
+ }
+ }
+
+ impl<'world, Func, Ret, #(TParam~I,)*>
+ Initializable<'world, fn(#(TParam~I,)*) -> Ret> for Stateful<Func>
+ where
+ Func: Fn(#(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world, Input: 'static>,)*
+ (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self>
+ {
+ type Inputs = <
+ (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self>
+ >::Inputs;
+
+ fn initialize(mut self, inputs: Self::Inputs) -> Self
+ {
+ init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs);
+
+ self
+ }
+ }
+
+ impl<'world, Func, Ret, #(TParam~I,)*>
+ IntoSystem<'world, fn(#(TParam~I,)*) -> Ret> for Func
+ where
+ Func: Fn(#(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world>,)*
+ {
+ type System = Stateful<Func>;
+
+ fn into_system(self) -> Self::System
+ {
+ Self::System {
+ func: self,
+ local_components: Vec::new(), // TODO: Use Vec::with_capacity
+ }
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 1..16 {
+ impl_system!(C);
+});
+
+impl<Func> SystemWithLocalComponents for Stateful<Func>
+{
+ fn add_local_component(&mut self, component_parts: ComponentParts)
+ {
+ self.local_components.push(component_parts);
+ }
+}
+
+#[derive(Debug)]
+pub struct Callbacks
+{
+ local_components: Vec<ComponentParts>,
+}
+
+impl crate::system::Callbacks for Callbacks
+{
+ fn on_created(&mut self, world: &mut World, metadata: Metadata)
+ {
+ for local_comp_parts in self.local_components.drain(..) {
+ world.add_component(metadata.ent_id, local_comp_parts);
+ }
+ }
+}
+
+fn init_initializable_params<'world, SystemT, Params>(
+ system: &mut SystemT,
+ inputs: Params::Inputs,
+) where
+ Params: MaybeInitializableParamTuple<'world, SystemT>,
+{
+ Params::init_initializable(system, inputs);
+}
+
+macro_rules! impl_observer {
+ ($c: tt) => {
+ seq!(I in 0..$c {
+ impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> System<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret
+ > for Stateful<Func>
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world>,)*
+ {
+ type Callbacks = Callbacks;
+
+ fn finish(self) -> (TypeErased, Callbacks)
+ {
+ unimplemented!();
+ }
+ }
+
+ impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Initializable<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret
+ > for Stateful<Func>
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world>,)*
+ (#(TParam~I,)*): MaybeInitializableParamTuple<'world, Self>
+ {
+ type Inputs = <
+ (#(TParam~I,)*) as MaybeInitializableParamTuple<'world, Self>
+ >::Inputs;
+
+ fn initialize(mut self, inputs: Self::Inputs) -> Self
+ {
+ init_initializable_params::<_, (#(TParam~I,)*)>(&mut self, inputs);
+
+ self
+ }
+ }
+
+ impl<'world, ObservedT, Func, Ret, #(TParam~I,)*> Observer<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret
+ > for Stateful<Func>
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world>,)*
+ {
+ type ObservedEvents = ObservedT::Events;
+
+ fn observed_events() -> Self::ObservedEvents
+ {
+ ObservedT::events()
+ }
+
+ fn finish_observer(self) -> (ObserverWrapperComponent, Callbacks)
+ {
+ #![allow(unused)]
+
+ let Self { func, local_components } = self;
+
+ let callbacks = Callbacks { local_components };
+
+ let wrapper_comp = ObserverWrapperComponent::new(
+ move |world, metadata, emitted_event| {
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let world = unsafe { &*std::ptr::from_ref(world) };
+
+ // SAFETY: The caller of TypeErased::run ensures the lifetime
+ // is correct
+ let emitted_event = unsafe {
+ transmute::<EmittedEvent<'_>, EmittedEvent<'_>>(
+ emitted_event
+ )
+ };
+
+ func(Observe::new(world, emitted_event), #({
+ TParam~I::new(world, &metadata)
+ },)*).into_result()
+ },
+ type_name::<Func>()
+ );
+
+ (wrapper_comp, callbacks)
+ }
+ }
+
+ impl<'world, Func, Ret, ObservedT, #(TParam~I,)*> IntoSystem<
+ 'world,
+ fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret
+ > for Func
+ where
+ ObservedT: Observed,
+ Func: Fn(Observe<'world, ObservedT>, #(TParam~I,)*) -> Ret + 'static,
+ Ret: ReturnValue,
+ #(TParam~I: Param<'world>,)*
+ {
+ type System = Stateful<Func>;
+
+ fn into_system(self) -> Stateful<Func>
+ {
+ Stateful {
+ func: self,
+ local_components: Vec::new(), // TODO: Use Vec::with_capacity
+ }
+ }
+ }
+ });
+ };
+}
+
+seq!(C in 0..16 {
+ impl_observer!(C);
+});