Skip to content

Commit

Permalink
Add First/Pre/Post/Last schedules to the Fixed timestep (#10977)
Browse files Browse the repository at this point in the history
Fixes #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`.
  • Loading branch information
Aceeri authored Dec 14, 2023
1 parent a4e0a0c commit fe28e0e
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 36 deletions.
5 changes: 3 additions & 2 deletions crates/bevy_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ pub mod prelude {
pub use crate::{
app::App,
main_schedule::{
First, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup, PreUpdate,
SpawnScene, Startup, StateTransition, Update,
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
PostStartup, PostUpdate, PreStartup, PreUpdate, SpawnScene, Startup, StateTransition,
Update,
},
DynamicPlugin, Plugin, PluginGroup,
};
Expand Down
115 changes: 101 additions & 14 deletions crates/bevy_app/src/main_schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ use bevy_ecs::{
/// * [`First`]
/// * [`PreUpdate`]
/// * [`StateTransition`]
/// * [`RunFixedUpdateLoop`]
/// * This will run [`FixedUpdate`] zero to many times, based on how much time has elapsed.
/// * [`RunFixedMainLoop`]
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
/// * [`Update`]
/// * [`PostUpdate`]
/// * [`Last`]
Expand Down Expand Up @@ -67,25 +67,62 @@ pub struct PreUpdate;
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
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.
#[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 exclusive `run_fixed_update_schedule` system runs this schedule.
/// This is run by the [`RunFixedUpdateLoop`] schedule.
/// The exclusive `run_fixed_main_schedule` system runs this schedule.
/// This is run by the [`RunFixedMainLoop`] schedule.
///
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
/// See [this example](https:/bevyengine/bevy/blob/latest/examples/time/time.rs).
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[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.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -131,7 +168,7 @@ impl Default for MainScheduleOrder {
First.intern(),
PreUpdate.intern(),
StateTransition.intern(),
RunFixedUpdateLoop.intern(),
RunFixedMainLoop.intern(),
Update.intern(),
SpawnScene.intern(),
PostUpdate.intern(),
Expand Down Expand Up @@ -188,20 +225,70 @@ 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;

impl Plugin for MainSchedulePlugin {
fn build(&self, app: &mut App) {
// simple "facilitator" schedules benefit from simpler single threaded scheduling
let mut main_schedule = Schedule::new(Main);
main_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_update_loop_schedule = Schedule::new(RunFixedUpdateLoop);
fixed_update_loop_schedule.set_executor_kind(ExecutorKind::SingleThreaded);
let mut fixed_main_schedule = Schedule::new(FixedMain);
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)
.add_schedule(fixed_update_loop_schedule)
.add_schedule(fixed_main_schedule)
.add_schedule(fixed_main_loop_schedule)
.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);
}
});
}
}
33 changes: 19 additions & 14 deletions crates/bevy_time/src/fixed.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use bevy_app::FixedMain;
use bevy_ecs::world::World;
use bevy_reflect::Reflect;
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.
///
Expand All @@ -12,7 +13,8 @@ use crate::{time::Time, virt::Virtual, FixedUpdate};
/// It is automatically inserted as a resource by
/// [`TimePlugin`](crate::TimePlugin) and updated based on
/// [`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
/// extremely useful for writing logic (like physics) that should have
Expand All @@ -26,7 +28,9 @@ use crate::{time::Time, virt::Virtual, FixedUpdate};
/// frame). Additionally, the value is a power of two which losslessly converts
/// 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
/// [`PreUpdate`](bevy_app::PreUpdate) and [`Update`](bevy_app::Update)
/// according to the accumulated [`overstep()`](Time::overstep) time divided by
Expand All @@ -43,20 +47,21 @@ use crate::{time::Time, virt::Virtual, FixedUpdate};
/// means it is affected by [`pause()`](Time::pause),
/// [`set_relative_speed()`](Time::set_relative_speed) and
/// [`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
/// that the [`elapsed()`](Time::elapsed) time in `Time<Fixed>` is always
/// between the previous `elapsed()` and the current `elapsed()` value in
/// `Time<Virtual>`, so the values are compatible.
/// clock is paused, the [`FixedUpdate`](bevy_app::FixedUpdate) schedule will
/// not run. It is guaranteed that the [`elapsed()`](Time::elapsed) time in
/// `Time<Fixed>` is always between the previous `elapsed()` and the current
/// `elapsed()` value in `Time<Virtual>`, so the values are compatible.
///
/// 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
/// 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,
/// [`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`]
/// schedule, meaning that it will affect the [`delta()`](Time::delta) value for
/// the very next [`FixedUpdate`], even if it is still during the same frame.
/// Any [`overstep()`](Time::overstep) present in the accumulator will be
/// new value will be used immediately for the next run of the
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule, meaning that it will affect
/// the [`delta()`](Time::delta) value for the very next
/// [`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.
#[derive(Debug, Copy, Clone, Reflect)]
pub struct Fixed {
Expand Down Expand Up @@ -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`]
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();
world.resource_mut::<Time<Fixed>>().accumulate(delta);

// 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() {
*world.resource_mut::<Time>() = world.resource::<Time<Fixed>>().as_generic();
schedule.run(world);
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub mod prelude {
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::prelude::*;
use bevy_utils::{tracing::warn, Duration, Instant};
Expand Down Expand Up @@ -57,11 +57,11 @@ impl Plugin for TimePlugin {
First,
(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>()
.add_systems(FixedUpdate, event_queue_update_system);
.add_systems(FixedPostUpdate, event_queue_update_system);

#[cfg(feature = "bevy_ci_testing")]
if let Some(ci_testing_config) = app
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_time/src/time.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bevy_utils::Duration;
/// virtual time.
/// - [`Time`] is a generic clock that corresponds to "current" or "default"
/// 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).
///
/// The time elapsed since the previous time this clock was advanced is saved as
Expand Down Expand Up @@ -45,7 +45,7 @@ use bevy_utils::Duration;
/// [`elapsed()`](Time::elapsed) should use `Res<Time>` to access the default
/// time configured for the program. By default, this refers to
/// [`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
/// either in [`Update`](bevy_app::Update) or
/// [`FixedUpdate`](bevy_app::FixedUpdate) schedule depending on what is needed.
Expand Down

0 comments on commit fe28e0e

Please sign in to comment.