Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1pub mod component;
2pub use component::{Component, ComponentRead, ComponentWrite};
3
4pub mod entity;
5pub use entity::{Entity, NewEntity};
6
7pub mod extension;
8pub use extension::Extension;
9
10pub mod hierarchy;
11pub use hierarchy::*;
12
13pub mod query;
14
15pub mod resource;
16pub use resource::*;
17
18pub mod system;
19use ::rusqlite::{params, OptionalExtension};
20pub use system::*;
21
22use std::path::Path;
23
24use query::DataFilter;
25use tracing::{debug, instrument};
26
27pub type EntityId = i64;
28
29#[derive(Debug, thiserror::Error)]
30pub enum Error {
31 #[error("Database Error: {0}")]
32 Database(#[from] rusqlite::Error),
33 #[error(transparent)]
34 ComponentStorage(#[from] component::StorageError),
35}
36
37pub struct Ecs {
38 conn: rusqlite::Connection,
39 systems: Vec<Box<dyn system::System>>,
40 extensions: anymap::Map<dyn anymap::any::Any + Send>,
41}
42
43impl Ecs {
44 pub fn open_in_memory() -> Result<Self, Error> {
45 Self::from_rusqlite(rusqlite::Connection::open_in_memory()?)
46 }
47
48 pub fn open(path: impl AsRef<Path>) -> Result<Self, Error> {
49 Self::from_rusqlite(rusqlite::Connection::open(path)?)
50 }
51
52 pub fn from_rusqlite(mut conn: rusqlite::Connection) -> Result<Self, Error> {
53 conn.pragma_update(None, "journal_mode", "wal")?;
54 conn.execute_batch(include_str!("schema.sql"))?;
55 conn.set_transaction_behavior(::rusqlite::TransactionBehavior::Immediate);
56 Ok(Self {
57 conn,
58 systems: Default::default(),
59 extensions: anymap::Map::new(),
60 })
61 }
62}
63
64impl Ecs {
65 pub fn close(self) -> Result<(), Error> {
66 self.conn.close().map_err(|(_conn, e)| Error::Database(e))
67 }
68
69 pub fn data_version(&self) -> Result<i64, Error> {
70 Ok(self
71 .conn
72 .query_row("select data_version from pragma_data_version", [], |x| {
73 x.get("data_version")
74 })?)
75 }
76}
77
78impl Ecs {
79 pub fn enable_change_tracking(&mut self) -> Result<(), Error> {
80 self.conn
81 .execute_batch(include_str!("change_tracking_enable.sql"))?;
82 Ok(())
83 }
84
85 pub fn disable_change_tracking(&mut self) -> Result<(), Error> {
86 self.conn
87 .execute_batch(include_str!("change_tracking_disable.sql"))?;
88 Ok(())
89 }
90}
91
92impl Ecs {
93 pub fn new_entity<'a>(&'a self) -> NewEntity<'a> {
94 Entity::without_id(self)
95 }
96
97 pub fn entity<'a>(&'a self, eid: EntityId) -> Entity<'a> {
98 Entity::with_id(self, eid)
99 }
100}
101
102impl Ecs {
103 pub fn query<'a, F>(&'a self) -> impl Iterator<Item = Entity<'a>> + 'a
104 where
105 F: query::Filter + 'a,
106 {
107 self.try_query::<'a, F>().unwrap()
108 }
109
110 #[instrument(name = "query", level = "debug", skip_all)]
111 pub fn try_query<'a, F>(&'a self) -> Result<impl Iterator<Item = Entity<'a>> + 'a, Error>
112 where
113 F: query::Filter + 'a,
114 {
115 debug!(query = std::any::type_name::<F>());
116 let query = query::Query::<F, ()>::new(self, ());
117 query.try_into_iter()
118 }
119}
120
121impl Ecs {
122 pub fn find<'a, V: DataFilter>(
123 &'a self,
124 components: V,
125 ) -> impl Iterator<Item = Entity<'a>> + 'a {
126 self.try_find(components).unwrap()
127 }
128
129 #[instrument(name = "find", level = "debug", skip_all)]
130 pub fn try_find<'a, V: DataFilter>(
131 &'a self,
132 components: V,
133 ) -> Result<impl Iterator<Item = Entity<'a>> + 'a, Error> {
134 let query = query::Query::<(), _>::new(self, components);
135 query.try_into_iter()
136 }
137}
138
139impl Ecs {
140 /// Returns the highest `last_modified` from all components of `entity`.
141 /// Returns `chrono::DateTime::MIN_UTC` if `entity` has no components
142 fn try_last_modified(
143 &self,
144 entity: EntityId,
145 ) -> Result<chrono::DateTime<chrono::Utc>, rusqlite::Error> {
146 let mut stmt = self.conn.prepare_cached(
147 "select max(last_modified) as last_modified from components where entity = ?",
148 )?;
149
150 let last_modified = stmt
151 .query_map(params![&entity], |row| {
152 row.get::<_, Option<String>>("last_modified")
153 })?
154 .flat_map(|dt| {
155 dt.unwrap_or_else(|e| panic!("max(last_modified) on {entity} TEXT error={e}"))
156 })
157 .next();
158
159 let last_modified = if let Some(last_modified) = last_modified {
160 chrono::DateTime::parse_from_rfc3339(&last_modified)
161 .expect("Valid chrono::DateTime")
162 .to_utc()
163 } else {
164 chrono::DateTime::<chrono::Utc>::MIN_UTC
165 };
166
167 Ok(last_modified)
168 }
169}
170
171impl Ecs {
172 #[instrument(name = "fetch", level = "debug", skip_all)]
173 fn fetch<'a>(
174 &'a self,
175 sql_query: sea_query::SelectStatement,
176 ) -> Result<impl Iterator<Item = Entity<'a>> + 'a, Error> {
177 // let sql = query.sql_query().to_string(sea_query::SqliteQueryBuilder);
178 let sql = sql_query.to_string(sea_query::SqliteQueryBuilder);
179 debug!(sql);
180
181 let rows = {
182 let mut stmt = self.conn.prepare(&sql)?;
183 let rows = stmt
184 .query_map([], |row| row.get::<_, EntityId>("entity"))?
185 .map(|r| r.expect("Valid EntityId"));
186 rows.collect::<Vec<_>>()
187 };
188
189 debug!(count = rows.len());
190
191 Ok(rows
192 .into_iter()
193 .scan(self, |ecs, eid| Some(Entity::with_id(&ecs, eid))))
194 }
195}
196
197#[derive(Debug, PartialEq, Eq)]
198pub enum Change {
199 Create { entity: EntityId },
200 Attach { entity: EntityId, component: String },
201 Detach { entity: EntityId, component: String },
202 Destroy { entity: EntityId },
203}
204
205impl Ecs {
206 pub fn latest_change_id(&self) -> Result<Option<i64>, Error> {
207 let seq: Option<i64> = self.conn.query_row_and_then(
208 "select max(sequence) from changes",
209 params![],
210 |row| row.get(0),
211 )?;
212
213 Ok(seq)
214 }
215
216 pub fn clear_changes_up_to(&self, up_to: i64) -> Result<(), Error> {
217 self.conn
218 .execute("delete from changes where sequence < ?1", params![up_to])?;
219 Ok(())
220 }
221
222 pub fn changes(&self) -> Result<Vec<Change>, Error> {
223 let mut stmt = self
224 .conn
225 .prepare_cached("select * from changes order by sequence asc")?;
226
227 let changes = stmt
228 .query_map(params![], |row| {
229 let entity = row.get("entity")?;
230 let change: String = row.get("change")?;
231
232 match change.as_str() {
233 "create" => Ok(Change::Create { entity }),
234 "attach" => {
235 let component = row.get("component")?;
236 Ok(Change::Attach { entity, component })
237 }
238 "detach" => {
239 let component = row.get("component")?;
240 Ok(Change::Detach { entity, component })
241 }
242 "destroy" => Ok(Change::Destroy { entity }),
243 other => {
244 panic!("Invalid 'changes.change' {other:?}");
245 }
246 }
247 })?
248 .collect::<Result<_, _>>()?;
249
250 Ok(changes)
251 }
252
253 pub fn clear_changes(&self) -> Result<(), Error> {
254 self.conn.execute("delete from changes", params![])?;
255 Ok(())
256 }
257}
258
259pub mod rusqlite {
260 pub use rusqlite::*;
261}
262
263impl Ecs {
264 pub fn raw_sql<'a>(&'a self) -> &'a rusqlite::Connection {
265 &self.conn
266 }
267}
268
269mod sql {
270 #[allow(unused)]
271 pub enum Components {
272 Table,
273 Entity,
274 Component,
275 Data,
276 }
277
278 impl sea_query::Iden for Components {
279 fn unquoted(&self, s: &mut dyn std::fmt::Write) {
280 let v = match self {
281 Self::Table => "components",
282 Self::Entity => "entity",
283 Self::Component => "component",
284 Self::Data => "data",
285 };
286 write!(s, "{v}").unwrap()
287 }
288 }
289
290 #[allow(unused)]
291 pub enum Changes {
292 Table,
293 Sequence,
294 Entity,
295 Component,
296 Change,
297 }
298
299 impl sea_query::Iden for Changes {
300 fn unquoted(&self, s: &mut dyn std::fmt::Write) {
301 let v = match self {
302 Self::Table => "changes",
303 Self::Sequence => "sequence",
304 Self::Entity => "entity",
305 Self::Component => "component",
306 Self::Change => "change",
307 };
308 write!(s, "{v}").unwrap()
309 }
310 }
311}
312
313#[cfg(test)]
314mod tests {
315 // #[derive(Component)] derives `impl ecsdb::Component for ...`
316 use crate::{self as ecsdb, Change, Ecs};
317 use crate::{BelongsTo, Component};
318
319 use anyhow::anyhow;
320 use serde::{Deserialize, Serialize};
321
322 #[derive(Debug, Serialize, Deserialize, Component)]
323 struct MarkerComponent;
324
325 #[derive(Debug, Serialize, Deserialize, PartialEq, Component)]
326 struct ComponentWithData(u64);
327
328 #[derive(Debug, Serialize, Deserialize, Component)]
329 struct A;
330
331 #[derive(Debug, Serialize, Deserialize, PartialEq, Component)]
332 struct B;
333
334 #[derive(Debug, Serialize, Deserialize, PartialEq, Component)]
335 struct C;
336
337 #[test]
338 fn derive_valid_component_name() {
339 assert_eq!(
340 MarkerComponent::component_name(),
341 "ecsdb::tests::MarkerComponent"
342 );
343 assert_eq!(
344 ComponentWithData::component_name(),
345 "ecsdb::tests::ComponentWithData"
346 );
347 }
348
349 #[test]
350 fn entity_attach_detach() {
351 let db = super::Ecs::open_in_memory().unwrap();
352 let entity = db
353 .new_entity()
354 .attach(ComponentWithData(1234))
355 .attach(MarkerComponent);
356
357 assert!(entity.component::<MarkerComponent>().is_some());
358 entity.detach::<MarkerComponent>();
359 assert!(entity.component::<MarkerComponent>().is_none());
360
361 assert_eq!(
362 entity.component::<ComponentWithData>(),
363 Some(ComponentWithData(1234))
364 );
365 }
366
367 #[test]
368 fn component_overwrites() {
369 let db = super::Ecs::open_in_memory().unwrap();
370
371 let entity = db
372 .new_entity()
373 .attach(ComponentWithData(42))
374 .attach(ComponentWithData(23));
375 assert_eq!(entity.component::<ComponentWithData>().unwrap().0, 23);
376 }
377
378 #[test]
379 fn attaching_same_component_in_bundle_overwrites() {
380 let db = super::Ecs::open_in_memory().unwrap();
381
382 let entity = db
383 .new_entity()
384 .attach((ComponentWithData(42), ComponentWithData(23)));
385 assert_eq!(entity.component::<ComponentWithData>().unwrap().0, 23);
386 }
387
388 use super::query::*;
389
390 #[test]
391 fn queries() {
392 let db = super::Ecs::open_in_memory().unwrap();
393 let _ = db.query::<MarkerComponent>();
394 let _ = db.query::<Without<(MarkerComponent, MarkerComponent)>>();
395 let _ = db.query::<(
396 MarkerComponent,
397 Or<(
398 Without<(MarkerComponent, MarkerComponent)>,
399 (MarkerComponent, MarkerComponent),
400 Or<(MarkerComponent, Without<MarkerComponent>)>,
401 )>,
402 )>();
403 let _ = db.query::<(
404 MarkerComponent,
405 ComponentWithData,
406 Without<(MarkerComponent, MarkerComponent)>,
407 )>();
408 let _ = db.query::<(MarkerComponent, Without<ComponentWithData>)>();
409 let _ = db.query::<(MarkerComponent, Without<ComponentWithData>)>();
410 let _ = db.query::<(
411 MarkerComponent,
412 MarkerComponent,
413 MarkerComponent,
414 MarkerComponent,
415 MarkerComponent,
416 MarkerComponent,
417 MarkerComponent,
418 MarkerComponent,
419 )>();
420 }
421
422 #[test]
423 fn query() {
424 let db = super::Ecs::open_in_memory().unwrap();
425
426 db.new_entity()
427 .attach(MarkerComponent)
428 .attach(ComponentWithData(1234));
429
430 db.new_entity().attach(ComponentWithData(1234));
431
432 assert_eq!(db.query::<()>().count(), 2);
433 assert_eq!(db.query::<MarkerComponent>().count(), 1);
434 assert_eq!(db.query::<MarkerComponent>().count(), 1);
435 assert_eq!(db.query::<Without<MarkerComponent>>().count(), 1);
436 assert_eq!(
437 db.query::<(MarkerComponent, ComponentWithData)>().count(),
438 1
439 );
440 assert_eq!(
441 db.query::<(MarkerComponent, Without<MarkerComponent>)>()
442 .count(),
443 0
444 );
445 assert_eq!(
446 db.query::<(
447 MarkerComponent,
448 Without<MarkerComponent>,
449 Or<(MarkerComponent, ComponentWithData)>
450 )>()
451 .count(),
452 0
453 );
454 assert_eq!(db.query::<ComponentWithData>().count(), 2);
455 }
456
457 #[test]
458 fn or() {
459 let db = Ecs::open_in_memory().unwrap();
460 let a = db.new_entity().attach(A).id();
461 let b = db.new_entity().attach(B).id();
462 let c = db.new_entity().attach(C).id();
463
464 assert_eq!(
465 db.query::<Or<(A, B, C)>>()
466 .map(|e| e.id())
467 .collect::<Vec<_>>(),
468 vec![a, b, c]
469 );
470 assert_eq!(
471 db.query::<Or<(A, B)>>().map(|e| e.id()).collect::<Vec<_>>(),
472 vec![a, b]
473 );
474 assert_eq!(
475 db.query::<Or<(A,)>>().map(|e| e.id()).collect::<Vec<_>>(),
476 vec![a]
477 );
478 assert_eq!(
479 db.query::<Or<(B,)>>().map(|e| e.id()).collect::<Vec<_>>(),
480 vec![b]
481 );
482 }
483
484 #[test]
485 fn find() {
486 let db = Ecs::open_in_memory().unwrap();
487 let eid = db.new_entity().attach(ComponentWithData(123)).id();
488 let _ = db.new_entity().attach(ComponentWithData(123));
489 let _ = db.new_entity().attach(ComponentWithData(255));
490
491 assert_eq!(db.find(eid).count(), 1);
492 assert_eq!(db.find(eid).next().unwrap().id(), eid);
493 assert_eq!(db.find((eid, MarkerComponent)).count(), 0);
494 assert_eq!(db.find(MarkerComponent).count(), 0);
495 assert_eq!(db.find(ComponentWithData(0)).count(), 0);
496 assert_eq!(db.find(ComponentWithData(123)).count(), 2);
497 assert_eq!(db.find(ComponentWithData(255)).count(), 1);
498
499 let _ = db
500 .new_entity()
501 .attach(MarkerComponent)
502 .attach(ComponentWithData(12345));
503 assert_eq!(
504 db.find((MarkerComponent, ComponentWithData(12345))).count(),
505 1
506 );
507 }
508
509 #[test]
510 fn parent() {
511 let db = Ecs::open_in_memory().unwrap();
512
513 let parent = db.new_entity().attach(A);
514 let child1 = db.new_entity().attach(A).attach(BelongsTo(parent.id()));
515 let child2 = db.new_entity().attach(A).attach(BelongsTo(child1.id()));
516
517 assert!(parent.parent().is_none());
518 assert_eq!(child1.parent().map(|e| e.id()), Some(parent.id()));
519 assert_eq!(child2.parent().map(|e| e.id()), Some(child1.id()));
520
521 assert_eq!(
522 child2.parents().map(|e| e.id()).collect::<Vec<_>>(),
523 vec![child1.id(), parent.id()]
524 );
525 }
526
527 #[test]
528 fn enum_component() {
529 #[derive(Serialize, Deserialize, Component, PartialEq, Debug)]
530 enum Foo {
531 A,
532 B,
533 }
534
535 let db = Ecs::open_in_memory().unwrap();
536 let entity = db.new_entity().attach(Foo::A);
537 assert_eq!(entity.component::<Foo>().unwrap(), Foo::A);
538 }
539
540 #[test]
541 fn blob_component() {
542 #[derive(Component, Debug, PartialEq, Clone)]
543 #[component(storage = "blob")]
544 struct X(Vec<u8>);
545
546 let x = X(b"asdfasdf".into());
547
548 let db = Ecs::open_in_memory().unwrap();
549 let entity = db.new_entity().attach(x.clone());
550
551 assert_eq!(entity.component::<X>().unwrap(), x.clone());
552 assert_eq!(db.find(x.clone()).next().unwrap().id(), entity.id());
553 }
554
555 #[test]
556 fn has_many() {
557 let db = Ecs::open_in_memory().unwrap();
558 let a = db.new_entity().attach(A);
559 assert!(a.has::<A>());
560 assert!(!a.has::<B>());
561
562 assert_eq!(a.has::<(A,)>(), true);
563 assert_eq!(a.has::<(A, B)>(), false);
564 assert_eq!(a.has::<(A, B, A)>(), false);
565
566 let ab = db.new_entity().attach(A).attach(B);
567 assert_eq!(ab.has::<(A, B)>(), true);
568 assert_eq!(ab.has::<(A, A)>(), true);
569 assert_eq!(ab.has::<(A, B, A)>(), true);
570 }
571
572 #[test]
573 fn from_component_composite() -> Result<(), anyhow::Error> {
574 #[derive(Serialize, Deserialize, Component)]
575 struct A;
576 #[derive(Serialize, Deserialize, Component)]
577 struct B;
578
579 let db = super::Ecs::open_in_memory()?;
580 let _e = db.new_entity().attach((A, B));
581
582 // let ab = e.component::<(A, B)>();
583 // assert!(ab.is_some());
584
585 Ok(())
586 }
587
588 #[test]
589 fn entity_matches() {
590 #[derive(Serialize, Deserialize, Component)]
591 struct A;
592 #[derive(Serialize, Deserialize, Component)]
593 struct B;
594
595 let db = super::Ecs::open_in_memory().unwrap();
596 let e = db.new_entity().attach(A);
597 let e2 = db.new_entity().attach((A, B));
598
599 assert!(e.matches::<A>());
600 assert!(!e.matches::<B>());
601 assert!(!e.matches::<(A, B)>());
602
603 assert!(e2.matches::<A>());
604 assert!(e2.matches::<B>());
605 assert!(e2.matches::<(A, B)>());
606 }
607
608 #[test]
609 fn last_modified() {
610 #[derive(Serialize, Deserialize, Component)]
611 struct A;
612 #[derive(Serialize, Deserialize, Component)]
613 struct B;
614
615 let db = super::Ecs::open_in_memory().unwrap();
616 let e = db.new_entity().attach(A);
617
618 assert!(e.last_modified() > chrono::Utc::now() - chrono::Duration::minutes(1));
619
620 let old = e.last_modified();
621
622 std::thread::sleep(std::time::Duration::from_millis(2));
623
624 e.attach(B);
625 assert!(e.last_modified() > old);
626 }
627
628 #[test]
629 fn modify_component() -> Result<(), anyhow::Error> {
630 let ecs = super::Ecs::open_in_memory()?;
631
632 #[derive(Component, Debug, Default, Deserialize, Serialize, PartialEq)]
633 struct Foo(Vec<u64>);
634
635 let entity = ecs.new_entity().modify_component(|foo: &mut Foo| {
636 *foo = Foo(vec![1, 2, 3]);
637 });
638 assert_eq!(entity.component(), Some(Foo(vec![1, 2, 3])));
639
640 let entity = ecs
641 .new_entity()
642 .attach(Foo(vec![1, 2, 3]))
643 .modify_component(|foo: &mut Foo| {
644 foo.0.clear();
645 });
646
647 assert_eq!(entity.component(), Some(Foo(vec![])));
648
649 Ok(())
650 }
651
652 #[test]
653 fn try_modify_component() -> Result<(), anyhow::Error> {
654 let ecs = super::Ecs::open_in_memory()?;
655
656 #[derive(Component, Debug, Default, Deserialize, Serialize, PartialEq)]
657 struct Foo(Vec<u64>);
658
659 assert!(ecs
660 .new_entity()
661 .try_modify_component(|_foo: &mut Foo| { Err(anyhow!("error")) })
662 .is_err());
663
664 assert!(ecs
665 .new_entity()
666 .attach(Foo(vec![1, 2, 3]))
667 .try_modify_component(|_foo: &mut Foo| { Err(anyhow!("error")) })
668 .is_err());
669
670 Ok(())
671 }
672
673 #[test]
674 fn change_tracking_enable_disable() -> Result<(), anyhow::Error> {
675 let mut ecs = super::Ecs::open_in_memory()?;
676 ecs.enable_change_tracking()?;
677
678 assert_eq!(ecs.changes()?, vec![]);
679
680 ecs.new_entity().attach(A);
681 assert_eq!(ecs.changes()?.len(), 2);
682
683 ecs.disable_change_tracking()?;
684 ecs.clear_changes()?;
685
686 ecs.new_entity().attach(A);
687 assert!(ecs.changes()?.is_empty());
688
689 Ok(())
690 }
691
692 #[test]
693 fn change_tracking() -> Result<(), anyhow::Error> {
694 let mut ecs = super::Ecs::open_in_memory()?;
695 ecs.enable_change_tracking()?;
696
697 let mut changes = vec![];
698
699 assert_eq!(ecs.changes()?, vec![]);
700
701 let entity = ecs.new_entity().attach(A);
702
703 changes.extend([
704 Change::Create {
705 entity: entity.id(),
706 },
707 Change::Attach {
708 entity: entity.id(),
709 component: <A as Component>::component_name().to_owned(),
710 },
711 ]);
712
713 assert_eq!(ecs.changes()?, changes);
714
715 entity.attach(B);
716 changes.push(Change::Attach {
717 entity: entity.id(),
718 component: <B as Component>::component_name().to_owned(),
719 });
720
721 assert_eq!(ecs.changes()?, changes);
722
723 entity.detach::<B>();
724 changes.push(Change::Detach {
725 entity: entity.id(),
726 component: <B as Component>::component_name().to_owned(),
727 });
728
729 assert_eq!(ecs.changes()?, changes);
730
731 entity.detach::<A>();
732 changes.extend([
733 Change::Detach {
734 entity: entity.id(),
735 component: <A as Component>::component_name().to_owned(),
736 },
737 Change::Destroy {
738 entity: entity.id(),
739 },
740 ]);
741
742 assert_eq!(ecs.changes()?, changes);
743
744 ecs.clear_changes()?;
745 assert!(ecs.changes()?.is_empty());
746
747 Ok(())
748 }
749
750 #[test]
751 fn changed_system_param() -> Result<(), anyhow::Error> {
752 #[derive(Debug, Deserialize, Serialize, Component)]
753 struct Seen;
754
755 let mut ecs = super::Ecs::open_in_memory()?;
756 ecs.enable_change_tracking()?;
757
758 fn system(query: Query<Attached<B>>) {
759 assert_eq!(query.iter().map(|e| e.id()).collect::<Vec<_>>(), vec![200]);
760
761 query.iter().for_each(|e| {
762 e.attach(Seen);
763 });
764 }
765
766 ecs.register(system);
767
768 ecs.entity(100).attach(A);
769 ecs.entity(200).attach(B);
770
771 ecs.tick();
772 assert!(ecs.entity(200).has::<Seen>());
773
774 Ok(())
775 }
776}