Tainted\\Coders

Bevy Commands

Bevy version: 0.14Last updated:

In Bevy, each Command represents a mutation we want to apply to the state of our world.

Each command, eventually executes a function that receives a mutable reference to your World that it uses to change its state.

Because each of your commands needs this mutable reference, its not safe to run while other systems might be running in parallel.

So Bevy schedules them to all run together inside one system in the order they are added to the CommandQueue, which we do through the Commands system parameter.

Once per game loop this system is run and each command applies itself to your world.

Scheduling commands

To execute a command, first we have to schedule them.

We don't manually add them to a CommandQueue but instead use a system parameter: Commands which is the public API to the queue.

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.

To despawn that same entity we would also use a command:

fn despawn_an_entity(
  mut commands: Commands,
  query: Query<Entity>
) {
  for entity in query.iter() {
    commands.entity(entity).despawn();
  }
}

Commands is the system parameter holding all of the methods to schedule changes, including custom commands you can write yourself.

Its important to note that neither command actually executes here. Instead they were queued to run the next time all your commands run together. This will happen any time we transition to the next schedule during the apply_deferred system that was added by the DefaultPlugins.

Spawning components and bundles

All changes to your world state should come from these commands, including spawning and despawning.

#[derive(Component)]
struct Player;

fn spawn_player(mut commands: Commands) {
  // Here we are `Commands`
  commands
    .spawn_empty()
    // We are now an `EntityCommands`
    // for the entity we just spawned
    .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 on the particular entity we spawned.

In fact, spawn can also 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) {
  let bundle = (Player, Transform::default());
  commands.spawn(bundle);
}

Commands are delayed until the next schedule

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

A Schedule is one part of the lifecycle of a frame. These schedules are collections of systems and instructions on exactly how to run them.

For example, Startup and Update are both common schedules you have likely seen before.

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, take_the_castle.after(raising_the_gates))
    .run();
}

Lets say we have a system take_the_castle that runs after another system raise_the_gates.

If you're smart, you'd think that by the time you go to take_the_castle you have already raised the gates.

This however, is not the case, because when take_the_castle 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 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 take_the_castle:

fn main() {
  let raise_the_gates = apply_deferred.after(raise_the_gates);
  let ordered_systems = (raise_the_gates, take_the_castle).chain();

  App::new()
    .add_systems(Update, ordered_systems)
    .run();
}

The chain call here just ensures that the first system apply_deferred.after(raise_the_gates) will run and then take_the_castle 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 size = 100.0;
  let shape = Circle::new(size);
  let color = Color::srgb(0.0, 0.0, 1.0);

  commands.spawn((
    PlanetBundle::new(size),
    ColorMesh2dBundle {
      mesh: meshes.add(shape).into(),
      material: materials.add(color),
      ..Default::default()
    },
  ));
}

However, 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 circle = Circle::new(self.radius);
        meshes.add(circle)
      });

    let color_material = world.resource_scope(
      |_world, mut materials: Mut<Assets<ColorMaterial>>| {
        let blue = Color::srgb(0.0, 0.0, 1.0);
        materials.add(blue)
      },
    );

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

That's a lot more code, but now our ability to spawn planets has become much more simplified:

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

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