pub mod component; pub use component::Bundle; pub use component::{Component, ComponentRead, ComponentWrite}; pub mod dyn_component; pub use dyn_component::DynComponent; pub mod entity; pub use entity::{Entity, NewEntity}; pub mod extension; pub use extension::Extension; pub mod hierarchy; pub mod query; pub mod resource; pub use resource::*; pub mod schedule; pub use schedule::Schedule; pub mod sqlite_ext; pub mod system; pub mod rusqlite { pub use rusqlite::*; } use self_cell::MutBorrow; use serde::{Deserialize, Serialize}; pub use system::*; mod tuple_macros; use std::borrow::Cow; use std::path::Path; use tracing::{debug, instrument}; use crate::query::QueryFilterValue; pub type EntityId = i64; #[derive(Debug, thiserror::Error)] pub enum Error { #[error("Database Error: {0}")] Database(#[from] rusqlite::Error), #[error(transparent)] ComponentStorage(#[from] component::StorageError), } pub struct Ecs { conn: rusqlite::Connection, extensions: anymap::Map, } impl Ecs { pub fn open_in_memory() -> Result { Self::from_rusqlite(rusqlite::Connection::open_in_memory()?) } pub fn open(path: impl AsRef) -> Result { Self::from_rusqlite(rusqlite::Connection::open(path)?) } pub fn from_rusqlite(mut conn: rusqlite::Connection) -> Result { conn.pragma_update(None, "journal_mode", "wal")?; conn.execute_batch(include_str!("schema.sql"))?; conn.set_transaction_behavior(::rusqlite::TransactionBehavior::Immediate); sqlite_ext::add_regexp_function(&conn)?; Ok(Self { conn, extensions: anymap::Map::new(), }) } } impl Ecs { pub fn close(self) -> Result<(), Error> { self.conn.close().map_err(|(_conn, e)| Error::Database(e)) } pub fn data_version(&self) -> Result { Ok(self .conn .query_row("select data_version from pragma_data_version", [], |x| { x.get("data_version") })?) } } impl Ecs { pub fn new_entity<'a>(&'a self) -> NewEntity<'a> { Entity::without_id(self) } pub fn entity<'a>(&'a self, eid: EntityId) -> Entity<'a> { Entity::with_id(self, eid) } } impl Ecs { pub fn entity_with<'a, B: Bundle>(&'a self, eid: EntityId) -> Option> { let e = Entity::with_id(self, eid); e.has::().then_some(e) } } impl Ecs { pub fn query<'a, D, F>(&'a self) -> impl Iterator> + 'a where D: query::QueryData + 'a, F: query::QueryFilter + 'a, { self.try_query::().unwrap() } #[instrument(name = "query", level = "debug", skip_all)] pub fn try_query<'a, D, F>(&'a self) -> Result> + 'a, Error> where D: query::QueryData + 'a, F: query::QueryFilter + 'a, { debug!( data = std::any::type_name::(), filter = std::any::type_name::() ); let query = query::Query::::new(self); query.try_iter() } pub fn query_filtered<'a, D, F>( &'a self, filter: impl QueryFilterValue + 'a, ) -> impl Iterator> + 'a where D: query::QueryData + 'a, F: query::QueryFilter + 'a, { self.try_query_fitered::(filter).unwrap() } #[instrument(name = "find", level = "debug", skip_all)] pub fn try_query_fitered<'a, D, F>( &'a self, filter_value: impl query::QueryFilterValue + 'a, ) -> Result> + 'a, Error> where D: query::QueryData + 'a, F: query::QueryFilter + 'a, { let query = query::Query::::with_filter(self, filter_value); query.try_iter() } } impl Ecs { pub fn find<'a>( &'a self, filter_value: impl query::QueryFilterValue + 'a, ) -> impl Iterator> + 'a { self.try_find(filter_value).unwrap() } pub fn try_find<'a>( &'a self, filter_value: impl query::QueryFilterValue + 'a, ) -> Result> + 'a, Error> { self.try_query_fitered::, ()>(filter_value) } } #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct CreatedAt(pub chrono::DateTime); #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct LastUpdated(pub chrono::DateTime); impl Ecs { #[instrument(level = "debug", skip_all)] fn fetch<'a, Q: query::QueryData + 'a>( &'a self, sql_query: query::ir::Query, ) -> Result> + 'a, Error> { let entity_ids = self.fetch_entity_ids(sql_query)?; let rows = entity_ids .into_iter() .scan(self, |ecs, eid| Some(Entity::with_id(ecs, eid))) .map(|e| { debug!( data = std::any::type_name::(), entity = ?e, "Fetching QueryData" ); Q::from_entity(e).unwrap() }); Ok(rows) } fn fetch_entity_ids<'a>(&'a self, sql_query: query::ir::Query) -> Result, Error> { let (sql, placeholders) = sql_query.into_sql(); debug!(sql); let mut stmt = self.conn.prepare(&sql)?; let params: Box<[(&str, &dyn rusqlite::ToSql)]> = placeholders .iter() .map(|(p, v)| (p.as_str(), v.as_ref())) .collect(); let rows = stmt .query_map(¶ms[..], |row| row.get("entity"))? .map(|r| r.expect("EntityId from Query")) .collect(); Ok(rows) } } #[allow(unused)] impl Ecs { #[instrument(level = "debug", skip_all)] fn fetch_lazy<'a, Q: query::QueryData + 'a>( &'a self, sql_query: query::ir::Query, ) -> Result> + 'a, Error> { let rows = self .fetch_entity_ids_lazy(sql_query)? .scan(self, |ecs, eid| Some(Entity::with_id(ecs, eid))) .map(|e| { debug!( data = std::any::type_name::(), entity = ?e, "Fetching QueryData" ); Q::from_entity(e).unwrap() }); Ok(rows) } fn fetch_entity_ids_lazy<'a>( &'a self, sql_query: query::ir::Query, ) -> Result + 'a, Error> { let (sql, placeholders) = sql_query.into_sql(); debug!(sql); type RowsRef<'a> = ::rusqlite::Rows<'a>; self_cell::self_cell!( struct OwningRows<'conn> { owner: self_cell::MutBorrow<::rusqlite::Statement<'conn>>, #[covariant] dependent: RowsRef, } ); impl<'conn> Iterator for OwningRows<'conn> { type Item = Result; fn next(&mut self) -> Option { match self.with_dependent_mut(|_stmt, rows| rows.next()) { Ok(Some(row)) => Some(row.get("entity")), Ok(None) => None, Err(e) => Some(Err(e)), } } } let stmt = self.conn.prepare(&sql)?; let params: Box<[(&str, &dyn rusqlite::ToSql)]> = placeholders .iter() .map(|(p, v)| (p.as_str(), v.as_ref())) .collect(); let owning_rows = OwningRows::try_new(MutBorrow::new(stmt), |s| s.borrow_mut().query(¶ms[..])) .unwrap(); Ok(owning_rows.map(|result| result.expect("EntityId from Row"))) } } impl Ecs { pub fn raw_sql<'a>(&'a self) -> &'a rusqlite::Connection { &self.conn } } impl AsRef> for CreatedAt { fn as_ref(&self) -> &chrono::DateTime { &self.0 } } impl Component for CreatedAt { type Storage = component::JsonStorage; const NAME: &'static str = "ecsdb::CreatedAt"; } impl Default for CreatedAt { fn default() -> Self { Self(chrono::DateTime::::MIN_UTC) } } impl AsRef> for LastUpdated { fn as_ref(&self) -> &chrono::DateTime { &self.0 } } impl Component for LastUpdated { type Storage = component::JsonStorage; const NAME: &'static str = "ecsdb::LastUpdated"; } impl Default for LastUpdated { fn default() -> Self { Self(chrono::DateTime::::MIN_UTC) } } pub fn system_name>(system: S) -> Cow<'static, str> { system.into_system().name() } #[doc(hidden)] pub mod doctests { use crate as ecsdb; // Fixes `#[derive(Component)]` pub use crate::{Component, Ecs, Entity, EntityId}; pub use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Component)] pub struct Marker; #[derive(Serialize, Deserialize, Component)] pub struct Date(pub chrono::DateTime); #[derive(Serialize, Deserialize, Component)] pub enum State { New, Processing, Finished, } #[cfg(doctest)] #[doc = include_str!("../README.md")] pub struct ReadmeDoctests; } #[cfg(test)] mod tests { // #[derive(Component)] derives `impl ecsdb::Component for ...` use crate::{self as ecsdb, CreatedAt, Ecs, Entity, EntityId}; use crate::{Bundle, Component}; use anyhow::anyhow; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize, Component)] struct MarkerComponent; #[derive(Debug, Serialize, Deserialize, PartialEq, Component)] struct ComponentWithData(u64); #[derive(Debug, Serialize, Deserialize, Component)] struct A; #[derive(Debug, Serialize, Deserialize, PartialEq, Component)] struct B; #[derive(Debug, Serialize, Deserialize, PartialEq, Component)] struct C; #[test] fn derive_valid_component_name() { assert_eq!( MarkerComponent::component_name(), "ecsdb::tests::MarkerComponent" ); assert_eq!( ComponentWithData::component_name(), "ecsdb::tests::ComponentWithData" ); } #[test] fn entity_attach_detach() { let db = super::Ecs::open_in_memory().unwrap(); let entity = db .new_entity() .attach(ComponentWithData(1234)) .attach(MarkerComponent); assert!(entity.component::().is_some()); entity.detach::(); assert!(entity.component::().is_none()); assert_eq!( entity.component::(), Some(ComponentWithData(1234)) ); } #[test] fn component_overwrites() { let db = super::Ecs::open_in_memory().unwrap(); let entity = db .new_entity() .attach(ComponentWithData(42)) .attach(ComponentWithData(23)); assert_eq!(entity.component::().unwrap().0, 23); } #[test] fn attaching_same_component_in_bundle_overwrites() { let db = super::Ecs::open_in_memory().unwrap(); let entity = db .new_entity() .attach((ComponentWithData(42), ComponentWithData(23))); assert_eq!(entity.component::().unwrap().0, 23); } #[test] fn bundle_struct() { let db = super::Ecs::open_in_memory().unwrap(); #[derive(Debug, Bundle)] struct Bundle { a: A, b: B, data: ComponentWithData, } let bundle = Bundle { a: A, b: B, data: ComponentWithData(1234), }; let entity = db.new_entity().attach(bundle); assert!(entity.has::<(A, B, ComponentWithData)>()); } #[test] fn bundle_tuplestruct() { let db = super::Ecs::open_in_memory().unwrap(); #[derive(Debug, Bundle)] struct Bundle(A, B, ComponentWithData); let bundle = Bundle(A, B, ComponentWithData(1234)); let entity = db.new_entity().attach(bundle); assert!(entity.has::<(A, B, ComponentWithData)>()); } #[test] fn bundle_optionals() { let db = super::Ecs::open_in_memory().unwrap(); #[derive(Debug, Bundle)] struct Bundle(A, Option); let entity = db.new_entity().attach(Bundle(A, None)); assert!(!entity.has::()); entity.attach(Bundle(A, Some(B))); assert!(entity.has::()); entity.attach(Bundle(A, None)); assert!(entity.has::()); } use super::query::*; #[test] fn query_tuples() { let db = super::Ecs::open_in_memory().unwrap(); let _: Vec = db.query::().collect(); let _: Vec = db.query::().collect(); let _: Vec<(Entity, MarkerComponent)> = db.query::<(Entity, MarkerComponent), ()>().collect(); let _: Vec = db.query::>().collect(); let _: Vec = db.query::>().collect(); let _: Vec = db .query::, With<(MarkerComponent, MarkerComponent)>, Or<(With, Without)>, )>>() .collect(); let _: Vec = db .query::, Without<(MarkerComponent, MarkerComponent)>, )>() .collect(); let _: Vec = db .query::>() .collect(); let _: Vec<()> = db .query::<(), ( With, With, With, With, With, With, Without, With, )>() .collect(); } #[test] fn query() { let db = super::Ecs::open_in_memory().unwrap(); db.new_entity() .attach(MarkerComponent) .attach(ComponentWithData(1234)); db.new_entity().attach(ComponentWithData(1234)); assert_eq!(db.query::().count(), 2); assert_eq!(db.query::().count(), 1); assert_eq!(db.query::().count(), 1); } #[test] fn entity_match_filtered() { let db = super::Ecs::open_in_memory().unwrap(); db.new_entity() .attach(MarkerComponent) .attach(ComponentWithData(1234)); db.new_entity().attach(ComponentWithData(1234)); assert_eq!(db.query::>().count(), 1); assert_eq!( db.query::() .count(), 1 ); assert_eq!( db.query::>() .count(), 0 ); assert_eq!( db.query::, Or<(With, With)> )>() .count(), 0 ); assert_eq!(db.query::().count(), 2); } #[test] fn or() { let db = Ecs::open_in_memory().unwrap(); let a = db.new_entity().attach(A).id(); let b = db.new_entity().attach(B).id(); let c = db.new_entity().attach(C).id(); assert_eq!( db.query::, With, With)>>() .collect::>(), vec![a, b, c] ); assert_eq!( db.query::, With)>>() .collect::>(), vec![a, b] ); assert_eq!( db.query::,)>>().collect::>(), vec![a] ); assert_eq!( db.query::,)>>().collect::>(), vec![b] ); } #[test] fn query_any_of() { let db = Ecs::open_in_memory().unwrap(); let a = db.new_entity().attach(A).id(); let b = db.new_entity().attach(B).id(); let c = db.new_entity().attach(C).id(); assert_eq!( db.query::>().collect::>(), vec![a, b] ); assert_eq!( db.query::>().collect::>(), vec![a, c] ); assert_eq!( db.query::>().collect::>(), vec![a] ); } #[test] fn query_filtered() { let db = Ecs::open_in_memory().unwrap(); let eid = db.new_entity().attach(ComponentWithData(123)).id(); let _ = db.new_entity().attach(ComponentWithData(123)); let _ = db.new_entity().attach(ComponentWithData(255)); assert_eq!(db.query_filtered::(eid).count(), 1); assert_eq!( db.query_filtered::(eid).collect::>(), vec![eid] ); assert_eq!( db.query_filtered::((eid, MarkerComponent)) .count(), 0 ); assert_eq!( db.query_filtered::(MarkerComponent).count(), 0 ); assert_eq!( db.query_filtered::(ComponentWithData(0)) .count(), 0 ); assert_eq!( db.query_filtered::(ComponentWithData(123)) .count(), 2 ); assert_eq!( db.query_filtered::(ComponentWithData(255)) .count(), 1 ); let _ = db .new_entity() .attach(MarkerComponent) .attach(ComponentWithData(12345)); assert_eq!( db.query_filtered::((MarkerComponent, ComponentWithData(12345))) .count(), 1 ); } #[test] fn find_ranges() { let db = Ecs::open_in_memory().unwrap(); for n in 0..100 { let _ = db.new_entity().attach(ComponentWithData(n)); } let mut res = db .query_filtered::(ComponentWithData(0)..ComponentWithData(10)) .map(|e| e.component::().unwrap()); assert_eq!(res.next(), Some(ComponentWithData(0))); assert_eq!(res.last(), Some(ComponentWithData(10))); let mut res = db .query_filtered::(ComponentWithData(10)..) .map(|e| e.component::().unwrap()); assert_eq!(res.next(), Some(ComponentWithData(10))); assert_eq!(res.last(), Some(ComponentWithData(99))); let mut res = db .query_filtered::(..ComponentWithData(10)) .map(|e| e.component::().unwrap()); assert_eq!(res.next(), Some(ComponentWithData(0))); assert_eq!(res.last(), Some(ComponentWithData(10))); } // #[test] // fn parent() { // let db = Ecs::open_in_memory().unwrap(); // let parent = db.new_entity().attach(A); // let child1 = db.new_entity().attach(A).attach(BelongsTo(parent.id())); // let child2 = db.new_entity().attach(A).attach(BelongsTo(child1.id())); // assert!(parent.parent().is_none()); // assert_eq!(child1.parent().map(|e| e.id()), Some(parent.id())); // assert_eq!(child2.parent().map(|e| e.id()), Some(child1.id())); // assert_eq!( // child2.parents().map(|e| e.id()).collect::>(), // vec![child1.id(), parent.id()] // ); // } #[test] fn enum_component() { #[derive(Serialize, Deserialize, Component, PartialEq, Debug)] enum Foo { A, B, } let db = Ecs::open_in_memory().unwrap(); let entity = db.new_entity().attach(Foo::A); assert_eq!(entity.component::().unwrap(), Foo::A); } #[test] fn blob_component() { #[derive(Component, Debug, PartialEq, Clone)] #[component(storage = "blob")] struct X(Vec); impl AsRef<[u8]> for X { fn as_ref(&self) -> &[u8] { self.0.as_slice() } } impl From> for X { fn from(value: Vec) -> Self { Self(value) } } let x = X(b"asdfasdf".into()); let db = Ecs::open_in_memory().unwrap(); let entity = db.new_entity().attach(x.clone()); assert_eq!(entity.component::().unwrap(), x.clone()); } #[test] fn has_many() { let db = Ecs::open_in_memory().unwrap(); let a = db.new_entity().attach(A); assert!(a.has::()); assert!(!a.has::()); assert!(a.has::<(A,)>()); assert!(!a.has::<(A, B)>()); assert!(!a.has::<(A, B, A)>()); let ab = db.new_entity().attach(A).attach(B); assert!(ab.has::<(A, B)>()); assert!(ab.has::<(A, A)>()); assert!(ab.has::<(A, B, A)>()); } #[test] fn from_component_composite() -> Result<(), anyhow::Error> { #[derive(Serialize, Deserialize, Component)] struct A; #[derive(Serialize, Deserialize, Component)] struct B; let db = super::Ecs::open_in_memory()?; let _e = db.new_entity().attach((A, B)); // let ab = e.component::<(A, B)>(); // assert!(ab.is_some()); Ok(()) } #[test] fn entity_matches() { #[derive(Serialize, Deserialize, Component)] struct A; #[derive(Serialize, Deserialize, Component)] struct B; let db = super::Ecs::open_in_memory().unwrap(); let e = db.new_entity().attach(A); let e2 = db.new_entity().attach((A, B)); assert!(e.matches::>()); assert!(!e.matches::>()); assert!(!e.matches::>()); assert!(e2.matches::>()); assert!(e2.matches::>()); assert!(e2.matches::>()); } // #[test] // fn entity_matches_filtered() { // #[derive(Serialize, Deserialize, Component)] // struct A; // #[derive(Serialize, Deserialize, Component)] // struct B; // let db = super::Ecs::open_in_memory().unwrap(); // let e = db.new_entity().attach(A); // let e2 = db.new_entity().attach((A, B)); // assert!(e.matches_filtered((With::::default(), e.id()))); // assert!(!e.matches::>()); // assert!(!e.matches::>()); // assert!(e2.matches::>()); // assert!(e2.matches::>()); // assert!(e2.matches::>()); // } #[test] fn last_modified() { #[derive(Serialize, Deserialize, Component)] struct A; #[derive(Serialize, Deserialize, Component)] struct B; let db = super::Ecs::open_in_memory().unwrap(); let e = db.new_entity().attach(A); assert!(e.last_modified() > chrono::Utc::now() - chrono::Duration::minutes(1)); let old = e.last_modified(); std::thread::sleep(std::time::Duration::from_millis(2)); e.attach(B); assert!(e.last_modified() > old); } #[test] fn created() { #[derive(Serialize, Deserialize, Component)] struct A; #[derive(Serialize, Deserialize, Component)] struct B; let db = super::Ecs::open_in_memory().unwrap(); let e = db.new_entity().attach(A); assert!(e.has::()); assert_ne!(e.created_at(), chrono::DateTime::::MIN_UTC); assert!(e.created_at() > chrono::Utc::now() - chrono::Duration::minutes(1),); let old = e.created_at(); std::thread::sleep(std::time::Duration::from_millis(2)); e.attach(B); assert_eq!(e.created_at(), old); } #[test] fn modify_component() -> Result<(), anyhow::Error> { let ecs = super::Ecs::open_in_memory()?; #[derive(Component, Debug, Default, Deserialize, Serialize, PartialEq)] struct Foo(Vec); let entity = ecs.new_entity().modify_component(|foo: &mut Foo| { *foo = Foo(vec![1, 2, 3]); }); assert_eq!(entity.component(), Some(Foo(vec![1, 2, 3]))); let entity = ecs .new_entity() .attach(Foo(vec![1, 2, 3])) .modify_component(|foo: &mut Foo| { foo.0.clear(); }); assert_eq!(entity.component(), Some(Foo(vec![]))); Ok(()) } #[test] fn try_modify_component() -> Result<(), anyhow::Error> { let ecs = super::Ecs::open_in_memory()?; #[derive(Component, Debug, Default, Deserialize, Serialize, PartialEq)] struct Foo(Vec); assert!( ecs.new_entity() .try_modify_component(|_foo: &mut Foo| { Err(anyhow!("error")) }) .is_err() ); assert!( ecs.new_entity() .attach(Foo(vec![1, 2, 3])) .try_modify_component(|_foo: &mut Foo| { Err(anyhow!("error")) }) .is_err() ); Ok(()) } }