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