#ecs #web-worker #parallel #web

my-ecs

An Entity Component System (ECS) library

4 releases

0.1.0 Feb 6, 2025
0.0.3 Oct 19, 2024
0.0.2 Sep 19, 2024
0.0.1 Jul 11, 2024

#99 in WebAssembly

Download history 12/week @ 2024-10-23 1/week @ 2024-10-30 2/week @ 2024-11-06 1/week @ 2024-12-04 4/week @ 2024-12-11 150/week @ 2025-02-05

150 downloads per month
Used in acttey

Apache-2.0 OR MIT

1MB
20K SLoC

my-ecs

my-ecs is a library that implements ECS pattern.

ECS

Entity Component System (ECS) is a software architecture pattern that breaks architecture up into data and functions. Component is data of a type and Entity is a collection of the Components. Lastly, System is a function. Plus, the crate contains another concept, Resource, which is a unique data.

Features

The crate provides features below.

  • Parallel execution by default.
    The crate exploits multiple CPU cores for the best performance. Of course the crate guarantees no data race between systems that access the same components, entities, or resources. Also, the crate can be collaborated with rayon's parallel iterator.

  • Supporting asynchronous function.
    The crate contains Future executor in it. Therefore, you can put asynchronous IO or compute logics in systems.

  • Supporting web.
    The crate provides web worker implementation and can be built for 'wasm32-unknown-unknown' target. So that you can easily use your native codebase for the web.

Component

You can declare component types using normal Rust types.

use my_ecs::prelude::*;

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

Entity

Entity is actually kind of an identifier. An entity can be generated by combining components dynamically. But if you declare the entity type in advance, the crate provides you easier ways to access the entity and entity container.

use my_ecs::prelude::*;

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

// Marker component.
#[derive(Component)]
struct Movable;

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

System

System is a type that accesses components, entities, or resoruces.

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

Resource

Resource is a unique data.

use my_ecs::prelude::*;

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

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

Data dependency

The crate executes systems at the same time over multiple CPU cores by default. The crate analyzes read and write data dependencies between systems then executes them in order without data race. Reads are allowed to be accessed simultaneously while write is exclusive. To do that, clients are required to explicitly state their read or write access.

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>) { /* ... */ }

Example

This is a "hello world" example.

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

Dependencies

~3–10MB
~105K SLoC