Exclusive Systems
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:
Local<T>
: Local parameter that holds state between invocationsSystemState<P>
:P
are normal system parameters, this lets us act like a normal systemQueryState<Q, F = ()>
: Just like aQuery
, provides scoped access to the world
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.