Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow ordering variable timesteps around fixed timesteps #14881

Merged
merged 13 commits into from
Aug 23, 2024
5 changes: 3 additions & 2 deletions crates/bevy_app/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,9 @@ pub mod prelude {
pub use crate::{
app::{App, AppExit},
main_schedule::{
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
PostStartup, PostUpdate, PreStartup, PreUpdate, SpawnScene, Startup, Update,
AroundFixedMainLoopSystem, First, FixedFirst, FixedLast, FixedPostUpdate,
FixedPreUpdate, FixedUpdate, Last, Main, PostStartup, PostUpdate, PreStartup,
PreUpdate, RunFixedMainLoop, SpawnScene, Startup, Update,
},
sub_app::SubApp,
Plugin, PluginGroup,
Expand Down
55 changes: 52 additions & 3 deletions crates/bevy_app/src/main_schedule.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{App, Plugin};
use bevy_ecs::{
schedule::{ExecutorKind, InternedScheduleLabel, Schedule, ScheduleLabel},
schedule::{
ExecutorKind, InternedScheduleLabel, IntoSystemSetConfigs, Schedule, ScheduleLabel,
SystemSet,
},
system::{Local, Resource},
world::{Mut, World},
};
Expand Down Expand Up @@ -75,6 +78,8 @@ pub struct First;
pub struct PreUpdate;

/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
/// If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`AroundFixedMainLoopSystem`] system set.
///
/// See the [`Main`] schedule for some details about how schedules are run.
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
Expand Down Expand Up @@ -127,7 +132,8 @@ pub struct FixedLast;
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
///
/// The exclusive `run_fixed_main_schedule` system runs this schedule.
/// This is run by the [`RunFixedMainLoop`] schedule.
/// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems
/// before or after the fixed update logic, use the [`AroundFixedMainLoopSystem`] system set.
///
/// 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).
Expand Down Expand Up @@ -288,7 +294,15 @@ impl Plugin for MainSchedulePlugin {
.init_resource::<MainScheduleOrder>()
.init_resource::<FixedMainScheduleOrder>()
.add_systems(Main, Main::run_main)
.add_systems(FixedMain, FixedMain::run_fixed_main);
.add_systems(FixedMain, FixedMain::run_fixed_main)
.configure_sets(
RunFixedMainLoop,
(
AroundFixedMainLoopSystem::Before,
AroundFixedMainLoopSystem::After,
)
.chain(),
);

#[cfg(feature = "bevy_debug_stepping")]
{
Expand Down Expand Up @@ -352,3 +366,38 @@ impl FixedMain {
});
}
}

/// Set enum for the systems that want to run inside [`RunFixedMainLoop`],
/// but before or after the fixed update logic. Systems in this set
/// will run exactly once per frame, regardless of the number of fixed updates.
/// They will also run under a variable timestep.
///
/// This is useful for handling things that need to run every frame, but
/// also need to be read by the fixed update logic. A good example of this
/// is camera movement, which needs to be updated in a variable timestep,
/// as you want the camera to move with as much precision and updates as
/// the frame rate allows. A physics system that needs to read the camera
/// position and orientation, however, should run in the fixed update logic,
/// as it needs to be deterministic and run at a fixed rate for better stability.
/// Note that we are not placing the camera movement system in `Update`, as that
/// would mean that the physics system already ran at that point.
///
/// # Example
/// ```
/// # use bevy_ecs::prelude::*;
/// App::new()
/// .add_systems(
/// RunFixedMainLoop,
/// update_camera_rotation.in_set(AroundFixedMainLoopSystem::Before))
/// .add_systems(FixedMain, update_physics);
///
/// # fn update_camera_rotation() {}
/// # fn update_physics() {}
/// ```
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
janhohenheim marked this conversation as resolved.
Show resolved Hide resolved
pub enum AroundFixedMainLoopSystem {
janhohenheim marked this conversation as resolved.
Show resolved Hide resolved
/// Runs before the fixed update logic
janhohenheim marked this conversation as resolved.
Show resolved Hide resolved
Before,
/// Runs after the fixed update logic
janhohenheim marked this conversation as resolved.
Show resolved Hide resolved
After,
}
4 changes: 2 additions & 2 deletions crates/bevy_gizmos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,13 +234,13 @@ impl AppGizmoBuilder for App {
.init_resource::<GizmoStorage<Config, Swap<Fixed>>>()
.add_systems(
RunFixedMainLoop,
start_gizmo_context::<Config, Fixed>.before(bevy_time::run_fixed_main_schedule),
start_gizmo_context::<Config, Fixed>.in_set(bevy_app::AroundFixedMainLoopSystem::Before),
)
.add_systems(FixedFirst, clear_gizmo_context::<Config, Fixed>)
.add_systems(FixedLast, collect_requested_gizmos::<Config, Fixed>)
.add_systems(
RunFixedMainLoop,
end_gizmo_context::<Config, Fixed>.after(bevy_time::run_fixed_main_schedule),
end_gizmo_context::<Config, Fixed>.in_set(bevy_app::AroundFixedMainLoopSystem::After),
)
.add_systems(
Last,
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_time/src/fixed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ impl Default for Fixed {
}

/// Runs [`FixedMain`] zero or more times based on delta of
/// [`Time<Virtual>`](Virtual) and [`Time::overstep`]
/// [`Time<Virtual>`](Virtual) and [`Time::overstep`].
/// You can order your systems relative to this by using
/// [`AroundFixedMainLoopSystem`](bevy_app::prelude::AroundFixedMainLoopSystem).
pub fn run_fixed_main_schedule(world: &mut World) {
let delta = world.resource::<Time<Virtual>>().delta();
world.resource_mut::<Time<Fixed>>().accumulate(delta);
Expand Down
7 changes: 6 additions & 1 deletion crates/bevy_time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,12 @@ impl Plugin for TimePlugin {
.in_set(TimeSystem)
.ambiguous_with(event_update_system),
)
.add_systems(RunFixedMainLoop, run_fixed_main_schedule);
.add_systems(
RunFixedMainLoop,
run_fixed_main_schedule
.after(AroundFixedMainLoopSystem::Before)
.before(AroundFixedMainLoopSystem::After),
);

// Ensure the events are not dropped until `FixedMain` systems can observe them
app.add_systems(FixedPostUpdate, signal_event_update_system);
Expand Down
Loading