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}