Bevy Version: | 0.13 | (outdated!) |
---|
As this page is outdated, please refer to Bevy's official migration guides while reading, to cover the differences: 0.13 to 0.14.
I apologize for the inconvenience. I will update the page as soon as I find the time.
Schedules
See also: ECS Intro: Your Code, for a general overview of Bevy's scheduling framework.
All systems to be run by Bevy are contained and organized using schedules. A schedule is a collection of systems, with metadata for how they should run, and an associated executor algorithm to run the systems.
A Bevy app has many different schedules for different purposes, to run them in different situations.
Scheduling Systems
If you want a system to be run by Bevy, you need to add it to a schedule via the app builder. Writing a new Rust function and forgetting to add it / register it with Bevy is a common mistake.
Whenever you add a system, you specify what schedule to put it in:
// add something to the Update schedule (runs every frame)
app.add_systems(Update, camera_movement);
// add something to the Startup schedule (runs once at app startup)
app.add_systems(Startup, setup_camera);
Per-System Configuration
You can add metadata to your systems, to affect how they will be run.
This can include:
- Run Conditions to control if a system should run
- Ordering Dependencies, if a system should run before/after specific other systems in the same schedule
- System Sets to group systems together, so common configuration can be applied to all of them
When the schedule runs, the executor algorithm will honor all of this configuration when determining if a system is ready to run. A system is ready when all of the following is true:
- No other currently-running system is accessing any of the same data mutably (as per the system parameters)
- All of the systems ordered "before" have finished or have been skipped due to run conditions
- The system's run conditions all return true
When a system becomes ready, it will be run on an available CPU thread. Systems run in a non-deterministic order by default! A system might run at different times every frame. If you care about its relationship to other systems, add ordering dependencies.
Dynamically Adding/Removing Systems
Bevy's schedules do not (yet?) support adding and removing systems at runtime. You need to configure everything ahead of time.
You should add all systems you might want to run, and then control them using run conditions. That is the mechanism for disabling them if they shouldn't run.
Bevy's App Structure
Bevy has three primary/foundational schedules: Main
, Extract
, Render
.
There are also other schedules, which are managed and run within Main
.
In a normal Bevy app, the Main
+Extract
+Render
schedules are run repeatedly
in a loop. Together, they produce one frame of your game. Every time Main
runs, it runs a sequence of other schedules. On its first run, it also first
runs a sequence of "startup" schedules.
Most Bevy users only have to deal with the sub-schedules of Main
.
Extract
and Render
are only relevant to graphics developers who want to
develop new/custom rendering features for the engine. This page is only focused
on Main
. If you want to learn more about Extract
and Render
, see
this page about Bevy's rendering architecture.
The Main Schedule
Main
is where all the application logic runs. It is a sort of meta-schedule,
whose job is to run other schedules in a specific order. You should not add any
custom systems directly to Main
. You should add your systems to the various
schedules managed by Main
.
Bevy provides the following schedules, to organize all the systems:
First
,PreUpdate
,StateTransition
,RunFixedMainLoop
,Update
,PostUpdate
,Last
- These schedules are run by
Main
every time it runs
- These schedules are run by
PreStartup
,Startup
,PostStartup
- These schedules are run by
Main
once, the first time it runs
- These schedules are run by
FixedMain
- The fixed timestep equivalent of the
Main
schedule. - Run by
RunFixedMainLoop
as many times as needed, to catch up to the fixed timestep interval.
- The fixed timestep equivalent of the
FixedFirst
,FixedPreUpdate
,FixedUpdate
,FixedPostUpdate
,FixedLast
- The fixed timestep equivalents of the
Main
sub-schedules.
- The fixed timestep equivalents of the
OnEnter(…)
/OnExit(…)
/OnTransition(…)
- These schedules are run by
StateTransition
on state changes
- These schedules are run by
The intended places for most user systems (your game logic) are Update
,
FixedUpdate
, Startup
, and the state transition schedules.
Update
is for your usual game logic that should run every frame. Startup
is
useful to perform initialization tasks, before the first normal frame update
loop. FixedUpdate
is if you want to use a fixed timestep.
The other schedules are intended for engine-internal functionality. Splitting them like that ensures that Bevy's internal engine systems will run correctly with respect to your systems, without any configuration on your part. Remember: Bevy's internals are implemented using ordinary systems and ECS, just like your own stuff!
If you are developing plugins to be used by other people, you might be
interested in adding functionality to PreUpdate
/PostUpdate
(or the Fixed
equivalents), so it can run alongside other "engine systems". Also consider
PreStartup
and PostStartup
if you have startup systems that should be
separated from your users' startup systems.
First
and Last
exist only for special edge cases, if you really need to
ensure something runs before/after everything else, including all the normal
"engine-internal" code.
Configuring Schedules
Bevy also offers some features that can be configured at the schedule level.
Single-Threaded Schedules
If you consider multi-threading to not be working well for you, for whatever reason, you can disable it per-schedule.
In a single-threaded schedule, systems will run one at a time, on the main thread. However, the same "readiness" algorithm is still applied and so systems can run in an undefined order. You should still specify ordering dependencies where you need determinism.
// Make FixedUpdate run single-threaded
app.edit_schedule(FixedUpdate, |schedule| {
schedule.set_executor_kind(ExecutorKind::SingleThreaded);
// or alternatively: Simple will apply Commands after every system
schedule.set_executor_kind(ExecutorKind::Simple);
});
Ambiguity Detection
The Ambiguity Detector is an optional Bevy feature that can help you debug issues related to non-determinism.
// Enable ambiguity warnings for the Update schedule
app.edit_schedule(Update, |schedule| {
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Warn,
..default()
});
});
It will print warnings for any combination of systems where at least one of them accesses some piece of data (resource or component) mutably, but the others don't have explicit ordering dependencies on that system.
Such situations might indicate a bug, because you don't know if the systems that read the data would run before or after the system that mutates the data.
It is up to you to decide if you care about this, on a case-by-case basis.
Deferred Application
Normally, Bevy will automatically manage where Commands and other deferred operations get applied. If systems have ordering dependencies on one another, Bevy will make sure to apply any pending deferred operations from the first system before the second system runs.
If you would like to disable this automatic behavior and manually manage the sync points, you can do that.
app.edit_schedule(Update, |schedule| {
schedule.set_build_settings(ScheduleBuildSettings {
auto_insert_apply_deferred: false,
..default()
});
});
Now, to manually create sync points, add special [apply_deferred
] systems
where you like them:
app.add_systems(
Update,
apply_deferred
.after(MyGameplaySet)
.before(MyUiSet)
);
app.add_systems(Update, (
(
system_a,
apply_deferred,
system_b,
).chain(),
));
Main Schedule Configuration
The order of schedules to be run by Main
every frame is configured in the
MainScheduleOrder
resource. For advanced use cases, if Bevy's
predefined schedules don't work for your needs, you can change it.
Creating a New Custom Schedule
As an example, let's say we want to add an additional schedule, that runs every
frame (like Update
), but runs before fixed timestep.
First, we need to create a name/label for our new schedule, by creating a Rust
type (a struct
or enum
) and deriving ScheduleLabel
+ an assortment of
required standard Rust traits.
#[derive(ScheduleLabel, Debug, Clone, PartialEq, Eq, Hash)]
struct PrepareUpdate;
Now, we can init the schedule in the app, add it to
MainScheduleOrder
to make it run every frame where we like it, and add some
systems to it!
// Ensure the schedule has been created
// (this is technically optional; Bevy will auto-init
// the schedule the first time it is used)
app.init_schedule(PrepareUpdate);
// Add it to the MainScheduleOrder so it runs every frame
// as part of the Main schedule. We want our PrepareUpdate
// schedule to run after StateTransition.
app.world.resource_mut::<MainScheduleOrder>()
.insert_after(StateTransition, PrepareUpdate);
// Now we can add some systems to our new schedule!
app.add_systems(PrepareUpdate, (
my_weird_custom_stuff,
));