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}