Legion aims to be a feature rich high performance ECS library for Rust game projects with minimal boilerplate.
Based on the ecs_bench project.
```rust use legion::*;
// Define our entity data types
struct Position { x: f32, y: f32, }
struct Velocity { dx: f32, dy: f32, }
struct Model(usize);
struct Static;
// Create a world to store our entities let universe = Universe::new(); let mut world = universe.create_world();
// Create entities with Position
and Velocity
data
world.insert(
(),
(0..999).map(|_| (Position { x: 0.0, y: 0.0 }, Velocity { dx: 0.0, dy: 0.0 }))
);
// Create entities with Position
data and a shared Model
data, tagged as Static
// Shared data values are shared across many entities,
// and enable further batch processing and filtering use cases
let entities: &[Entity] = world.insert(
(Model(5), Static),
(0..999).map(|_| (Position { x: 0.0, y: 0.0 },))
);
// Create a query which finds all Position
and Velocity
components
let query = <(Write
// Iterate through all entities that match the query in the world for (mut pos, vel) in query.iter(&mut world) { pos.x += vel.dx; pos.y += vel.dy; } ```
Legion aims to be a more feature-complete game-ready ECS than many of its predecessors.
The query API can do much more than pull entity data out of the world.
Additional data type filters:
rust
// It is possible to specify that entities must contain data beyond that being fetched
let query = Read::<Position>::query()
.filter(component::<Velocity>());
for position in query.iter(&mut world) {
// these entities also have `Velocity`
}
Filter boolean operations:
rust
// Filters can be combined with boolean operators
let query = Read::<Position>::query()
.filter(tag::<Static>() | !component::<Velocity>());
for position in query.iter(&mut world) {
// these entities are also either marked as `Static`, or do *not* have a `Velocity`
}
Filter by shared data value:
rust
// Filters can filter by specific shared data values
let query = Read::<Position>::query()
.filter(tag_value(&Model(3)));
for position in query.iter(&mut world) {
// these entities all have shared data value `Model(3)`
}
Change detection:
rust
// Queries can perform coarse-grained change detection, rejecting entities who's data
// has not changed since the last time the query was iterated.
let query = <(Read<Position>, Shared<Model>)>::query()
.filter(changed::<Position>());
for (pos, model) in query.iter(&mut world) {
// entities who have changed position
}
Entities can be loaded and initialized in a background World
on separate threads and then
when ready, merged into the main World
near instantaneously.
```rust let universe = Universe::new(); let mut worlda = universe.createworld(); let mut worldb = universe.createworld();
// Merge all entities from world_b
into world_a
// Entity IDs are guarenteed to be unique across worlds and will
// remain unchanged across the merge.
worlda.merge(worldb);
```
Entity data is allocated in blocks called "chunks", each approximately containing 64KiB of data. The query API exposes each chunk via iter_chunk
. As all entities in a chunk are guarenteed to contain the same set of entity data and shared data values, it is possible to do batch processing via the chunk API.
``rust
fn render_instanced(model: &Model, transforms: &[Transform]) {
// pass
transforms` pointer to graphics API to load into constant buffer
// issue instanced draw call with model data and transforms
}
let query = Read::
for chunk in query.iter_chunks(&mut world) { // get the chunk's model let model: &Model = chunk.tag().unwrap();
// get a (runtime borrow checked) slice of transforms
let transforms = chunk.components::<Transform>().unwrap();
// give the model and transform slice to our renderer
render_instanced(model, &transforms);
} ```