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
150 downloads per month
Used in acttey
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 Component
s. 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 withrayon
's parallel iterator. -
Supporting asynchronous function.
The crate containsFuture
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