Exclusive Systems

Exclusive systems are systems that Bevy will not run in parallel with any other system. They can have full unrestricted access to the whole ECS World, by taking a &mut World parameter.

Inside of an exclusive system, you have full control over all data stored in the ECS. You can do whatever you want.

Some example situations where exclusive systems are useful:

  • Dump various entities and components to a file, to implement things like saving and loading of game save files, or scene export from an editor
  • Directly spawn/despawn entities, or insert/remove resources, immediately with no delay (unlike when using Commands from a regular system)
  • Run arbitrary systems and schedules with your own custom control flow logic

See the direct World access page to learn more about how to do such things.

fn do_crazy_things(world: &mut World) {
    // we can do anything with any data in the Bevy ECS here!
}

You need to add exclusive systems to the App, just like regular systems. All scheduling APIs (ordering, run conditions, sets) are supported and work the same as with regular systems.

app.add_systems(Update,
    do_crazy_things
        .run_if(needs_crazy_things)
        .after(do_regular_things)
        .before(other_things)
);

Exclusive System Parameters

There are a few other things, besides &mut World, that can be used as parameters for exclusive systems:

SystemState can be used to emulate a normal system. You can put regular system parameters inside. This allows you to access the World as you would from a normal system, but you can confine it to a specific scope inside your function body, making it more flexible.

QueryState is the same thing, but for a single query. It is a simpler alternative to SystemState for when you just need to be able to query for some data.

use bevy::ecs::system::SystemState;

fn spawn_particles_for_enemies(
    world: &mut World,
    // behaves sort of like a query in a regular system
    q_enemies: &mut QueryState<&Transform, With<Enemy>>,
    // emulates a regular system with an arbitrary set of parameters
    params: &mut SystemState<(
        ResMut<MyGameSettings>,
        ResMut<MyParticleTracker>,
        Query<&mut Transform, With<Player>>,
        EventReader<MyDamageEvent>,
        // yes, even Commands ;)
        Commands,
    )>,
    // local resource, just like in a regular system
    mut has_run_once: Local<bool>,
) {
    // note: unlike with a regular Query, we need to provide the world as an argument.
    // The world will only be "locked" for the duration of this loop
    for transform in q_enemies.iter(world) {
        // TODO: do something with the transforms
    }

    // create a scope where we can access our things like a regular system
    {
        let (mut settings, mut tracker, mut q_player, mut evr, commands) =
            params.get_mut(world);

        // TODO: do things with our resources, query, events, commands, ...
    }

    // because our SystemState includes Commands,
    // we must apply them when we are done
    params.apply(world);

    // we are now free to directly spawn entities
    // because the World is no longer used by anything
    // (the SystemState and the QueryState are no longer accessing it)

    world.spawn_batch((0..10000) // efficiently spawn 10000 particles
        .map(|_| SpriteBundle {
            // ...
            ..Default::default()
        })
    );

    // and, of course, we can use our Local
    *has_run_once = true;
}

Note: if your SystemState includes Commands, you must call .apply() after you are done! That is when the deferred operations queued via commands will be applied to the World.

Performance Considerations

Exclusive systems, by definition, limit parallelism and multi-threading, as nothing else can access the same ECS World while they run. The whole schedule needs to come to a stop, to accomodate the exclusive system. This can easily introduce a performance bottleneck.

Generally speaking, you should avoid using exclusive systems, unless you need to do something that is only possible with them.

On the other hand, if your alternative is to use commands, and you need to process a huge number of entities, exclusive systems are faster.

Commands is effectively just a way to ask Bevy do to exclusive World access for you, at a later time. Going through the commands queue is much slower than just doing the exclusive access yourself.

Some examples for when exclusive systems can be faster:

  • You want to spawn/despawn a ton of entities.
    • Example: Setup/cleanup for your whole game map.
  • You want to do it every frame.
    • Example: Managing hordes of enemies.

Some examples for when normal systems with Commands can be faster:

  • You need to check some stuff every frame, but only use commands sometimes.
    • Example: Despawn enemies when they reach 0 HP.
    • Example: Spawn/despawn entities when timers finish.
    • Example: Add/remove some UI elements depending on what is happening in-game.