[−][src]Crate pyro
What is an Entity Component System?
An Entity Component System or ECS is very similar to a relational database like SQL. The
World
is the data store where game objects (also known as Entity
) live. An Entity
contains data or Component
s.
The ECS can efficiently query those components.
Give me all entities that have a position and velocity component, and then update the position based on the velocity.
type PosVelQuery = (Write<Pos>, Read<Vel>); // ^^^^^ ^^^^ // Mutable Immutable world.matcher::<All<PosVelQuery>>().for_each(|(pos, vel)|{ pos += vel; })
Internals
Overview
- Iteration is always linear.
- Different component combinations live in a separate storage
- Removing entities does not create holes.
- All operations are designed to be used in bulk.
- Borrow rules are enforced at runtime. See
RuntimeBorrow
Entity
is using a wrapping generational index. SeeEntity::version
// A Storage that contains `Pos`, `Vel`, `Health`. ( [Pos1, Pos2, Pos3, .., PosN], [Vel1, Vel2, Vel3, .., VelN], [Health1, Health2, Health3, .., HealthN], ) // A Storage that contains `Pos`, `Vel`. ( [Pos1, Pos2, Pos3, .., PosM] [Vel1, Vel2, Vel3, .., VelM] )
Iteration is fully linear with the exception of jumping to different storages.
The iteration pattern from the query above would be
positions: [Pos1, Pos2, Pos3, .., PosN], [Pos1, Pos2, Pos3, .., PosM] velocities: [Vel1, Vel2, Vel3, .., VelN], [Vel1, Vel2, Vel3, .., VelM] ^ Jump occurs here
The jump is something like a chain of two iterators. We look at all the storages
that match specific query. If the query would be Write<Position>
, then we would
look for all the storages that contain a position array, extract the iterators and chain them
Every combination of components will be in a separate storage. This guarantees that iteration will always be linear.
Benchmarks
Getting started
extern crate pyro; use pyro::{ World, Entity, Read, Write, All, SoaStorage }; struct Position; struct Velocity; // By default creates a world backed by a [`SoaStorage`] let mut world: World<SoaStorage> = World::new(); let add_pos_vel = (0..99).map(|_| (Position{}, Velocity{})); // ^^^^^^^^^^^^^^^^^^^^^^^^ // A tuple of (Position, Velocity), // Note: Order does *not* matter // Appends 99 entities with a Position and Velocity component. world.append_components(add_pos_vel); // Appends a single entity world.append_components(Some((Position{}, Velocity{}))); // Requests a mutable borrow to Position, and an immutable borrow to Velocity. // Common queries can be reused with a typedef like this but it is not necessary. type PosVelQuery = (Write<Position>, Read<Velocity>); // Retrieves all entities that have a Position and Velocity component as an iterator. world.matcher::<All<PosVelQuery>>().for_each(|(pos, vel)|{ // ... }); // The same query as above but also retrieves the entities and collects the entities into a // `Vec<Entity>`. let entities: Vec<Entity> = world.matcher_with_entities::<All<PosVelQuery>>() .filter_map(|(entity, (pos, vel))|{ Some(entity) }).collect(); // Removes all the entities world.remove_entities(entities); let count = world.matcher::<All<PosVelQuery>>().count(); assert_eq!(count, 0);
Structs
All | Is satisfied when a storages contains all of the specified components. |
Borrow | |
BorrowIter | The |
EmptyStorage | |
Entity | Serves as an ID to lookup components for entities which can be in different storages. |
Read | Implements |
RuntimeBorrow | Rust's borrowing rules are not flexible enough for an ECS. Often it would preferred to nest multiple
queries like |
SoaStorage | A runtime SoA storage. It stands for Structure of Arrays. |
UnsafeStorage | |
World |
|
Write | Implements |
Traits
AppendComponents | |
BuildStorage |
|
Component | |
Fetch | A helper trait that works in lockstep with |
IteratorSoa | |
Matcher | |
PushBorrow | Is implemented for |
Query | Allows to query multiple components from a |
RegisterBorrow | |
RegisterComponent | A |
RuntimeStorage | |
Storage |
|
Type Definitions
ComponentId | |
StorageId | |
Version |