From 1019924a29527eba2c8ec8bd976ece6ed76075b0 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 25 Feb 2024 23:25:03 +0100 Subject: feat(ecs): add support for multiple system queries & local components --- ecs/src/system.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 193 insertions(+), 33 deletions(-) (limited to 'ecs/src/system.rs') diff --git a/ecs/src/system.rs b/ecs/src/system.rs index 22e2215..d90e0a2 100644 --- a/ecs/src/system.rs +++ b/ecs/src/system.rs @@ -1,16 +1,21 @@ -use std::any::Any; +use std::any::{Any, TypeId}; use std::convert::Infallible; use std::fmt::Debug; +use std::mem::{transmute_copy, ManuallyDrop}; +use std::ptr::addr_of_mut; -use crate::component::Sequence as ComponentSequence; -use crate::{ComponentStorage, Query}; +use seq_macro::seq; + +use crate::component::Component; +use crate::system::util::check_params_are_compatible; +use crate::ComponentStorage; pub mod stateful; +mod util; + pub trait System: 'static { - type Query<'a>; - type Input; #[must_use] @@ -19,39 +24,90 @@ pub trait System: 'static fn run(&mut self, component_storage: &mut ComponentStorage); fn into_type_erased(self) -> TypeErased; -} -impl System)> for Func -where - Func: Fn(Query) + 'static, - Comps: ComponentSequence, -{ - type Input = Infallible; - type Query<'a> = Query<'a, Comps>; + fn get_local_component_mut( + &mut self, + ) -> Option<&mut LocalComponent>; - fn initialize(self, _input: Self::Input) -> Self - { - self - } - - fn run(&mut self, component_storage: &mut ComponentStorage) - { - self(Query::new(component_storage)); - } + fn set_local_component( + &mut self, + local_component: LocalComponent, + ); +} - fn into_type_erased(self) -> TypeErased - { - TypeErased { - data: Box::new(self), - func: Box::new(|data, component_storage| { - let me = data.downcast_mut::().unwrap(); - - me.run(component_storage); - }), - } - } +macro_rules! impl_system { + ($c: tt) => { + seq!(I in 0..=$c { + impl<'world, Func, #(TParam~I,)*> System + for Func + where + Func: Fn(#(TParam~I,)*) + Copy + 'static, + #(TParam~I: Param<'world, Flags = NoInitParamFlag>,)* + { + type Input = Infallible; + + fn initialize(self, _input: Self::Input) -> Self + { + self + } + + fn run(&mut self, component_storage: &mut ComponentStorage) + { + #( + check_params_are_compatible!(I, TParam~I, $c); + )* + + let func = *self; + + func(#({ + // SAFETY: All parameters are compatible so this is fine + let this = unsafe { + &mut *addr_of_mut!(*self) + }; + + // SAFETY: All parameters are compatible so this is fine + let component_storage = unsafe { + &mut *addr_of_mut!(*component_storage) + }; + + TParam~I::new(this, component_storage) + },)*); + } + + fn into_type_erased(self) -> TypeErased + { + TypeErased { + data: Box::new(self), + func: Box::new(|data, component_storage| { + let me = data.downcast_mut::().unwrap(); + + me.run(component_storage); + }), + } + } + + fn get_local_component_mut( + &mut self, + ) -> Option<&mut LocalComponent> + { + panic!("System does not have any local components"); + } + + fn set_local_component( + &mut self, + _local_component: LocalComponent, + ) { + panic!("System does not have any local components"); + } + } + }); + }; } +seq!(C in 0..=4 { + impl_system!(C); +}); + pub trait Into { type System; @@ -83,3 +139,107 @@ impl Debug for TypeErased /// Function in [`TypeErased`] used to run the system. type TypeErasedFunc = dyn Fn(&mut dyn Any, &mut ComponentStorage); + +/// A parameter to a [`System`]. +/// +/// # Safety +/// The `is_compatible` function is used for safety so it must be implemented properly. +pub unsafe trait Param<'world> +{ + type Input; + type Flags; + + fn initialize(system: &mut impl System, input: Self::Input); + + fn new( + system: &'world mut impl System, + component_storage: &'world mut ComponentStorage, + ) -> Self; + + fn is_compatible>() -> bool; + + fn get_comparable() -> Box; +} + +pub struct NoInitParamFlag {} + +/// A type which can be used as input to a [`System`]. +pub trait Input: 'static {} + +pub trait InputFilter +{ + type Filtered: FilteredInputs; +} + +pub trait FilteredInputs +{ + type InOptions: OptionInputs; + + fn into_in_options(self) -> Self::InOptions; +} + +macro_rules! impl_filtered_inputs { + ($cnt: tt) => { + seq!(I in 0..$cnt { + impl<#(Input~I: Input,)*> FilteredInputs for (#(Input~I,)*) { + type InOptions = (#(Option,)*); + + fn into_in_options(self) -> Self::InOptions { + #![allow(clippy::unused_unit)] + (#(Some(self.I),)*) + } + } + }); + }; +} + +seq!(N in 0..4 { + impl_filtered_inputs!(N); +}); + +pub trait OptionInputs +{ + fn take(&mut self) -> TakeOptionInputResult; +} + +macro_rules! impl_option_inputs { + ($cnt: tt) => { + seq!(I in 0..$cnt { + impl<#(Input~I: 'static,)*> OptionInputs for (#(Option,)*) { + fn take(&mut self) -> TakeOptionInputResult { + #( + if TypeId::of::() == TypeId::of::() { + let input = match self.I.take() { + Some(input) => ManuallyDrop::new(input), + None => { + return TakeOptionInputResult::AlreadyTaken; + } + }; + + return TakeOptionInputResult::Found( + // SAFETY: It can be transmuted safely since it is the + // same type and the type is 'static + unsafe { transmute_copy(&input) } + ); + } + )* + + TakeOptionInputResult::NotFound + } + } + }); + }; +} + +seq!(N in 0..4 { + impl_option_inputs!(N); +}); + +pub enum TakeOptionInputResult +{ + Found(Input), + NotFound, + AlreadyTaken, +} + +include!(concat!(env!("OUT_DIR"), "/system_input_impls.rs")); -- cgit v1.2.3-18-g5258