Legion

Build Status Crates.io Docs.rs

Legion aims to be a feature rich high performance ECS library for Rust game projects with minimal boilerplate.

Benchmarks

Based on the ecs_bench project.

Getting Started

```rust use legion::prelude::*;

// Define our entity data types

[derive(Clone, Copy, Debug, PartialEq)]

struct Position { x: f32, y: f32, }

[derive(Clone, Copy, Debug, PartialEq)]

struct Velocity { dx: f32, dy: f32, }

[derive(Clone, Copy, Debug, PartialEq)]

struct Model(usize);

[derive(Clone, Copy, Debug, PartialEq)]

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, Read)>::query();

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

WASM

Legion runs with parallelism on by default, which is not currently supported by Web Assembly as it runs single-threaded. Therefore, to build for WASM, ensure you set default-features = false in Cargo.toml: toml legion = { version = "*", default-features = false }

Features

Legion aims to be a more feature-complete game-ready ECS than many of its predecessors.

Advanced Query Filters

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 }

Content Streaming

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

// Move all entities from world_b into world_a // Entity IDs are guaranteed to be unique across worlds and will // remain unchanged across the move. worlda.movefrom(world_b); ```

Chunk Iteration

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]) { // passtransforms` pointer to graphics API to load into constant buffer // issue instanced draw call with model data and transforms }

let query = Read::::query() .filter(tag::());

for chunk in query.iterchunksmut(&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);

} ```