Experiments in applying Entity-Component-System patterns to durable data storage APIs.
use ecsdb::*;
use ecsdb::query::*;
use serde::{Serialize, Deserialize};
#[derive(Debug, Component, Serialize, Deserialize)]
struct Headline(String);
#[derive(Debug, Component, Serialize, Deserialize)]
struct Date(String);
let ecs = Ecs::open_in_memory().unwrap();
ecs.new_entity()
.attach(Headline("My Note".into()))
.attach(Date(chrono::Utc::now().to_rfc3339()));
ecs.new_entity().attach(Headline("My Note".into()));
for (entity, headline) in ecs.query::<(Entity, Headline), Without<Date>>().into_iter() {
println!(
"Entity '{}' (id={}) is missing component 'Date'",
headline.0,
entity.id()
);
entity.destroy();
}A component is a singular piece of data, similar to a column in a relational database.
They must implement serde::Serialize, serde::Deserialize and
ecsdb::Component, all of which can be #[derive]'d.
# use serde::{Serialize, Deserialize};
# use ecsdb::Component;
#[derive(Serialize, Deserialize, Component)]
pub struct Marker;
#[derive(Serialize, Deserialize, Component)]
pub struct Date(chrono::DateTime<chrono::Utc>);
#[derive(Serialize, Deserialize, Component)]
pub enum State {
New,
Processing,
Finished
}# use ecsdb::{Component, Ecs, query::*};
# use serde::{Serialize, Deserialize};
# use ecsdb::doctests::*;
# let ecs = Ecs::open_in_memory().unwrap();
// Attach components via `Entity::attach`:
let entity = ecs.new_entity()
.attach(State::New);
// To retrieve an attached component, use `Entity::component`:
let date: Option<Date> = entity.component::<Date>();
// To detach a component, use `Entity::detach`. Detaching a non-attached component is a no-op:
entity.detach::<Date>();
// Re-attaching a component of the same type overwrites the old. Attaching the
// same value is a no-op:
entity.attach(State::Finished);Systems are functions operating on an Ecs. They can be registerd via
Ecs::register and run via Ecs::tick. They can take a set of 'magic'
parameters to access data in the Ecs:
# use ecsdb::doctests::*;
use ecsdb::query::{Query, With, Without};
// This system will attach `State::New` to all entities that have a `Marker` but
// no `State` component
fn process_marked_system(marked_entities: Query<Entity, (With<Marker>, Without<State>)>) {
for entity in marked_entities.iter() {
entity
.attach(State::New)
.detach::<Marker>();
}
}
// This system logs all entities that have both `Date` and `Marker` but no
// `State`
fn log_system(entities: Query<(EntityId, Date, Marker), Without<State>>) {
for (entity_id, Date(date), _marker) in entities.iter() {
println!("{entity_id} {date}");
}
}
let ecs = Ecs::open_in_memory().unwrap();
ecs.run_system(process_marked_system).unwrap();
ecs.run_system(log_system).unwrap();ecsdb::Schedule allows scheduling of different systems by different criterias:
# use ecsdb::doctests::*;
# let ecs = Ecs::open_in_memory().unwrap();
fn sys_a() {}
fn sys_b() {}
use ecsdb::schedule::*;
let mut schedule = Schedule::new();
// Run `sys_a` every 15 minutes
schedule.add(sys_a, Every(chrono::Duration::minutes(15)));
// Run `sys_b` after `sys_a`
schedule.add(sys_b, After::system(sys_a));
// Run all pending systems
schedule.tick(&ecs);schedule::Every(Duration)runs a system periodicallyschedule::Afterruns one system after another finishedschedule::Onceruns a system once per databaseschedule::Alwaysruns a system on everySchedule::tick