Skip to content

ecoricemon/my-ecs

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

my-ecs

Crates.io CI Status Codecov

my-ecs is an Entity Component System library for Rust with parallel system execution, built-in async task integration, and wasm32 support.

What It Provides

  • Parallel scheduling by default. Systems can run across multiple worker threads while my-ecs enforces data access rules for components, entities, and resources.
  • Explicit data access. Systems declare what they read or write, and the scheduler uses that information to order execution safely.
  • Async integration. Systems can enqueue futures through the built-in posting/execution flow, which makes it practical to mix ECS logic with async work.
  • Native and web targets. The crate supports native workers and includes web-worker support for wasm32-unknown-unknown.

Core Concepts

  • Component: plain data attached to entities.
  • Entity: a collection of components.
  • System: logic that reads or writes ECS data.
  • Resource: unique global data stored once in the world.
  • Filter / Select: type-level queries created with the filter! macro.

Architecture

my-ecs overview

Demo

my-ecs-demo-small.mp4

my-ecs demo video

Rendering the Mandelbrot set with multiple threads using my-ecs

Try the demo here

Quick Start

Add the crate:

[dependencies]
my-ecs = "0.1"

Define components with ordinary Rust types:

use my_ecs::prelude::*;

#[derive(Component)]
struct Position {
    x: u32,
    y: u32,
}

Define an entity as a named bundle of components:

use my_ecs::prelude::*;

#[derive(Component)]
struct Position {
    x: u32,
    y: u32,
}

#[derive(Component)]
struct Movable;

#[derive(Entity)]
struct MovableObject {
    pos: Position,
    movable: Movable,
}

Define a selector with filter!, then read or write matching components from a system:

use my_ecs::prelude::*;

#[derive(Component)]
struct Position {
    x: u32,
    y: u32,
}

filter!(Fpos, Target = Position);

fn moves(mut w: Write<Fpos>) {
    for Position { x, y } in w.iter_mut().flatten() {
        *x += 10;
        *y += 10;
    }
}

Resources are singletons:

use my_ecs::prelude::*;

#[derive(Resource)]
struct Count(u32);

Ecs::create(WorkerPool::new(), [])
    .add_resource(Count(0))
    .add_once_systems((
        |rw: ResWrite<Count>| rw.take().0 += 1,
        |rr: ResRead<Count>| println!("{}", rr.take().0),
    ))
    .step();

Hello World Example

This example registers two entity types, creates data, mutates one subset, then prints the result.

use my_ecs::prelude::*;

#[derive(Component)]
struct Position {
    x: u32,
    y: u32,
}

#[derive(Component)]
struct Movable;

#[derive(Entity)]
struct Object {
    pos: Position,
}

#[derive(Entity)]
struct MovableObject {
    pos: Position,
    _m: Movable,
}

filter!(Fpos, Target = Position);
filter!(Fmovpos, Target = Position, All = Movable);

fn main() {
    Ecs::create(WorkerPool::with_len(2), [2])
        .register_entity_of::<Object>()
        .register_entity_of::<MovableObject>()
        .add_once_systems((create, print, moves, print))
        .step();
}

fn create(ew: EntWrite<(Object, MovableObject)>) {
    let (mut obj_container, mut mov_container) = ew.take_recur();

    obj_container.add(Object {
        pos: Position { x: 1, y: 2 },
    });
    mov_container.add(MovableObject {
        pos: Position { x: 3, y: 4 },
        _m: Movable,
    });
}

fn moves(mut w: Write<Fmovpos>) {
    for Position { x, y } in w.iter_mut().flatten() {
        *x += 10;
        *y += 10;
    }
}

fn print(r: Read<Fpos>) {
    for container in r.iter() {
        for Position { x, y } in container.iter() {
            println!("{}: ({x}, {y})", container.entity_name().unwrap());
        }
    }
}

Declaring Access

Parallel execution depends on explicit access declarations. Reads may run together, while writes are exclusive.

fn read_components(v: Read<A>) { /* ... */ }
fn write_components(v: Write<B>) { /* ... */ }
fn read_resource(v: ResRead<C>) { /* ... */ }
fn write_resource(v: ResWrite<D>) { /* ... */ }
fn write_entity(v: EntWrite<E>) { /* ... */ }

Because systems state their access up front, my-ecs can detect dependency conflicts and schedule safe execution automatically.

Examples In This Crate

  • examples/parallel.rs: using rayon with ECS-aware parallel iteration.
  • examples/async.rs: posting futures and applying their results back into the ECS world.
  • examples/async_io.rs: separating async I/O work from compute-heavy work by worker group.
  • demo/: a browser demo that renders the Mandelbrot set with my-ecs.

Prelude

Most applications can start with:

use my_ecs::prelude::*;

The prelude re-exports the main ECS API, derive macros, helper macros, and rayon::prelude::*.

About

An ECS implementation supporting wasm

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE.txt
MIT
LICENSE-MIT.txt

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages