Skip to content

the-kenny/ecsdb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ecsdb

Experiments in applying Entity-Component-System patterns to durable data storage APIs.

Usage

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();
}

Components

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
}

Entities

# 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

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();

Scheduling

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 periodically
  • schedule::After runs one system after another finished
  • schedule::Once runs a system once per database
  • schedule::Always runs a system on every Schedule::tick

About

Experiment with a different kind of database API

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published