Crate rin::ecs[][src]

Rinecs

rinecs is a fast and easy to use ECS library oriented to work mostly in conjuntion with rin and associated crates

ECS

ECS stands for Entity Component System, understanding what this three elements mean and how they are used in rinecs will help you understand how to use rinecs

Entity

An entity in ECS is just an id, a number identifying such entity. In your application an entity could be a particle in a particle system or a character in a video game

Component

A component in ECS is the data that the application uses, each component is associated to an entity.

For example in a particle system there could be components for the Position, Velocity, Size and Color.

Each particle would have these components but there could be other entities with only some of this components or others in the same world

System

A system in ECS is the functionality that reads and modifies the components in the world

For example in our particle system a ParticleUpdateSystem could add the Velocity to the Position of every entity that have both a Position and a Velocity

Another system could for example change the Color based on the Velocity for entities that have both Color and Velocity

Differences with object oriented programming

In object oriented programming each object contains it’s own data, for example in the particle system we are using as example if we used OO each particle would have it’s own position, velocity, size, something like:

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

struct Particle {
    position: Position,
    velocity: Velocity,
    size: f32,
}

Now if we wanted to update this particle each frame to change it’s postion based on it’s velocity we would usually create an update method that updated the position by adding the velocity to it. Let’s say that we also want to update the size relative to the velocity of the particle:

impl Particle{
    fn update(&mut self){
        self.position.x += self.velocity.x;
        self.position.x += self.velocity.y;
        self.size = self.velocity.x + self.velocity.y;
    }
}

Now we would create a vector of particles, update them all every frame and draw them afterwards probably through a draw method in the particle:

let mut particles: Vec<Particle> = vec![];

for particle in particles.iter_mut() {
    particle.update();
}

for particle in particles.iter() {
    particle.draw();
}

If we now wanted to add a new type of object that updated it’s position in the same way that the particle does but didn’t change it’s size, lets say a FixedSizeParticle, we would need to reimplement all of the update and even the draw logic.

In ECS the data for each particle is not contained on a specific object which solves this problem:

#[macro_use] extern crate rinecs_derive;
use rinecs::*;

#[derive(Component, Debug, Clone, Copy)]
pub struct Position{pub x: f32, pub y: f32}

#[derive(Component, Debug, Clone, Copy)]
pub struct Velocity{pub x: f32, pub y: f32}

#[derive(Component, Debug, Clone, Copy)]
pub struct Size(pub f32);

#[system(name = "update position")]
#[needs("Velocity")]
#[updates("Position")]
fn update_position_system(mut entities: Entities, resources: Resources){
    for (pos, vel) in entities.iter_for_mut::<(Write<Position>, Read<Velocity>)>(){
        pos.x += vel.x;
        pos.y += vel.y;
    }
}

#[system(name = "update size")]
#[needs("Velocity")]
#[updates("Size")]
fn update_size_system(mut entities: Entities, resources: Resources){
    for (size, vel) in entities.iter_for_mut::<(Write<Size>, Read<Velocity>)>(){
        size.0 = vel.x + vel.y;
    }
}

#[system(name = "update velocity")]
#[updates("Velocity")]
fn update_velocity_system(mut entities: Entities, resources: Resources){
    for vel in entities.iter_for_mut::<Write<Velocity>>(){
        vel.x *= 0.9999;
        vel.y *= 0.9999;
    }
}

#[system_thread_local(name = "update size")]
#[needs("Position", "Size")]
fn renderer(entities: EntitiesThreadLocal, resources: ResourcesThreadLocal){
    for (pos, size) in entities.iter_for::<(Read<Position>, ReadOption<Size>)>(){
        let size = size.unwrap_or(&Size(10.));
        // draw circle at pos with radius size
    }
}

