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

  1. system object (HelloWorld)
  2. with some name ("hello_world"")
  3. 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 and WriteStorage are implementors of SystemData themselves, that's why we could use the first one for our HelloWorld 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 Resources, 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.res);

Full example code

Here the code for this chapter:

use specs::{Builder, Component, DispatcherBuilder, ReadStorage,
            System, VecStorage, World, 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.res);
    world.maintain();
}

The next chapter will be a really short chapter about Resources, a way to share data between systems which only exist independent of entities (as opposed to 0..1 times per entity).