[][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 Components. 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.

This example is not tested
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. See Entity::version
This example is not tested
// 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

This example is not tested
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 Iterator is used to end a borrow from a query like World::matcher.

EmptyStorage
Entity

Serves as an ID to lookup components for entities which can be in different storages.

Read

Implements Fetch and allows components to be borrowed immutable.

RuntimeBorrow

Rust's borrowing rules are not flexible enough for an ECS. Often it would preferred to nest multiple queries like World::matcher, but this is not possible if both borrows would be mutable. Instead we track active borrows at runtime. Multiple reads are allowed but read/write and write/write are not.

SoaStorage

A runtime SoA storage. It stands for Structure of Arrays.

UnsafeStorage
World

World is the heart of this library. It owns all the Components and Storages. It also manages entities and allows Components to be safely queried.

Write

Implements Fetch and allows components to be borrowed mutable.

Traits

AppendComponents
BuildStorage

BuildStorage is used to create different Storages at runtime. See also AppendComponents and World::append_components

Component
Fetch

A helper trait that works in lockstep with Read and Write to borrow components either mutable or immutable.

IteratorSoa
Matcher

Allows to match over different Storages. See also All.

PushBorrow

Is implemented for Read and Write and is used to insert reads and writes into the correct HashSet.

Query

Allows to query multiple components from a Storage. See also All.

RegisterBorrow
RegisterComponent

A Storage won't have any arrays or vectors when it is created. RegisterComponent can register or add those component arrays. See also EmptyStorage::register_component

RuntimeStorage
Storage

Storage allows to abstract over different types of storages. The most common storage that implements this trait is SoaStorage.

Type Definitions

ComponentId
StorageId
Version