fn main(){
   let mut world = World::new();

   world.new_entity()
       .add(Position{x: 0., y: 0.})
       .add(Velocity{x: 10., y: 10.})
       .add(Size(0.));

   world.new_entity()
       .add(Position{x: 0., y: 0.})
       .add(Velocity{x: -10., y: -10.})
       .add(Size(0.));

   world.new_entity()
       .add(Position{x: 0., y: 0.})
       .add(Velocity{x: 10., y: -10.});

   world.add_system(update_velocity_system);
   world.add_system(update_position_system);
   world.add_system(update_size_system);
   world.add_system_thread_local(renderer);


   while !done {
       world.run_once();
   }
}

Now the logic to update the position based on the velocity is independent from the particle, as well as all the other logic. We divide it in systems and each system knows what to do based on the data it uses. It doesn’t know if the position belongs to a particle, a character or any other element in the system.

Also now the updating of the postion and the size happen in parallel, in different threads, for free. Which can accelerate greatly the performance of the application if used properly.

Apart from this, the memory layout in the ECS version is potentially much faster since all the positions, velocities and sizes are contiguous in memory. For something like the particle in our example it probably won’t make any difference but for more complex obejcts with much more data the fact that each system only uses the data that it needs and that that data is contiguous in memory can make a big difference in terms of performance.

Finally you might have noticed that the renderer in our example is added as a thread local system, this makes this system always run on the main thread which might be needed depending on the rendering technology you are using (for example with opengl).

Operators

In the previous example the different systems iterate over the data they use by using certain operators.

For example the update_velocity system, only uses the Write operator with a Velocity parameter, this gives it access to all the velocity components in the world no matter to which entity they belong.

In that example we’ve also used Read, which is similar to Write but gives read only access to the components and ReadOption that reads the component if that specific entity has it or returns None if not.

Components can be grouped using tuples of types when specifying the operators.

For the full documentation of all the operators in the system see operators

Filters

In some cases it might be useful to treat a set of operators as an object. For example in the previous example we might be doing some operations in different systems. Instead of replicating the functionality we can use a Filter to group a set of components and implement methods over that object:

#[derive(Filter)]
struct Particle<'a>{
    position: Read<'a, Position>,
    size: ReadOption<'a, Size>,
}

impl<'a> Particle<'a>{
    fn render(&self){
        let size = self.size.unwrap_or(&Size(10.));
        println!("{},{} {}", self.position.x, self.position.y, size.0);
    }
}

fn renderer(entities: EntitiesThreadLocal, resources: ResourcesThreadLocal){
    for particle in entities.iter_for::<Particle>(){
        particle.render()
    }
}

As you can see in the previous example Filters can be used anywhere you would use an operator and equivalent to a tuple of all their members in terms of which entities they’ll match

Systems run order

System’s run order is determined by creating a dependencies graph from anotations in the system implementation.

In the following example:

#[macro_use] extern crate rinecs_derive;
use rinecs::*;

#[derive(Component, Debug, Clone, Copy)]
pub struct Position{pub x: f32, pub y: f32}

#[derive(Component, Debug, Clone, Copy)]
pub struct Velocity{pub x: f32, pub y: f32}

#[derive(Component, Debug, Clone, Copy)]
pub struct Size(pub f32);

#[system(name = "update position")]
#[needs("Velocity")]
#[updates("Position")]
fn update_position_system(mut entities: Entities, resources: Resources){
    for (pos, vel) in entities.iter_for_mut::<(Write<Position>, Read<Velocity>)>(){
        pos.x += vel.x;
        pos.y += vel.y;
    }
}

#[system(name = "update size")]
#[needs("Velocity")]
#[updates("Size")]
fn update_size_system(mut entities: Entities, resources: Resources){
    for (size, vel) in entities.iter_for_mut::<(Write<Size>, Read<Velocity>)>(){
        size.0 = vel.x + vel.y;
    }
}

#[system(name = "update velocity")]
#[updates("Velocity")]
fn update_velocity_system(mut entities: Entities, resources: Resources){
    for vel in entities.iter_for_mut::<Write<Velocity>>(){
        vel.x *= 0.9999;
        vel.y *= 0.9999;
    }
}

