Dispatcher
When to use a Dispatcher
The Dispatcher
allows you to automatically parallelize
system execution where possible, using the fork-join model to split up the
work and merge the result at the end. It requires a bit more planning
and may have a little bit more overhead, but it's pretty convenient,
especially when you're building a big game where you don't
want to do this manually.
Building a dispatcher
First of all, we have to build such a dispatcher.
use specs::DispatcherBuilder;
let mut dispatcher = DispatcherBuilder::new()
.with(HelloWorld, "hello_world", &[])
.build();
Let's see what this does. After creating the builder, we add a new
- system object (
HelloWorld
) - with some name (
"hello_world""
) - and no dependencies (
&[]
).
The name can be used to specify that system as a dependency of another one. But we don't have a second system yet.
Creating another system
struct UpdatePos;
impl<'a> System<'a> for UpdatePos {
type SystemData = (ReadStorage<'a, Velocity>,
WriteStorage<'a, Position>);
}
Let's talk about the system data first. What you see here is a tuple, which we are using as our SystemData
.
In fact, SystemData
is implemented for all tuples with up to 26 other types implementing SystemData
in it.
Notice that
ReadStorage
andWriteStorage
are implementors ofSystemData
themselves, that's why we could use the first one for ourHelloWorld
system without wrapping it in a tuple; for more information see the Chapter about system data.
To complete the implementation block, here's the run
method:
fn run(&mut self, (vel, mut pos): Self::SystemData) {
use specs::Join;
for (vel, pos) in (&vel, &mut pos).join() {
pos.x += vel.x * 0.05;
pos.y += vel.y * 0.05;
}
}
Now the .join()
method also makes sense: it joins the two component
storages, so that you either get no new element or a new element with
both components, meaning that entities with only a Position
, only
a Velocity
or none of them will be skipped. The 0.05
fakes the
so called delta time which is the time needed for one frame.
We have to hardcode it right now, because it's not a component (it's the
same for every entity). The solution to this are Resource
s, see
the next Chapter.
Adding a system with a dependency
Okay, we'll add two more systems after the HelloWorld
system:
.with(UpdatePos, "update_pos", &["hello_world"])
.with(HelloWorld, "hello_updated", &["update_pos"])
The UpdatePos
system now depends on the HelloWorld
system and will only
be executed after the dependency has finished. The final HelloWorld
system prints the resulting updated positions.
Now to execute all the systems, just do
dispatcher.dispatch(&mut world);
Full example code
Here the code for this chapter:
use specs::{Builder, Component, DispatcherBuilder, ReadStorage,
System, VecStorage, World, WorldExt, WriteStorage};
#[derive(Debug)]
struct Position {
x: f32,
y: f32,
}
impl Component for Position {
type Storage = VecStorage<Self>;
}
#[derive(Debug)]
struct Velocity {
x: f32,
y: f32,
}
impl Component for Velocity {
type Storage = VecStorage<Self>;
}
struct HelloWorld;
impl<'a> System<'a> for HelloWorld {
type SystemData = ReadStorage<'a, Position>;
fn run(&mut self, position: Self::SystemData) {
use specs::Join;
for position in position.join() {
println!("Hello, {:?}", &position);
}
}
}
struct UpdatePos;
impl<'a> System<'a> for UpdatePos {
type SystemData = (ReadStorage<'a, Velocity>,
WriteStorage<'a, Position>);
fn run(&mut self, (vel, mut pos): Self::SystemData) {
use specs::Join;
for (vel, pos) in (&vel, &mut pos).join() {
pos.x += vel.x * 0.05;
pos.y += vel.y * 0.05;
}
}
}
fn main() {
let mut world = World::new();
world.register::<Position>();
world.register::<Velocity>();
// Only the second entity will get a position update,
// because the first one does not have a velocity.
world.create_entity().with(Position { x: 4.0, y: 7.0 }).build();
world
.create_entity()
.with(Position { x: 2.0, y: 5.0 })
.with(Velocity { x: 0.1, y: 0.2 })
.build();
let mut dispatcher = DispatcherBuilder::new()
.with(HelloWorld, "hello_world", &[])
.with(UpdatePos, "update_pos", &["hello_world"])
.with(HelloWorld, "hello_updated", &["update_pos"])
.build();
dispatcher.dispatch(&mut world);
world.maintain();
}
The next chapter will be a really short chapter about Resource
s,
a way to share data between systems which only exist independent of
entities (as opposed to 0..1 times per entity).