use serde::{Deserialize, Serialize}; use tracing::{debug, error}; use crate::{self as ecsdb, query, Component, Ecs, Entity, Error}; use core::marker::PhantomData; use std::borrow::Cow; #[derive(Serialize, Deserialize, Component, Debug, PartialEq, Eq, Hash)] pub struct Name(pub String); #[derive(Serialize, Deserialize, Component, Debug)] pub struct LastRun(pub chrono::DateTime); pub trait System: 'static + Send { fn name(&self) -> Cow<'static, str>; fn run(&self, app: &Ecs) -> Result<(), anyhow::Error>; } pub trait IntoSystem { type System: System; fn into_system(self) -> Self::System; } impl IntoSystem for F where F: SystemParamFunction, { type System = FunctionSystem; fn into_system(self) -> Self::System { FunctionSystem { system: self, params: PhantomData, } } } pub struct FunctionSystem { system: F, params: PhantomData Params>, } impl System for FunctionSystem where F: SystemParamFunction, { fn name(&self) -> Cow<'static, str> { Cow::Borrowed(std::any::type_name::()) } fn run(&self, app: &Ecs) -> Result<(), anyhow::Error> { SystemParamFunction::run(&self.system, F::Param::get_param(app, &self.name())).into_result() } } trait SystemParamFunction: Send + Sync + 'static { type Param: SystemParam; fn run(&self, param: ::Item<'_>) -> Result<(), anyhow::Error>; } pub trait SystemOutput { fn into_result(self) -> Result<(), anyhow::Error>; } impl SystemOutput for () { fn into_result(self) -> Result<(), anyhow::Error> { Ok(()) } } impl SystemOutput for Result<(), anyhow::Error> { fn into_result(self) -> Result<(), anyhow::Error> { self } } impl SystemParamFunction<()> for F where F: Fn() -> Out + Send + Sync + 'static, Out: SystemOutput, { type Param = (); fn run(&self, _app: ()) -> Result<(), anyhow::Error> { eprintln!("calling a function with no params"); self().into_result() } } type SystemParamItem<'world, P> =

