Tainted \\ Coders

Bevy Commands

Last updated:

In Bevy, each Command represents some mutation we want to perform on our world. Bevy takes these commands and will apply them to our world.

Because these commands need a mutable reference to the World they are not executed immediately. Instead, they get added to a CommandQueue which schedules them to run together in a single system in the order they were added.

Scheduling commands

To execute a command we schedule them in our systems with the Command system parameter:

fn spawn_an_entity(
    mut commands: Commands,
) {
    commands.spawn_empty();
}

This is the most simple command we can write. It will simply spawn an empty Entity without any components.

Its important to note that the command did not actually execute here. Instead it was queued to run the next time all your commands run together. This will happen once per tick during the apply_deferred system that was added in the DefaultPlugins.

Spawning components and bundles

The most commonly used commands will be for spawning entities with components:

#[derive(Component)]
struct Player;

fn spawn_player(
    mut commands: Commands
) {
    commands
        .spawn_empty()
        .insert(Player)
        .insert(Transform::default());
}

After we spawn_empty we actually get back an EntityCommands for the particular entity we spawned.

EntityCommands allow us to insert components onto the new entity. Insert itself will return EntityCommands which lets us chain these calls together.

Spawning an entity with some kind of component is so common that there is a shorter version of this:

fn spawn_player_shorter(
    mut commands: Commands
) {
    commands
        .spawn(Player)
        .insert(Transform::default());
}

Here instead of spawn_empty we use spawn which takes a component to add to the newly spawned entity. This also passes back EntityCommands which we can use to further insert components.

In fact, spawn can take a Bundle of components:

#[derive(Bundle)]
struct PlayerBundle {
    player: Player,
    transform: Transform,
}

fn spawn_with_bundle(
    mut commands: Commands
) {
    commands.spawn(PlayerBundle {
        player: Player,
        transform: Transform::default()
    });
}

And because tuples of components are already Bundle types this can be shortened even further to:

fn spawn_player_shortest(
    mut commands: Commands
) {
    commands.spawn((
        Player,
        Transform::default()
    ));
}

Commands are delayed until next stage

When you use the system parameter Commands you are enqueuing your commands to run when we transition to the next stage.

If we have only one stage even if we have systems that run before or after each other, the actual commands will not be applied to the world until the next frame.

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(
            Update,
            place_enemy_capital.after(build_board)
        )
        .run();
}

Lets say we have a system place_enemy_capital that runs after another system build_board. You would think that we would have access to the board components that spawned. But this is not the case, because when place_enemy_capital runs our commands have only been enqueued and not executed.

Since each command requires exclusive access to the World (we are mutating state), all queued commands are automatically applied in sequence when the builtin apply_deferred system runs.

The solution here would be to ensure that we run our apply_deferred after we have enqueued our commands but before we run place_enemy_capital:

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_systems(
            Update,
            (apply_deferred.after(build_board), place_enemy_capital).chain()
        )
        .run();
}

The chain call here just ensures that the first system apply_deferred.after(build_board) will run and then place_enemy_capital will run right after.

Custom commands

We can implement our own commands to make our logic more understandable.

Imagine we have have a game that spawns a big planet in the middle of the screen. We might have a system that looks like:

#[derive(Component)]
struct Planet {
    radius: f32
}

#[derive(Bundle)]
struct PlanetBundle {
    planet: Planet,
    transform: Transform,
}

impl PlanetBundle {
    fn new(radius: f32) -> Self {
        Self {
            planet: Planet{ radius },
            transform: Transform::default()
        }
    }
}

// Spawns a blue planet in the middle of the screen
fn spawn_planet_manually(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<ColorMaterial>>
) {
    let shape = Circle::new(100.0);
    let color_material = ColorMaterial::from(Color::rgb(0.0, 0.0, 1.0));
    commands.spawn((
        PlanetBundle::new(100.0),
        ColorMesh2dBundle {
            mesh: meshes.add(Mesh::from(shape)).into(),
            material: materials.add(color_material),
            ..Default::default()
        }
    ));
}

However for now for every system that spawns a planet we will need our meshes and materials resources. Its also not entirely clear without our comment and system name what exactly is happening here.

To shorten our parameter lists and encapsulate our logic we can create our own custom commands by implementing Command on a struct holding our parameters:

// Our custom command
struct SpawnPlanet {
    radius: f32,
    position: Vec2
}

use bevy::sprite::MaterialMesh2dBundle;

impl Command for SpawnPlanet {
    fn apply(self, world: &mut World) {
        // Resource scope works by removing the resource from the world
        // and then running our closure. This lets us mutably borrow
        // the world safely multiple times
        let mesh_handle = world.resource_scope(|_world, mut meshes: Mut<Assets<Mesh>>| {
            let shape = Circle::new(self.radius);
            meshes.add(Mesh::from(shape))
        });

        let material_handle = world.resource_scope(|_world, mut materials: Mut<Assets<ColorMaterial>>| {
            let material = ColorMaterial::from(Color::rgb(0.0, 0.0, 1.0));
            materials.add(material)
        });

        world.spawn((
            PlanetBundle::new(self.radius),
            MaterialMesh2dBundle {
                mesh: mesh_handle.into(),
                material: material_handle,
                transform: Transform::from_translation(self.position.extend(0.)),
                ..default()
            }
        ));
    }
}

fn spawn_planet(mut commands: Commands) {
    commands.add(SpawnPlanet {
        radius: 100.,
        position: Vec2::new(0., 0.)
    });
}

Commands::add will schedule our command to have apply called with a mutable reference to a world. This execution happens with all the other commands in the CommandQueue during the builtin system apply_deferred.

Extending the commands API

Sometimes it would be even nicer if we could extend the API of our commands. Something like commands.spawn_planet(Vec2::Zero, 100.0) might work even better. To do so we can use a trait extension.

Trait extensions allow you to add methods to an existing type via a new implementation. We can see an example in bevy_hierarchy:

// https://github.com/bevyengine/bevy/blob/13d46a528ac6e8c2e08e8b9ba436abb9baaefefc/crates/bevy_hierarchy/src/hierarchy.rs#L84

// Trait that holds functions for despawning recursively down the transform hierarchy
pub trait DespawnRecursiveExt {
    // Despawns the provided entity alongside all descendants.
    fn despawn_recursive(self);

    // Despawns all descendants of the given entity.
    fn despawn_descendants(&mut self);
}

impl<'w, 's, 'a> DespawnRecursiveExt for EntityCommands<'w, 's, 'a> {
    // Despawns the provided entity and its children.
    fn despawn_recursive(mut self) {
        let entity = self.id();
        self.commands().add(DespawnRecursive { entity });
    }

    fn despawn_descendants(&mut self) {
        let entity = self.id();
        self.commands().add(DespawnChildrenRecursive { entity });
    }
}

Testing commands

We can write tests for our custom commands and manually push them to a CommandQueue and finally trigger an apply:

use bevy::ecs::system::Command;
use bevy::prelude::*;

struct MyCommand;

impl Command for MyCommand {
    fn apply(self, world: &mut World) {
        info!("Hello, world!");
    }
}

#[cfg(test)]
mod tests {
    use bevy::ecs::system::CommandQueue;

    #[test]
    fn test_my_command() {
        let mut world = World::default();
        let mut command_queue = CommandQueue::default();

        // We could manually add our commands to the queue
        command_queue.push(MyCommand);

        // We can apply the commands to a given world:
        command_queue.apply(&mut world);

        // Assert something about the world here
    }
}

Read more