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 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 Input; #[must_use] fn initialize(self, input: Self::Input) -> Self; fn run(&mut self, component_storage: &mut ComponentStorage); fn into_type_erased(self) -> TypeErased; fn get_local_component_mut( &mut self, ) -> Option<&mut LocalComponent>; fn set_local_component( &mut self, local_component: LocalComponent, ); } 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; fn into_system(self) -> Self::System; } pub struct TypeErased { data: Box, func: Box, } impl TypeErased { pub fn run(&mut self, component_storage: &mut ComponentStorage) { (self.func)(self.data.as_mut(), component_storage); } } impl Debug for TypeErased { fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter.debug_struct("TypeErased").finish_non_exhaustive() } } /// 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"));