Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1use serde::{Deserialize, Serialize}; 2use tracing::{debug, error}; 3 4use crate::{self as ecsdb, query, Component, Ecs, Entity, Error}; 5 6use core::marker::PhantomData; 7use std::borrow::Cow; 8 9#[derive(Serialize, Deserialize, Component, Debug, PartialEq, Eq, Hash)] 10pub struct Name(pub String); 11 12#[derive(Serialize, Deserialize, Component, Debug)] 13pub struct LastRun(pub chrono::DateTime<chrono::Utc>); 14 15pub trait System: 'static + Send { 16 fn name(&self) -> Cow<'static, str>; 17 fn run(&self, app: &Ecs) -> Result<(), anyhow::Error>; 18} 19 20pub trait IntoSystem<Params> { 21 type System: System; 22 23 fn into_system(self) -> Self::System; 24} 25 26impl<F, Params: SystemParam + 'static> IntoSystem<Params> for F 27where 28 F: SystemParamFunction<Params>, 29{ 30 type System = FunctionSystem<F, Params>; 31 32 fn into_system(self) -> Self::System { 33 FunctionSystem { 34 system: self, 35 params: PhantomData, 36 } 37 } 38} 39 40pub struct FunctionSystem<F: 'static, Params: SystemParam> { 41 system: F, 42 params: PhantomData<fn() -> Params>, 43} 44 45impl<F, Params: SystemParam + 'static> System for FunctionSystem<F, Params> 46where 47 F: SystemParamFunction<Params>, 48{ 49 fn name(&self) -> Cow<'static, str> { 50 Cow::Borrowed(std::any::type_name::<F>()) 51 } 52 53 fn run(&self, app: &Ecs) -> Result<(), anyhow::Error> { 54 SystemParamFunction::run(&self.system, F::Param::get_param(app, &self.name())).into_result() 55 } 56} 57 58trait SystemParamFunction<Marker>: Send + Sync + 'static { 59 type Param: SystemParam; 60 fn run(&self, param: <Self::Param as SystemParam>::Item<'_>) -> Result<(), anyhow::Error>; 61} 62 63pub trait SystemOutput { 64 fn into_result(self) -> Result<(), anyhow::Error>; 65} 66 67impl SystemOutput for () { 68 fn into_result(self) -> Result<(), anyhow::Error> { 69 Ok(()) 70 } 71} 72 73impl SystemOutput for Result<(), anyhow::Error> { 74 fn into_result(self) -> Result<(), anyhow::Error> { 75 self 76 } 77} 78 79impl<F, Out> SystemParamFunction<()> for F 80where 81 F: Fn() -> Out + Send + Sync + 'static, 82 Out: SystemOutput, 83{ 84 type Param = (); 85 fn run(&self, _app: ()) -> Result<(), anyhow::Error> { 86 eprintln!("calling a function with no params"); 87 self().into_result() 88 } 89} 90 91type SystemParamItem<'world, P> = <P as SystemParam>::Item<'world>; 92 93macro_rules! impl_system_function { 94 ($($param: ident),*) => { 95 impl<F, Out, $($param: SystemParam),*> SystemParamFunction<($($param,)*)> for F 96 where 97 F: Send + Sync + 'static, 98 for<'a> &'a F: 99 Fn($($param),*) -> Out 100 + 101 Fn($(SystemParamItem<$param>),*) -> Out, 102 Out: SystemOutput, 103 { 104 type Param = ($($param,)*); 105 106 #[allow(non_snake_case)] 107 #[allow(clippy::too_many_arguments)] 108 fn run(&self, p: SystemParamItem<($($param,)*)>) -> Result<(), anyhow::Error> { 109 let ($($param,)*) = p; 110 (&self)( $($param),*).into_result() 111 } 112 } 113 114 impl<$($param: SystemParam,)*> SystemParam for ($($param,)*) { 115 type Item<'world> = ($($param::Item<'world>,)*); 116 117 fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world> { 118 ($($param::get_param(world, system),)*) 119 } 120 } 121 }; 122} 123 124impl_system_function!(P1); 125impl_system_function!(P1, P2); 126impl_system_function!(P1, P2, P3); 127impl_system_function!(P1, P2, P3, P4); 128impl_system_function!(P1, P2, P3, P4, P5); 129impl_system_function!(P1, P2, P3, P4, P5, P6); 130impl_system_function!(P1, P2, P3, P4, P5, P6, P7); 131 132// impl<F, Out, P1> SystemParamFunction<(P1,)> for F 133// where 134// F: Send + Sync + 'static, 135// for<'a> &'a F: Fn(P1) -> Out + Fn(<P1 as SystemParam>::Item<'_>) -> Out, 136// P1: SystemParam, 137// Out: SystemOutput, 138// { 139// type Param = (P1,); 140// fn run(&self, p1: <Self::Param as SystemParam>::Item<'_>) { 141// // #[allow(clippy::too_many_arguments)] 142// // fn call_inner<Out, P1>(f: impl Fn(P1) -> Out, p1: P1) -> Out { 143// // f(p1) 144// // } 145// // let (p1,) = p1; 146// // call_inner(self, p1); 147 148// (&self)(p1.0); 149// } 150// } 151 152// impl<F, Out, P1, P2> SystemParamFunction<(P1, P2)> for F 153// where 154// F: Send + Sync + 'static, 155// for<'a> &'a F: 156// Fn(P1, P2) -> Out + Fn(<P1 as SystemParam>::Item<'_>, <P2 as SystemParam>::Item<'_>) -> Out, 157// P1: SystemParam, 158// P2: SystemParam, 159// Out: SystemOutput, 160// { 161// type Param = (P1, P2); 162// fn run(&self, p: <Self::Param as SystemParam>::Item<'_>) { 163// (&self)(p.0, p.1); 164// } 165// } 166 167pub trait SystemParam: Sized { 168 type Item<'world>: SystemParam; 169 fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world>; 170} 171 172impl SystemParam for () { 173 type Item<'world> = (); 174 175 fn get_param<'world>(_world: &'world Ecs, _system: &str) -> Self::Item<'world> { 176 () 177 } 178} 179 180// impl<T1: SystemParam> SystemParam for (T1,) { 181// type Item<'world> = (T1::Item<'world>,); 182 183// fn get_param<'world>(world: &'world Ecs) -> Self::Item<'world> { 184// (T1::get_param(world),) 185// } 186// } 187 188// impl<T1: SystemParam, T2: SystemParam> SystemParam for (T1, T2) { 189// type Item<'world> = (T1::Item<'world>, T2::Item<'world>); 190 191// fn get_param<'world>(world: &'world Ecs) -> Self::Item<'world> { 192// (T1::get_param(world), T2::get_param(world)) 193// } 194// } 195 196impl Ecs { 197 pub fn tick(&self) { 198 let latest_change = self.latest_change_id().unwrap(); 199 200 for system in &self.systems { 201 let _span = tracing::info_span!("system", name = system.name().as_ref()).entered(); 202 let started = std::time::Instant::now(); 203 204 let entity = self 205 .system_entity(&system.name()) 206 .unwrap_or_else(|| self.new_entity().attach(Name(system.name().to_string()))); 207 208 debug!("Running"); 209 210 if let Err(e) = system.run(&self) { 211 error!(?e); 212 } 213 214 if let Some(latest_change) = latest_change { 215 self.clear_changes_up_to(latest_change).unwrap(); 216 } 217 218 entity.attach(LastRun(chrono::Utc::now())); 219 220 debug!(elapsed_ms = started.elapsed().as_millis(), "Finished",); 221 } 222 } 223 224 pub fn register<F: IntoSystem<Params>, Params: SystemParam>(&mut self, system: F) { 225 let system = Box::new(system.into_system()); 226 227 if self.system_entity(&system.name()).is_none() { 228 self.new_entity().attach(Name(system.name().to_string())); 229 } 230 231 self.systems.push(system); 232 } 233 234 pub fn system<'a>(&'a self, name: &str) -> Option<&'a dyn System> { 235 self.systems 236 .iter() 237 .find(|s| s.name() == name) 238 .map(|s| s.as_ref()) 239 } 240 241 pub fn system_entity<'a>(&'a self, name: &str) -> Option<Entity<'a>> { 242 self.find(Name(name.to_string())).next() 243 } 244} 245 246#[derive(Debug, Clone, Copy)] 247pub struct SystemEntity<'a>(pub Entity<'a>); 248 249impl<'a> AsRef<Entity<'a>> for SystemEntity<'a> { 250 fn as_ref(&self) -> &Entity<'a> { 251 &self.0 252 } 253} 254 255impl SystemParam for SystemEntity<'_> { 256 type Item<'world> = SystemEntity<'world>; 257 258 fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world> { 259 let Some(entity) = world.system_entity(system) else { 260 panic!("Couldn't find SystemEntity for {system:?}. This should not happen."); 261 }; 262 263 SystemEntity(entity) 264 } 265} 266 267impl SystemParam for &'_ Ecs { 268 type Item<'world> = &'world Ecs; 269 270 fn get_param<'world>(world: &'world Ecs, _system: &str) -> Self::Item<'world> { 271 world 272 } 273} 274 275impl<F> SystemParam for query::Query<'_, F, ()> { 276 type Item<'world> = query::Query<'world, F, ()>; 277 278 fn get_param<'world>(world: &'world Ecs, _system: &str) -> Self::Item<'world> { 279 query::Query::new(world, ()) 280 } 281} 282 283impl SystemParam for LastRun { 284 type Item<'world> = LastRun; 285 286 fn get_param<'world>(world: &'world Ecs, system: &str) -> Self::Item<'world> { 287 let never = LastRun(chrono::DateTime::<chrono::Utc>::MIN_UTC); 288 289 world 290 .system_entity(system) 291 .and_then(|entity| entity.component()) 292 .unwrap_or(never) 293 } 294} 295 296#[cfg(test)] 297mod tests { 298 use crate::{query, Ecs, SystemEntity}; 299 300 #[test] 301 fn no_param() { 302 let mut ecs = Ecs::open_in_memory().unwrap(); 303 ecs.register(|| ()); 304 } 305 306 #[test] 307 fn ecs_param() { 308 let mut ecs = Ecs::open_in_memory().unwrap(); 309 ecs.register(|_ecs: &Ecs| ()); 310 } 311 312 #[test] 313 fn query_param() { 314 let mut ecs = Ecs::open_in_memory().unwrap(); 315 ecs.register(|_q: query::Query<()>| ()); 316 } 317 318 #[test] 319 fn multiple_params() { 320 let mut ecs = Ecs::open_in_memory().unwrap(); 321 ecs.register(|_ecs: &Ecs, _q: query::Query<()>| ()); 322 ecs.register(|_: &Ecs, _: &Ecs| ()); 323 ecs.register(|_: &Ecs, _: &Ecs, _: &Ecs| ()); 324 ecs.register(|_: &Ecs, _: &Ecs, _: &Ecs, _: &Ecs| ()); 325 ecs.register(|_: &Ecs, _: &Ecs, _: &Ecs, _: &Ecs, _: &Ecs| ()); 326 } 327 328 use crate as ecsdb; 329 use ecsdb::Component; 330 use serde::{Deserialize, Serialize}; 331 332 #[derive(Debug, Serialize, Deserialize, Component)] 333 struct A; 334 335 #[derive(Debug, Serialize, Deserialize, Component)] 336 struct B; 337 338 #[derive(Debug, Serialize, Deserialize, Component)] 339 struct Seen; 340 341 #[test] 342 fn run_query() { 343 let mut db = Ecs::open_in_memory().unwrap(); 344 fn system(query: query::Query<(A, B)>) { 345 for entity in query.try_iter().unwrap() { 346 entity.attach(Seen); 347 } 348 } 349 350 db.register(system); 351 352 let a_and_b = db.new_entity().attach(A).attach(B); 353 let a = db.new_entity().attach(A); 354 355 db.tick(); 356 assert!(a_and_b.component::<Seen>().is_some()); 357 assert!(a.component::<Seen>().is_none()); 358 } 359 360 #[test] 361 fn run_ecs() { 362 let mut db = Ecs::open_in_memory().unwrap(); 363 fn system(ecs: &Ecs) { 364 ecs.new_entity().attach(Seen); 365 } 366 367 db.register(system); 368 db.tick(); 369 370 assert!(db.find(Seen).next().is_some()); 371 } 372 373 #[test] 374 fn system_entity_param() { 375 let mut db = Ecs::open_in_memory().unwrap(); 376 fn system(ecs: &Ecs, system: SystemEntity<'_>) { 377 assert_eq!( 378 system 379 .as_ref() 380 .component::<crate::system::Name>() 381 .unwrap() 382 .0, 383 "ecsdb::system::tests::system_entity_param::system" 384 ); 385 386 ecs.new_entity().attach(Seen); 387 } 388 389 db.register(system); 390 db.tick(); 391 392 assert!(db.find(Seen).next().is_some()); 393 } 394}