Tainted\\Coders

Exclusive Systems

Bevy version: 0.16Last updated:

Normally you are not allowed to make changes to the world directly. Instead, we schedule commands which run in their own exclusive system: apply_deferred that runs once per frame.

Exclusive systems are systems that take a &mut World system parameter. This gives them a set of superpowers at the cost of not being able to be run in parallel. This makes them a less performant than your other systems.

fn exclusive_system(world: &mut World) {
  // ...
}

fn main() {
  App::new()
    .add_plugins(DefaultPlugins)
    .add_systems(Update, exclusive_system);
}

There are only a few other system parameters you can take once you decide to be exclusive:

QueryState and SystemState both have to be provided your world to retrieve their data, instead of being given directly through dependency injection

Another difference between these and your normal system parameters is that to access certain params won't work unless you cache them which can make it more complicated than it needs to be.

fn psudo_command_system(mut world: &mut World) {
  let mut system_state: SystemState<Commands> = SystemState::new(world);

  let mut commands = system_state.get_mut(world);

  commands.spawn(Player);

  // We have to use this if we use `Commands`
  // and want them to be scheduled normally
  system_state.apply(world)
}

However, in most cases where we want to emulate a system, it can be much simpler to just call our other systems immediately from our exclusive system:

fn custom_system_runner(world: &mut World) {
  let system = world.register_system(hello_world);
  world.run_system(system);
}

This gets around all the gotchas from using the other exclusive system params.

Use cases

You can spawn entities immediately instead of scheduling them with commands.

fn spawn_immediately(world: &mut World) {
  world.spawn(Player);
}

This system is doing what the commands that get scheduled eventually do in their own system where they are given their own mutable access to the World.

A common use case for exclusive systems is in managing your scenes:

fn save_scene_system(world: &mut World) {
  let scene = DynamicScene::from_world(world);

  // Scenes can be serialized like this:
  let type_registry = world.resource::<AppTypeRegistry>();
  let type_registry = type_registry.read();
  let serialized_scene = scene.serialize(&type_registry).unwrap();

  // Showing the scene in the console
  info!("{}", serialized_scene);

  // Writing the scene to a new file. Using a task to avoid calling the
  // filesystem APIs in a system as they are blocking This can't work in WASM as
  // there is no filesystem access
  #[cfg(not(target_arch = "wasm32"))]
  IoTaskPool::get()
    .spawn(async move {
      // Write the scene RON data to file
      File::create(format!("assets/{NEW_SCENE_FILE_PATH}"))
        .and_then(|mut file| file.write(serialized_scene.as_bytes()))
        .expect("Error while writing scene to file");
    })
    .detach();
}

Sometimes we have situations where we don't know all our system parameters up front. Having full access to the world allows us to grab what we need dynamically.

Exclusive systems can be great for prototyping too. You can use them to write your game logic without worrying as much about exactly when and where you run your systems. Just call them directly when you need to and they execute immediately.