1 unstable release
0.1.0 | Jan 26, 2020 |
---|
#731 in Concurrency
157 stars & 8 watchers
97KB
2.5K
SLoC
A multi-threaded entity component system without locks on components
Features
- Fast, aims to have low overhead
- Threaded, systems will automatically be threaded where possible
- Simple, nothing complex in terms of api usage
How it works
Systems declare the components they wish to access by using parameters.
A Read<T>
parameter declares that the system only wishes to access
the component as read-only, whilst Write<T>
declares that the system
may mutate the component (add/remove/edit).
Internally a scheduler will execute all systems using a set number of threads only allowing systems that can run safely in parallel to run at any given time. The rules for this system are simple: A component may have any number of readers at a given time as long as it has no writers. Only a single system may write to a component and no readers are allow whilst a system holds write access to a component.
Usage
Firstly all structs that you wish to use as components must implement
the Component
trait. This can be done with the component!
macro.
(See component!
's documentation for the different types).
The component must then be registered with the Container
.
struct Position {
x: i32,
y: i32,
}
component!(Position => Vec);
let mut c = Container::new();
c.register_component::<Position>();
Now the container can be used to create entities, add components and access them.
let entity = c.new_entity();
c.add_component(entity, Position { x: 5, y: 10});
// Mutable access to the component
{
let pos = c.get_component_mut::<Position>(entity).unwrap();
pos.x += 4;
}
// Immutable access to the component
assert_eq!(c.get_component::<Position>(entity), Some(&Position { x: 9, y: 10}));
Entities are generally processed via systems. Systems are just functions.
You can register a list of functions to be run via the Systems
type.
When run Systems
will automatically decide when to run a system based on
its parameters and what other systems are currently running. The order
that systems are run in is not defined.
The functions take at least one parameter, a EntityManager
reference.
This provides a interface to create and iterate over all entities in the
system. Other parameters must either be a Read<T>
or Write<T>
reference
where T is a component type. Read
provides immutable access to a component
and Write
provides mutable access (adding/removing the component as well).
Both the Read
and Write
types provide a mask
method which can be used
with EntityManager
's iter_mask
method to iterator over a subset of entities.
Masks can be chained as followed to iterator over the intersection of multiple
types pos.mask().and(vel)
.
let mut sys = Systems::new();
closure_system!(fn example(em: EntityManager, mut pos: Write<Position>) {
let mask = pos.mask();
for e in em.iter_mask(&mask) {
let pos = pos.get_component_mut(e).unwrap();
pos.y -= 3;
}
});
sys.add(example);
sys.run(&mut c);
Quirks/Issues
- Removing an entity whilst in a system wont actually remove it until after all systems have finished executing.
Dependencies
~1.5MB
~25K SLoC