::Item<'world>; macro_rules! impl_system_function { ($($param: ident),*) => { impl SystemParamFunction<($($param,)*)> for F where F: Send + Sync + 'static, for<'a> &'a F: Fn($($param),*) -> Out + Fn($(SystemParamItem<$param>),*) -> Out, Out: SystemOutput, { type Param = ($($param,)*); #[allow(non_snake_case)] #[allow(clippy::too_many_arguments)] fn run(&self, p: SystemParamItem<($($param,)*)>) -> Result<(), anyhow::Error> { let ($($param,)*) = p; (&self)( $($param),*).into_result() } } impl<$($param: SystemParam,)*> SystemParam for ($($param,)*) { type Item<'world> = ($($param::Item<'world>,)*); fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world> { ($($param::get_param(world, system),)*) } } }; } impl_system_function!(P1); impl_system_function!(P1, P2); impl_system_function!(P1, P2, P3); impl_system_function!(P1, P2, P3, P4); impl_system_function!(P1, P2, P3, P4, P5); impl_system_function!(P1, P2, P3, P4, P5, P6); impl_system_function!(P1, P2, P3, P4, P5, P6, P7); // impl SystemParamFunction<(P1,)> for F // where // F: Send + Sync + 'static, // for<'a> &'a F: Fn(P1) -> Out + Fn(::Item<'_>) -> Out, // P1: SystemParam, // Out: SystemOutput, // { // type Param = (P1,); // fn run(&self, p1: ::Item<'_>) { // // #[allow(clippy::too_many_arguments)] // // fn call_inner(f: impl Fn(P1) -> Out, p1: P1) -> Out { // // f(p1) // // } // // let (p1,) = p1; // // call_inner(self, p1); // (&self)(p1.0); // } // } // impl SystemParamFunction<(P1, P2)> for F // where // F: Send + Sync + 'static, // for<'a> &'a F: // Fn(P1, P2) -> Out + Fn(::Item<'_>, ::Item<'_>) -> Out, // P1: SystemParam, // P2: SystemParam, // Out: SystemOutput, // { // type Param = (P1, P2); // fn run(&self, p: ::Item<'_>) { // (&self)(p.0, p.1); // } // } pub trait SystemParam: Sized { type Item<'world>: SystemParam; fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world>; } impl SystemParam for () { type Item<'world> = (); fn get_param<'world>(_world: &'world Ecs, _system: &str) -> Self::Item<'world> { () } } // impl SystemParam for (T1,) { // type Item<'world> = (T1::Item<'world>,); // fn get_param<'world>(world: &'world Ecs) -> Self::Item<'world> { // (T1::get_param(world),) // } // } // impl SystemParam for (T1, T2) { // type Item<'world> = (T1::Item<'world>, T2::Item<'world>); // fn get_param<'world>(world: &'world Ecs) -> Self::Item<'world> { // (T1::get_param(world), T2::get_param(world)) // } // } impl Ecs { pub fn tick(&self) { let latest_change = self.latest_change_id().unwrap(); for system in &self.systems { let _span = tracing::info_span!("system", name = system.name().as_ref()).entered(); let started = std::time::Instant::now(); let entity = self .system_entity(&system.name()) .unwrap_or_else(|| self.new_entity().attach(Name(system.name().to_string()))); debug!("Running"); if let Err(e) = system.run(&self) { error!(?e); } if let Some(latest_change) = latest_change { self.clear_changes_up_to(latest_change).unwrap(); } entity.attach(LastRun(chrono::Utc::now())); debug!(elapsed_ms = started.elapsed().as_millis(), "Finished",); } } pub fn register, Params: SystemParam>(&mut self, system: F) { let system = Box::new(system.into_system()); if self.system_entity(&system.name()).is_none() { self.new_entity().attach(Name(system.name().to_string())); } self.systems.push(system); } pub fn system<'a>(&'a self, name: &str) -> Option<&'a dyn System> { self.systems .iter() .find(|s| s.name() == name) .map(|s| s.as_ref()) } pub fn system_entity<'a>(&'a self, name: &str) -> Option> { self.find(Name(name.to_string())).next() } } #[derive(Debug, Clone, Copy)] pub struct SystemEntity<'a>(pub Entity<'a>); impl<'a> AsRef> for SystemEntity<'a> { fn as_ref(&self) -> &Entity<'a> { &self.0 } } impl SystemParam for SystemEntity<'_> { type Item<'world> = SystemEntity<'world>; fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world> { let Some(entity) = world.system_entity(system) else { panic!("Couldn't find SystemEntity for {system:?}. This should not happen."); }; SystemEntity(entity) } } impl SystemParam for &'_ Ecs { type Item<'world> = &'world Ecs; fn get_param<'world>(world: &'world Ecs, _system: &str) -> Self::Item<'world> { world } } impl SystemParam for query::Query<'_, F, ()> { type Item<'world> = query::Query<'world, F, ()>; fn get_param<'world>(world: &'world Ecs, _system: &str) -> Self::Item<'world> { query::Query::new(world, ()) } } impl SystemParam for LastRun { type Item<'world> = LastRun; fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world> { let never = LastRun(chrono::DateTime::::MIN_UTC); world .system_entity(system) .and_then(|entity| entity.component()) .unwrap_or(never) } } #[cfg(test)] mod tests { use crate::{query, Ecs, SystemEntity}; #[test] fn no_param() { let mut ecs = Ecs::open_in_memory().unwrap(); ecs.register(|| ()); } #[test] fn ecs_param() { let mut ecs = Ecs::open_in_memory().unwrap(); ecs.register(|_ecs: &Ecs| ()); } #[test] fn query_param() { let mut ecs = Ecs::open_in_memory().unwrap(); ecs.register(|_q: query::Query<()>| ()); } #[test] fn multiple_params() { let mut ecs = Ecs::open_in_memory().unwrap(); ecs.register(|_ecs: &Ecs, _q: query::Query<()>| ()); ecs.register(|_: &Ecs, _: &Ecs| ()); ecs.register(|_: &Ecs, _: &Ecs, _: &Ecs| ()); ecs.register(|_: &Ecs, _: &Ecs, _: &Ecs, _: &Ecs| ()); ecs.register(|_: &Ecs, _: &Ecs, _: &Ecs, _: &Ecs, _: &Ecs| ()); } use crate as ecsdb; use ecsdb::Component; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Component)] struct A; #[derive(Debug, Serialize, Deserialize, Component)] struct B; #[derive(Debug, Serialize, Deserialize, Component)] struct Seen; #[test] fn run_query() { let mut db = Ecs::open_in_memory().unwrap(); fn system(query: query::Query<(A, B)>) { for entity in query.try_iter().unwrap() { entity.attach(Seen); } } db.register(system); let a_and_b = db.new_entity().attach(A).attach(B); let a = db.new_entity().attach(A); db.tick(); assert!(a_and_b.component::().is_some()); assert!(a.component::().is_none()); } #[test] fn run_ecs() { let mut db = Ecs::open_in_memory().unwrap(); fn system(ecs: &Ecs) { ecs.new_entity().attach(Seen); } db.register(system); db.tick(); assert!(db.find(Seen).next().is_some()); } #[test] fn system_entity_param() { let mut db = Ecs::open_in_memory().unwrap(); fn system(ecs: &Ecs, system: SystemEntity<'_>) { assert_eq!( system .as_ref() .component::() .unwrap() .0, "ecsdb::system::tests::system_entity_param::system" ); ecs.new_entity().attach(Seen); } db.register(system); db.tick(); assert!(db.find(Seen).next().is_some()); } }