Add First/Pre/Post/Last schedules to the Fixed timestep (#10977)

Fixes https://github.com/bevyengine/bevy/issues/10974

# Objective
Duplicate the ordering logic of the `Main` schedule into the `FixedMain`
schedule.

---

## Changelog
- `FixedUpdate` is no longer the main schedule ran in
`RunFixedUpdateLoop`, `FixedMain` has replaced this and has a similar
structure to `Main`.

## Migration Guide
- Usage of `RunFixedUpdateLoop` should be renamed to `RunFixedMainLoop`.
This commit is contained in:
Aceeri 2023-12-13 20:35:40 -08:00 committed by GitHub
parent a4e0a0c0b9
commit fe28e0ec32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 129 additions and 36 deletions

View File

@ -24,8 +24,9 @@ pub mod prelude {
pub use crate::{ pub use crate::{
app::App, app::App,
main_schedule::{ main_schedule::{
First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate, First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
SpawnScene, Startup, StateTransition, Update, PostStartup, PostUpdate, PreStartup, PreUpdate, SpawnScene, Startup, StateTransition,
Update,
}, },
DynamicPlugin, Plugin, PluginGroup, DynamicPlugin, Plugin, PluginGroup,
}; };

View File

@ -18,8 +18,8 @@ use bevy_ecs::{
/// * [`First`] /// * [`First`]
/// * [`PreUpdate`] /// * [`PreUpdate`]
/// * [`StateTransition`] /// * [`StateTransition`]
/// * [`RunFixedUpdateLoop`] /// * [`RunFixedMainLoop`]
/// * This will run [`FixedUpdate`] zero to many times, based on how much time has elapsed. /// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
/// * [`Update`] /// * [`Update`]
/// * [`PostUpdate`] /// * [`PostUpdate`]
/// * [`Last`] /// * [`Last`]
@ -67,25 +67,62 @@ pub struct PreUpdate;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct StateTransition; pub struct StateTransition;
/// Runs the [`FixedUpdate`] schedule in a loop according until all relevant elapsed time has been "consumed". /// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
/// ///
/// See the [`Main`] schedule for some details about how schedules are run. /// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct RunFixedUpdateLoop; pub struct RunFixedMainLoop;
/// Runs first in the [`FixedMain`] schedule.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedFirst;
/// The schedule that contains logic that must run before [`FixedUpdate`].
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedPreUpdate;
/// The schedule that contains most gameplay logic.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedUpdate;
/// The schedule that runs after the [`FixedUpdate`] schedule, for reacting
/// to changes made in the main update logic.
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedPostUpdate;
/// The schedule that runs last in [`FixedMain`]
///
/// See the [`FixedMain`] schedule for details on how fixed updates work.
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedLast;
/// The schedule that contains systems which only run after a fixed period of time has elapsed. /// The schedule that contains systems which only run after a fixed period of time has elapsed.
/// ///
/// The exclusive `run_fixed_update_schedule` system runs this schedule. /// The exclusive `run_fixed_main_schedule` system runs this schedule.
/// This is run by the [`RunFixedUpdateLoop`] schedule. /// This is run by the [`RunFixedMainLoop`] schedule.
/// ///
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default. /// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs). /// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
/// ///
/// See the [`Main`] schedule for some details about how schedules are run. /// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
pub struct FixedUpdate; pub struct FixedMain;
/// The schedule that contains app logic. /// The schedule that contains app logic. Ideally containing anything that must run once per
/// render frame, such as UI.
/// ///
/// See the [`Main`] schedule for some details about how schedules are run. /// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
@ -131,7 +168,7 @@ impl Default for MainScheduleOrder {
First.intern(), First.intern(),
PreUpdate.intern(), PreUpdate.intern(),
StateTransition.intern(), StateTransition.intern(),
RunFixedUpdateLoop.intern(), RunFixedMainLoop.intern(),
Update.intern(), Update.intern(),
SpawnScene.intern(), SpawnScene.intern(),
PostUpdate.intern(), PostUpdate.intern(),
@ -188,7 +225,7 @@ impl Main {
} }
} }
/// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`]. /// Initializes the [`Main`] schedule, sub schedules, and resources for a given [`App`].
pub struct MainSchedulePlugin; pub struct MainSchedulePlugin;
impl Plugin for MainSchedulePlugin { impl Plugin for MainSchedulePlugin {
@ -196,12 +233,62 @@ impl Plugin for MainSchedulePlugin {
// simple "facilitator" schedules benefit from simpler single threaded scheduling // simple "facilitator" schedules benefit from simpler single threaded scheduling
let mut main_schedule = Schedule::new(Main); let mut main_schedule = Schedule::new(Main);
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded); main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_update_loop_schedule = Schedule::new(RunFixedUpdateLoop); let mut fixed_main_schedule = Schedule::new(FixedMain);
fixed_update_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded); fixed_main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_loop_schedule = Schedule::new(RunFixedMainLoop);
fixed_main_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
app.add_schedule(main_schedule) app.add_schedule(main_schedule)
.add_schedule(fixed_update_loop_schedule) .add_schedule(fixed_main_schedule)
.add_schedule(fixed_main_loop_schedule)
.init_resource::<MainScheduleOrder>() .init_resource::<MainScheduleOrder>()
.add_systems(Main, Main::run_main); .init_resource::<FixedMainScheduleOrder>()
.add_systems(Main, Main::run_main)
.add_systems(FixedMain, FixedMain::run_fixed_main);
}
}
/// Defines the schedules to be run for the [`FixedMain`] schedule, including
/// their order.
#[derive(Resource, Debug)]
pub struct FixedMainScheduleOrder {
/// The labels to run for the [`FixedMain`] schedule (in the order they will be run).
pub labels: Vec<InternedScheduleLabel>,
}
impl Default for FixedMainScheduleOrder {
fn default() -> Self {
Self {
labels: vec![
FixedFirst.intern(),
FixedPreUpdate.intern(),
FixedUpdate.intern(),
FixedPostUpdate.intern(),
FixedLast.intern(),
],
}
}
}
impl FixedMainScheduleOrder {
/// Adds the given `schedule` after the `after` schedule
pub fn insert_after(&mut self, after: impl ScheduleLabel, schedule: impl ScheduleLabel) {
let index = self
.labels
.iter()
.position(|current| (**current).eq(&after))
.unwrap_or_else(|| panic!("Expected {after:?} to exist"));
self.labels.insert(index + 1, schedule.intern());
}
}
impl FixedMain {
/// A system that runs the fixed timestep's "main schedule"
pub fn run_fixed_main(world: &mut World) {
world.resource_scope(|world, order: Mut<FixedMainScheduleOrder>| {
for &label in &order.labels {
let _ = world.try_run_schedule(label);
}
});
} }
} }