#[system_thread_local(name = "update size")]
#[needs("Position", "Size")]
fn renderer(entities: EntitiesThreadLocal, resources: ResourcesThreadLocal){
    for (pos, size) in entities.iter_for::<(Read<Position>, ReadOption<Size>)>(){
        let size = size.unwrap_or(&Size(10.));
        // draw circle at pos with radius size
    }
}

update_position_system specifies that it updates Position and needs Velocity and update_velocity_system updates velocity and doesn’t need anything. That means that update_velocity_system will run before update_position_system.

update_size_system needs Velocity and updates Size so it will run after update_velocity_system and in parallel with update_position_system since they don’t have any dependency in common.

Apart from needs and updates a system can also specify the following annotations:

Although most times needs and updates are enough, sometimes a needs / updates graph can produce a cycle which is not allowed. In that case when running the world for the first time, the application will panic specifying the systems involved in that cycle. To solve it usually we can identify a system that doesn’t need the very last information from the conflictive component and substitute a needs for a reads. Or perhaps identify a system which update is not really important for the dependency graph, and change an updates for a writes.

The dependency graph can be debugged by printing it after adding all the systems using:

world.render_systems_dependency_graph("systems.dot");

Which creates a vizgraph file with the systems dependency graph that can be viewed with any vizgraph application, or even plugins for several IDEs or text editors.

Modules

component

Different component traits usually implmented using the corresponding derive attribute.

entity
operators

Operators are the way to access the different components in rinecs

storage

Different storage types used internally to store entities components

streaming_iterator

Streaming iterators.

Macros

cast
unwrap_or_return

Structs

BuilderConditions
BuilderConditionsCreation
BuilderConditionsCreationOnce
BuilderConditionsThreadLocal
BuilderElse
BuilderElseCreation
BuilderElseCreationOnce
BuilderElseThreadLocal
CacheGuard
CreationSto
Entities

Allows to access and modify the Send components of the entities in the world.

EntitiesCreation

EntitiesCreation is passed to CreationSystem when they are run and allows to access and modify components but also to create new components for already exisiting entities or create new entities and resources.

EntitiesDebug

EntitiesDebug is passed to DebugSystems when they are run and allows to access debug information for entities’ compoenents

EntitiesThreadLocal

EntitiesThreadLocal allows to access and modify any entities components.

Entity

The id of an entity.

EntityBuilder

Adds an entity and it’s components to the world.

EntityStorages

Allows to access and modify the Send components of the entities in the world.

EntityStoragesCreation

EntityStoragesCreation allows to access and modify components but also to create new components for already exisiting entities or create new entities and resources.

EntityStoragesThreadLocal

EntitiesThreadLocal allows to access and modify any entities components.

Has

Operator that matches every entity that doe has this component without locking it’s storage

HasOption

Operator that matches every entity no matter if it has the component or not but doesn’t give access to the component, only returns a boolean specifying if the component is present or not.

HasOr

Matches entities that have any of the passed components with read only access to those components

NodeId

A node identifier within a particular Arena.

NodePtr
NodePtrMut
NodeRef
NodeRefMut
Not

Operator that matches every entity that doesn’t have this component

Ptr
PtrMut
Read

Operator that matches all entities that contain this component for read only access

ReadAndParent

Operator over hierarchical components that will select an entity’s component and it’s parent for read only

ReadAndParentRef

Operator over hierarchical components that will select an entity’s component for read only and apply another operator on it’s parent

ReadGuardRef
ReadHierarchical
ReadNot

Operator that matches every entity that has the first component and not the second

ReadOption

Operator that matches every entity no matter if it has the component or not with read only access to that component

ReadOr

Matches entities that have any of the passed components with read only access to those components

Ref

Applies a component in another entity referenced through a component that Derefs to an Entity:

RefN
Res

Marker to access resources from functions that use signature to access them instead of resources parameter

ResMut

Marker to access resources mutably from functions that use signature to access them instead of resources parameter

