Hello, World!

Setting up

First of all, thanks for trying out specs. Before setting up the project, please make sure you're using the latest Rust version:

rustup update

Okay, now let's set up the project!

cargo new --bin my_game

Add the following line to your Cargo.toml:

[dependencies]
specs = "0.16.1"

Components

Let's start by creating some data:

use specs::{Component, VecStorage};

#[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>;
}

These will be our two component types. Optionally, the specs-derive crate provides a convenient custom #[derive] you can use to define component types more succinctly.

But first, you will need to enable the derive feature:

[dependencies]
specs = { version = "0.16.1", features = ["specs-derive"] }

Now you can do this:

use specs::{Component, VecStorage};

#[derive(Component, Debug)]
#[storage(VecStorage)]
struct Position {
    x: f32,
    y: f32,
}

#[derive(Component, Debug)]
#[storage(VecStorage)]
struct Velocity {
    x: f32,
    y: f32,
}

If the #[storage(...)] attribute is omitted, the given component will be stored in a DenseVecStorage by default. But for this example, we are explicitly asking for these components to be kept in a VecStorage instead (see the later storages chapter for more details). But before we move on, we need to create a world in which to store all of our components.

The World

use specs::{World, WorldExt, Builder};

let mut world = World::new();
world.register::<Position>();
world.register::<Velocity>();

This will create component storages for Positions and Velocitys.

let ball = world.create_entity().with(Position { x: 4.0, y: 7.0 }).build();

Now you have an Entity, associated with a position.

Note: World is a struct coming from shred, an important dependency of Specs. Whenever you call functions specific to Specs, you will need to import the WorldExt trait.

So far this is pretty boring. We just have some data, but we don't do anything with it. Let's change that!

The system

use specs::System;

struct HelloWorld;

impl<'a> System<'a> for HelloWorld {
    type SystemData = ();

    fn run(&mut self, data: Self::SystemData) {}
}

This is what a system looks like. Though it doesn't do anything (yet). Let's talk about this dummy implementation first. The SystemData is an associated type which specifies which components we need in order to run the system.

Let's see how we can read our Position components:

use specs::{ReadStorage, System};

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);
        }
    }
}

Note that all components that a system accesses must be registered with world.register::<Component>() before that system is run, or you will get a panic. This will usually be done automatically during setup, but we'll come back to that in a later chapter.

There are many other types you can use as system data. Please see the System Data Chapter for more information.

Running the system

This just iterates through all the components and prints them. To execute the system, you can use RunNow like this:

use specs::RunNow;

let mut hello_world = HelloWorld;
hello_world.run_now(&world);
world.maintain();

The world.maintain() is not completely necessary here. Calling maintain should be done in general, however. If entities are created or deleted while a system is running, calling maintain will record the changes in its internal data structure.

Full example code

Here the complete example of this chapter:

use specs::{Builder, Component, ReadStorage, System, VecStorage, World, WorldExt, RunNow};

#[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);
        }
    }
}

fn main() {
    let mut world = World::new();
    world.register::<Position>();
    world.register::<Velocity>();

    world.create_entity().with(Position { x: 4.0, y: 7.0 }).build();

    let mut hello_world = HelloWorld;
    hello_world.run_now(&world);
    world.maintain();
}

This was a pretty basic example so far. A key feature we haven't seen is the Dispatcher, which allows us to configure systems to run in parallel (and it offers some other nice features, too).

Let's see how that works in Chapter 3: Dispatcher.