From fe28e0ec32017a9f01462d7e10c1faea9dc760f8 Mon Sep 17 00:00:00 2001 From: Aceeri Date: Wed, 13 Dec 2023 20:35:40 -0800 Subject: [PATCH] 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`. --- crates/bevy_app/src/lib.rs | 5 +- crates/bevy_app/src/main_schedule.rs | 115 +++++++++++++++++++++++---- crates/bevy_time/src/fixed.rs | 33 ++++---- crates/bevy_time/src/lib.rs | 8 +- crates/bevy_time/src/time.rs | 4 +- 5 files changed, 129 insertions(+), 36 deletions(-) diff --git a/crates/bevy_app/src/lib.rs b/crates/bevy_app/src/lib.rs index 9aaabc25ee696..dfcd0dc99db1f 100644 --- a/crates/bevy_app/src/lib.rs +++ b/crates/bevy_app/src/lib.rs @@ -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, }; diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index 947abc54b2b2d..24872e9950f76 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -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`] @@ -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` resource, 64 Hz by default. /// 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. #[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)] @@ -131,7 +168,7 @@ impl Default for MainScheduleOrder { First.intern(), PreUpdate.intern(), StateTransition.intern(), - RunFixedUpdateLoop.intern(), + RunFixedMainLoop.intern(), Update.intern(), SpawnScene.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; impl Plugin for MainSchedulePlugin { @@ -196,12 +233,62 @@ impl Plugin for MainSchedulePlugin { // 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::() - .add_systems(Main, Main::run_main); + .init_resource::() + .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, +} + +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| { + for &label in &order.labels { + let _ = world.try_run_schedule(label); + } + }); } } diff --git a/crates/bevy_time/src/fixed.rs b/crates/bevy_time/src/fixed.rs index faef1d7d38839..9e5314d4d880a 100644 --- a/crates/bevy_time/src/fixed.rs +++ b/crates/bevy_time/src/fixed.rs @@ -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. /// @@ -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). 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 @@ -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 @@ -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` is always -/// between the previous `elapsed()` and the current `elapsed()` value in -/// `Time`, 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` is always between the previous `elapsed()` and the current +/// `elapsed()` value in `Time`, 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 { @@ -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) 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::>().delta(); world.resource_mut::>().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::>().expend() { *world.resource_mut::