Resources

Gives access to the world global Send resources for reading and writing

ResourcesCreation

Gives access to the world global Send and thread local resources for reading and writing

ResourcesThreadLocal

Gives access to the world global Send and thread local resources for reading and writing

Sto

Used as a argument in storages systems, wraps the storage for an operator or query

SystemBuilder
SystemBuilderCreation
SystemBuilderThreadLocal
URef

Matches a component in another entity referenced through a component that Derefs to an Entity:

UniqueEntities
UniqueEntitiesIterWrapper
UniqueEntity
World

Main rinecs object that contains all entities, resources and systems.

Write

Operator that matches all entity’s that contain this component for read and write access

WriteAndParent

Operator over hierarchical components that will select an entity’s component and it’s parent for read and write

WriteGuardRef
WriteHierarchical
WriteOption

Operator that matches every entity no matter if it has the component or not with read and write access to that component

Enums

Bitmask
SystemCondition
SystemId

Traits

Barrier

A Barrier is one more way to synchronize systems.

ChangedData
ChangedDataSend
ChangedOrderedData
ChangedOrderedDataSend
Changes
ChangesData
ChangesDataSend
ChangesOrderedData
ChangesOrderedDataSend
Component

Main Component trait.

ComponentSend

Send Component.

ComponentThreadLocal

Non Send Component.

CreationContextExt
CreationSystem

Trait for systems that can create entities and resources.

DataAccesses
DebugParameter
EntitiesCreationExt

Trait implemented by World and CreationProxy allows to create functions that can create new entities, components and resources without relying on an specific object

EntitiesExt

Common trait for all Entities like objects.

EntityStoragesExt

Common trait for all Entities like objects.

FromComponent
HierarchicalOneToNStorage
HierarchicalStorage
IntoEntitiesIterator
IntoIter
IntoIterMut
IntoOrderedIter
IntoOrderedIterMut
IntoSendStorage
IntoStorages
IntoUniqueIterator
IntoUniqueParallelIterator
NToOneComponent

N to One components are just references to other entities.

NToOneComponentSend

Send N to One Component

NToOneComponentThreadLocal

Non Send N to One Component

OneToNComponent

One to N Component trait.

OneToNComponentSend

Send OneToNComponent.

OneToNComponentThreadLocal

Non Send OneToNComponent.

OneToNStorage
OneToOneComponent

One to One components are just references to other entities where the reference must be unique.

OneToOneComponentSend

Send N to One Component

OneToOneComponentThreadLocal

Non Send N to One Component

OrderedData
OrderedDataSend
ReferenceType
ResourcesCreationExt
ResourcesExt
ResourcesThreadLocalExt
Storage
StorageEntitiesExt
StorageRef
StreamingIterator

An interface for dealing with streaming iterators.

StreamingIteratorMut

An interface for dealing with mutable streaming iterators.

System

Trait for systems that can run from any thread in parallel with other systems.

SystemDebug

System that gives access to debug information for entites and their components

SystemOnce

Trait for systems that will run only once from any thread in parallel with other systems.

SystemOnceThreadLocal

Trait for systems that will run only once from the main thread but parallel with Send systems.

SystemThreadLocal

Trait for systems that can only run from the main thread but in parallel with Send systems.

TupleStorageEntities
UnorderedData
UnorderedDataSend

Type Definitions

MaskType
ReadRef
SystemConditionCreation
SystemConditionSend
SystemConditionThreadLocal

Attribute Macros

barrier
creation_system
creation_system_storages
debug_param_fn
system
system_foreach
system_foreach_once
system_foreach_storages
system_once
system_once_thread_local
system_once_thread_local_foreach
system_once_thread_local_storages
system_storages
system_thread_local
system_thread_local_foreach
system_thread_local_storages

Derive Macros

Component
Filter
HierarchicalComponent
HierarchicalOneToNComponent
NToOneComponent
OneToNComponent
OneToOneComponent
Tag