View File

@ -1,8 +1,9 @@
use bevy_app::FixedMain;
use bevy_ecs::world::World; use bevy_ecs::world::World;
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use bevy_utils::Duration; use bevy_utils::Duration;
use crate::{time::Time, virt::Virtual, FixedUpdate}; use crate::{time::Time, virt::Virtual};
/// The fixed timestep game clock following virtual time. /// The fixed timestep game clock following virtual time.
/// ///
@ -12,7 +13,8 @@ use crate::{time::Time, virt::Virtual, FixedUpdate};
/// It is automatically inserted as a resource by /// It is automatically inserted as a resource by
/// [`TimePlugin`](crate::TimePlugin) and updated based on /// [`TimePlugin`](crate::TimePlugin) and updated based on
/// [`Time<Virtual>`](Virtual). The fixed clock is automatically set as the /// [`Time<Virtual>`](Virtual). The fixed clock is automatically set as the
/// generic [`Time`] resource during [`FixedUpdate`] schedule processing. /// generic [`Time`] resource during [`FixedUpdate`](bevy_app::FixedUpdate)
/// schedule processing.
/// ///
/// The fixed timestep clock advances in fixed-size increments, which is /// The fixed timestep clock advances in fixed-size increments, which is
/// extremely useful for writing logic (like physics) that should have /// extremely useful for writing logic (like physics) that should have
@ -26,7 +28,9 @@ use crate::{time::Time, virt::Virtual, FixedUpdate};
/// frame). Additionally, the value is a power of two which losslessly converts /// frame). Additionally, the value is a power of two which losslessly converts
/// into [`f32`] and [`f64`]. /// into [`f32`] and [`f64`].
/// ///
/// To run a system on a fixed timestep, add it to the [`FixedUpdate`] schedule. /// To run a system on a fixed timestep, add it to one of the [`FixedMain`]
/// schedules, most commonly [`FixedUpdate`](bevy_app::FixedUpdate).
///
/// This schedule is run a number of times between /// This schedule is run a number of times between
/// [`PreUpdate`](bevy_app::PreUpdate) and [`Update`](bevy_app::Update) /// [`PreUpdate`](bevy_app::PreUpdate) and [`Update`](bevy_app::Update)
/// according to the accumulated [`overstep()`](Time::overstep) time divided by /// according to the accumulated [`overstep()`](Time::overstep) time divided by
@ -43,20 +47,21 @@ use crate::{time::Time, virt::Virtual, FixedUpdate};
/// means it is affected by [`pause()`](Time::pause), /// means it is affected by [`pause()`](Time::pause),
/// [`set_relative_speed()`](Time::set_relative_speed) and /// [`set_relative_speed()`](Time::set_relative_speed) and
/// [`set_max_delta()`](Time::set_max_delta) from virtual time. If the virtual /// [`set_max_delta()`](Time::set_max_delta) from virtual time. If the virtual
/// clock is paused, the [`FixedUpdate`] schedule will not run. It is guaranteed /// clock is paused, the [`FixedUpdate`](bevy_app::FixedUpdate) schedule will
/// that the [`elapsed()`](Time::elapsed) time in `Time<Fixed>` is always /// not run. It is guaranteed that the [`elapsed()`](Time::elapsed) time in
/// between the previous `elapsed()` and the current `elapsed()` value in /// `Time<Fixed>` is always between the previous `elapsed()` and the current
/// `Time<Virtual>`, so the values are compatible. /// `elapsed()` value in `Time<Virtual>`, so the values are compatible.
/// ///
/// Changing the timestep size while the game is running should not normally be /// Changing the timestep size while the game is running should not normally be
/// done, as having a regular interval is the point of this schedule, but it may /// done, as having a regular interval is the point of this schedule, but it may
/// be necessary for effects like "bullet-time" if the normal granularity of the /// be necessary for effects like "bullet-time" if the normal granularity of the
/// fixed timestep is too big for the slowed down time. In this case, /// fixed timestep is too big for the slowed down time. In this case,
/// [`set_timestep()`](Time::set_timestep) and be called to set a new value. The /// [`set_timestep()`](Time::set_timestep) and be called to set a new value. The
/// new value will be used immediately for the next run of the [`FixedUpdate`] /// new value will be used immediately for the next run of the
/// schedule, meaning that it will affect the [`delta()`](Time::delta) value for /// [`FixedUpdate`](bevy_app::FixedUpdate) schedule, meaning that it will affect
/// the very next [`FixedUpdate`], even if it is still during the same frame. /// the [`delta()`](Time::delta) value for the very next
/// Any [`overstep()`](Time::overstep) present in the accumulator will be /// [`FixedUpdate`](bevy_app::FixedUpdate), even if it is still during the same
/// frame. Any [`overstep()`](Time::overstep) present in the accumulator will be
/// processed according to the new [`timestep()`](Time::timestep) value. /// processed according to the new [`timestep()`](Time::timestep) value.
#[derive(Debug, Copy, Clone, Reflect)] #[derive(Debug, Copy, Clone, Reflect)]
pub struct Fixed { pub struct Fixed {
@ -225,14 +230,14 @@ impl Default for Fixed {
} }
} }
/// Runs [`FixedUpdate`] zero or more times based on delta of /// Runs [`FixedMain`] zero or more times based on delta of
/// [`Time<Virtual>`](Virtual) and [`Time::overstep`] /// [`Time<Virtual>`](Virtual) and [`Time::overstep`]
pub fn run_fixed_update_schedule(world: &mut World) { pub fn run_fixed_main_schedule(world: &mut World) {
let delta = world.resource::<Time<Virtual>>().delta(); let delta = world.resource::<Time<Virtual>>().delta();
world.resource_mut::<Time<Fixed>>().accumulate(delta); world.resource_mut::<Time<Fixed>>().accumulate(delta);
// Run the schedule until we run out of accumulated time // Run the schedule until we run out of accumulated time
let _ = world.try_schedule_scope(FixedUpdate, |world, schedule| { let _ = world.try_schedule_scope(FixedMain, |world, schedule| {
while world.resource_mut::<Time<Fixed>>().expend() { while world.resource_mut::<Time<Fixed>>().expend() {
*world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic(); *world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic();
schedule.run(world); schedule.run(world);

View File

@ -24,7 +24,7 @@ pub mod prelude {
pub use crate::{Fixed, Real, Time, Timer, TimerMode, Virtual}; pub use crate::{Fixed, Real, Time, Timer, TimerMode, Virtual};
} }
use bevy_app::{prelude::*, RunFixedUpdateLoop}; use bevy_app::{prelude::*, RunFixedMainLoop};
use bevy_ecs::event::{event_queue_update_system, EventUpdateSignal}; use bevy_ecs::event::{event_queue_update_system, EventUpdateSignal};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_utils::{tracing::warn, Duration, Instant}; use bevy_utils::{tracing::warn, Duration, Instant};
@ -57,11 +57,11 @@ impl Plugin for TimePlugin {
First, First,
(time_system, virtual_time_system.after(time_system)).in_set(TimeSystem), (time_system, virtual_time_system.after(time_system)).in_set(TimeSystem),
) )
.add_systems(RunFixedUpdateLoop, run_fixed_update_schedule); .add_systems(RunFixedMainLoop, run_fixed_main_schedule);
// ensure the events are not dropped until `FixedUpdate` systems can observe them // ensure the events are not dropped until `FixedMain` systems can observe them
app.init_resource::<EventUpdateSignal>() app.init_resource::<EventUpdateSignal>()
.add_systems(FixedUpdate, event_queue_update_system); .add_systems(FixedPostUpdate, event_queue_update_system);
#[cfg(feature = "bevy_ci_testing")] #[cfg(feature = "bevy_ci_testing")]
if let Some(ci_testing_config) = app if let Some(ci_testing_config) = app

View File

@ -15,7 +15,7 @@ use bevy_utils::Duration;
/// virtual time. /// virtual time.
/// - [`Time`] is a generic clock that corresponds to "current" or "default" /// - [`Time`] is a generic clock that corresponds to "current" or "default"
/// time for systems. It contains [`Time<Virtual>`](crate::virt::Virtual) /// time for systems. It contains [`Time<Virtual>`](crate::virt::Virtual)
/// except inside the [`FixedUpdate`](bevy_app::FixedUpdate) schedule when it /// except inside the [`FixedMain`](bevy_app::FixedMain) schedule when it
/// contains [`Time<Fixed>`](crate::fixed::Fixed). /// contains [`Time<Fixed>`](crate::fixed::Fixed).
/// ///
/// The time elapsed since the previous time this clock was advanced is saved as /// The time elapsed since the previous time this clock was advanced is saved as
@ -45,7 +45,7 @@ use bevy_utils::Duration;
/// [`elapsed()`](Time::elapsed) should use `Res<Time>` to access the default /// [`elapsed()`](Time::elapsed) should use `Res<Time>` to access the default
/// time configured for the program. By default, this refers to /// time configured for the program. By default, this refers to
/// [`Time<Virtual>`](crate::virt::Virtual) except during the /// [`Time<Virtual>`](crate::virt::Virtual) except during the
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule when it refers to /// [`FixedMain`](bevy_app::FixedMain) schedule when it refers to
/// [`Time<Fixed>`](crate::fixed::Fixed). This ensures your system can be used /// [`Time<Fixed>`](crate::fixed::Fixed). This ensures your system can be used
/// either in [`Update`](bevy_app::Update) or /// either in [`Update`](bevy_app::Update) or
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule depending on what is needed. /// [`FixedUpdate`](bevy_app::FixedUpdate) schedule depending on what is needed.