diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 0e9534d63b237..28708df2a0269 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -552,7 +552,9 @@ impl Plugin for AnimationPlugin { .register_type::() .add_systems( PostUpdate, - animation_player.before(TransformSystem::TransformPropagate), + animation_player + .before(TransformSystem::TransformPropagate) + .ignore_stepping(), ); } } diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index d7a4da63cd1b0..346eaa11f3a2b 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -3,11 +3,12 @@ use crate::{ }; pub use bevy_derive::AppLabel; use bevy_ecs::{ + event::ManualEventReader, prelude::*, schedule::{ apply_state_transition, common_conditions::run_once as run_once_condition, run_enter_schedule, BoxedScheduleLabel, IntoSystemConfigs, IntoSystemSetConfigs, - ScheduleLabel, + ScheduleEvent, ScheduleLabel, }, }; use bevy_utils::{tracing::debug, HashMap, HashSet}; @@ -82,6 +83,9 @@ pub struct App { plugin_name_added: HashSet, /// A private counter to prevent incorrect calls to `App::run()` from `Plugin::build()` building_plugin_depth: usize, + + /// Tracks which system stepping events have been processed + schedule_event_reader: ManualEventReader, } impl Debug for App { @@ -192,6 +196,7 @@ impl Default for App { app.add_plugin(MainSchedulePlugin); app.add_event::(); + app.add_event::(); #[cfg(feature = "bevy_ci_testing")] { @@ -223,6 +228,7 @@ impl App { plugin_name_added: Default::default(), main_schedule_label: Box::new(Main), building_plugin_depth: 0, + schedule_event_reader: ManualEventReader::::default(), } } @@ -250,6 +256,29 @@ impl App { sub_app.run(); } + // move schedules out of the world so we can modify it as we iterate + // through the schedule events + let mut schedules = self.world.remove_resource::().unwrap(); + + if let Some(mut schedule_events) = self.world.get_resource_mut::>() { + use ScheduleEvent::*; + + for event in self.schedule_event_reader.iter(&schedule_events) { + match event { + EnableStepping(label) + | DisableStepping(label) + | ToggleStepping(label) + | StepFrame(label) + | StepSystem(label) => { + schedules.get_mut(&**label).unwrap().handle_event(event); + } + } + } + schedule_events.clear(); + } + + // Reinsert the Schedules resource, as we're now done with it + self.world.insert_resource(schedules); self.world.clear_trackers(); } diff --git a/crates/bevy_app/src/main_schedule.rs b/crates/bevy_app/src/main_schedule.rs index 2042b9e01f4e8..8e1addd6c8097 100644 --- a/crates/bevy_app/src/main_schedule.rs +++ b/crates/bevy_app/src/main_schedule.rs @@ -1,6 +1,6 @@ use crate::{App, Plugin}; use bevy_ecs::{ - schedule::{ExecutorKind, Schedule, ScheduleLabel}, + schedule::{ExecutorKind, IntoSystemConfigs, Schedule, ScheduleLabel}, system::{Local, Resource}, world::{Mut, World}, }; @@ -163,6 +163,6 @@ impl Plugin for MainSchedulePlugin { app.add_schedule(Main, main_schedule) .add_schedule(RunFixedUpdateLoop, fixed_update_loop_schedule) .init_resource::() - .add_systems(Main, Main::run_main); + .add_systems(Main, Main::run_main.ignore_stepping()); } } diff --git a/crates/bevy_app/tests/stepping_test.rs b/crates/bevy_app/tests/stepping_test.rs new file mode 100644 index 0000000000000..8cfcd3819ef7f --- /dev/null +++ b/crates/bevy_app/tests/stepping_test.rs @@ -0,0 +1,32 @@ +mod stepping { + use bevy_app::prelude::*; + use bevy_app::App; + use bevy_ecs::prelude::*; + use bevy_ecs::schedule::ScheduleEvent; + + // verify App::update() ScheduleEvents behavior + #[test] + fn app_update_schedule_events() { + let mut app = App::new(); + + // add a system to write a ScheduleEvent + app.add_systems(Update, |mut schedule_events: EventWriter| { + schedule_events.send(ScheduleEvent::EnableStepping(Box::new(Main))); + }); + + // ensure stepping isn't enabled on the schedule + let schedule = app.get_schedule(Main).unwrap(); + assert!(!schedule.stepping()); + + app.update(); + + // verify the event was sent to the schedule by verifing stepping has + // been turned on + let schedule = app.get_schedule(Main).unwrap(); + assert!(schedule.stepping()); + + // verify the ScheduleEvent list was cleared + let schedule_events = app.world.get_resource::>().unwrap(); + assert!(schedule_events.is_empty()); + } +} diff --git a/crates/bevy_asset/src/debug_asset_server.rs b/crates/bevy_asset/src/debug_asset_server.rs index 955fadb80f0ac..994295c1ac241 100644 --- a/crates/bevy_asset/src/debug_asset_server.rs +++ b/crates/bevy_asset/src/debug_asset_server.rs @@ -75,7 +75,7 @@ impl Plugin for DebugAssetServerPlugin { watch_for_changes: true, }); app.insert_non_send_resource(DebugAssetApp(debug_asset_app)); - app.add_systems(Update, run_debug_asset_app); + app.add_systems(Update, run_debug_asset_app.ignore_stepping()); } } diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 3265690165738..fa43091f30915 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -47,7 +47,7 @@ pub use path::*; pub use reflect::*; use bevy_app::{prelude::*, MainScheduleOrder}; -use bevy_ecs::schedule::ScheduleLabel; +use bevy_ecs::schedule::{IntoSystemConfigs, ScheduleLabel}; /// Asset storages are updated. #[derive(Debug, Hash, PartialEq, Eq, Clone, ScheduleLabel)] @@ -103,8 +103,10 @@ impl Plugin for AssetPlugin { app.insert_resource(asset_server); } - app.register_type::() - .add_systems(PreUpdate, asset_server::free_unused_assets_system); + app.register_type::().add_systems( + PreUpdate, + asset_server::free_unused_assets_system.ignore_stepping(), + ); app.init_schedule(LoadAssets); app.init_schedule(AssetEvents); diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 468ff3d622c24..37e9f6bfa29d2 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -47,6 +47,7 @@ pub use sinks::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Asset}; +use bevy_ecs::schedule::IntoSystemConfigs; /// Adds support for audio playback to a Bevy Application /// @@ -61,7 +62,10 @@ impl Plugin for AudioPlugin { .add_asset::() .add_asset::() .init_resource::>() - .add_systems(PostUpdate, play_queued_audio_system::); + .add_systems( + PostUpdate, + play_queued_audio_system::.ignore_stepping(), + ); #[cfg(any(feature = "mp3", feature = "flac", feature = "wav", feature = "vorbis"))] app.init_asset_loader::(); diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index daf43cf86e63b..81fbc39e83110 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -107,7 +107,7 @@ impl Plugin for TaskPoolPlugin { self.task_pool_options.create_default_pools(); #[cfg(not(target_arch = "wasm32"))] - _app.add_systems(Last, tick_global_task_pools); + _app.add_systems(Last, tick_global_task_pools.ignore_stepping()); } } /// A dummy type that is [`!Send`](Send), to force systems to run on the main thread. @@ -142,7 +142,7 @@ pub struct FrameCountPlugin; impl Plugin for FrameCountPlugin { fn build(&self, app: &mut App) { app.init_resource::(); - app.add_systems(Last, update_frame_count); + app.add_systems(Last, update_frame_count.ignore_stepping()); } } diff --git a/crates/bevy_core_pipeline/src/bloom/mod.rs b/crates/bevy_core_pipeline/src/bloom/mod.rs index 62e577ea23f13..0779d509596ba 100644 --- a/crates/bevy_core_pipeline/src/bloom/mod.rs +++ b/crates/bevy_core_pipeline/src/bloom/mod.rs @@ -72,7 +72,8 @@ impl Plugin for BloomPlugin { prepare_downsampling_pipeline.in_set(RenderSet::Prepare), prepare_upsampling_pipeline.in_set(RenderSet::Prepare), queue_bloom_bind_groups.in_set(RenderSet::Queue), - ), + ) + .ignore_stepping(), ); // Add bloom to the 3d render graph diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index e682257dc980a..2d8407efc7986 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -52,7 +52,10 @@ impl Plugin for Core2dPlugin { render_app .init_resource::>() - .add_systems(ExtractSchedule, extract_core_2d_camera_phases) + .add_systems( + ExtractSchedule, + extract_core_2d_camera_phases.ignore_stepping(), + ) .add_systems( Render, ( @@ -60,7 +63,8 @@ impl Plugin for Core2dPlugin { batch_phase_system:: .after(sort_phase_system::) .in_set(RenderSet::PhaseSort), - ), + ) + .ignore_stepping(), ); let pass_node_2d = MainPass2dNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 9bfa5486dfb8e..19c4a9ef49b46 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -68,7 +68,10 @@ impl Plugin for Core3dPlugin { .init_resource::>() .init_resource::>() .init_resource::>() - .add_systems(ExtractSchedule, extract_core_3d_camera_phases) + .add_systems( + ExtractSchedule, + extract_core_3d_camera_phases.ignore_stepping(), + ) .add_systems( Render, ( @@ -78,7 +81,8 @@ impl Plugin for Core3dPlugin { sort_phase_system::.in_set(RenderSet::PhaseSort), sort_phase_system::.in_set(RenderSet::PhaseSort), sort_phase_system::.in_set(RenderSet::PhaseSort), - ), + ) + .ignore_stepping(), ); let prepass_node = PrepassNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 943e2d39b8eb2..8345d354123a9 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -90,7 +90,12 @@ impl Plugin for FxaaPlugin { render_app .init_resource::() .init_resource::>() - .add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare)); + .add_systems( + Render, + prepare_fxaa_pipelines + .in_set(RenderSet::Prepare) + .ignore_stepping(), + ); { let fxaa_node = FxaaNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/msaa_writeback.rs b/crates/bevy_core_pipeline/src/msaa_writeback.rs index 115b085efa320..655a0e7431585 100644 --- a/crates/bevy_core_pipeline/src/msaa_writeback.rs +++ b/crates/bevy_core_pipeline/src/msaa_writeback.rs @@ -22,7 +22,9 @@ impl Plugin for MsaaWritebackPlugin { render_app.add_systems( Render, - queue_msaa_writeback_pipelines.in_set(RenderSet::Queue), + queue_msaa_writeback_pipelines + .in_set(RenderSet::Queue) + .ignore_stepping(), ); let msaa_writeback_2d = MsaaWritebackNode::new(&mut render_app.world); let msaa_writeback_3d = MsaaWritebackNode::new(&mut render_app.world); diff --git a/crates/bevy_core_pipeline/src/tonemapping/mod.rs b/crates/bevy_core_pipeline/src/tonemapping/mod.rs index 31ecd12177d93..9ce73dd66e4d8 100644 --- a/crates/bevy_core_pipeline/src/tonemapping/mod.rs +++ b/crates/bevy_core_pipeline/src/tonemapping/mod.rs @@ -96,7 +96,9 @@ impl Plugin for TonemappingPlugin { .init_resource::>() .add_systems( Render, - queue_view_tonemapping_pipelines.in_set(RenderSet::Queue), + queue_view_tonemapping_pipelines + .in_set(RenderSet::Queue) + .ignore_stepping(), ); } } diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index f3594397d5120..b6646dbca6499 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -16,7 +16,9 @@ impl Plugin for UpscalingPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app.add_systems( Render, - queue_view_upscaling_pipelines.in_set(RenderSet::Queue), + queue_view_upscaling_pipelines + .in_set(RenderSet::Queue) + .ignore_stepping(), ); } } diff --git a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs index 5c4415c09b621..52ea8b621abba 100644 --- a/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/entity_count_diagnostics_plugin.rs @@ -9,8 +9,8 @@ pub struct EntityCountDiagnosticsPlugin; impl Plugin for EntityCountDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, Self::setup_system) - .add_systems(Update, Self::diagnostic_system); + app.add_systems(Startup, Self::setup_system.ignore_stepping()) + .add_systems(Update, Self::diagnostic_system.ignore_stepping()); } } diff --git a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs index 4dbc3c4cfa92d..47bf78e074503 100644 --- a/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/frame_time_diagnostics_plugin.rs @@ -10,8 +10,8 @@ pub struct FrameTimeDiagnosticsPlugin; impl Plugin for FrameTimeDiagnosticsPlugin { fn build(&self, app: &mut bevy_app::App) { - app.add_systems(Startup, Self::setup_system) - .add_systems(Update, Self::diagnostic_system); + app.add_systems(Startup, Self::setup_system.ignore_stepping()) + .add_systems(Update, Self::diagnostic_system.ignore_stepping()); } } diff --git a/crates/bevy_diagnostic/src/lib.rs b/crates/bevy_diagnostic/src/lib.rs index 854f6e74cd854..b4b4cfa0b2e4e 100644 --- a/crates/bevy_diagnostic/src/lib.rs +++ b/crates/bevy_diagnostic/src/lib.rs @@ -5,6 +5,7 @@ mod log_diagnostics_plugin; mod system_information_diagnostics_plugin; use bevy_app::prelude::*; +use bevy_ecs::schedule::IntoSystemConfigs; pub use diagnostic::*; pub use entity_count_diagnostics_plugin::EntityCountDiagnosticsPlugin; pub use frame_time_diagnostics_plugin::FrameTimeDiagnosticsPlugin; @@ -19,7 +20,7 @@ impl Plugin for DiagnosticsPlugin { fn build(&self, app: &mut App) { app.init_resource::().add_systems( Startup, - system_information_diagnostics_plugin::internal::log_system_info, + system_information_diagnostics_plugin::internal::log_system_info.ignore_stepping(), ); } } diff --git a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs index 65f212dcc60ca..ad346b00e711c 100644 --- a/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/log_diagnostics_plugin.rs @@ -37,9 +37,12 @@ impl Plugin for LogDiagnosticsPlugin { }); if self.debug { - app.add_systems(PostUpdate, Self::log_diagnostics_debug_system); + app.add_systems( + PostUpdate, + Self::log_diagnostics_debug_system.ignore_stepping(), + ); } else { - app.add_systems(PostUpdate, Self::log_diagnostics_system); + app.add_systems(PostUpdate, Self::log_diagnostics_system.ignore_stepping()); } } } diff --git a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs index b33cba5ca468c..44ed6ecfeea63 100644 --- a/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs +++ b/crates/bevy_diagnostic/src/system_information_diagnostics_plugin.rs @@ -1,5 +1,6 @@ use crate::DiagnosticId; use bevy_app::prelude::*; +use bevy_ecs::schedule::IntoSystemConfigs; /// Adds a System Information Diagnostic, specifically `cpu_usage` (in %) and `mem_usage` (in %) /// @@ -14,8 +15,8 @@ use bevy_app::prelude::*; pub struct SystemInformationDiagnosticsPlugin; impl Plugin for SystemInformationDiagnosticsPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, internal::setup_system) - .add_systems(Update, internal::diagnostic_system); + app.add_systems(Startup, internal::setup_system.ignore_stepping()) + .add_systems(Update, internal::diagnostic_system.ignore_stepping()); } } diff --git a/crates/bevy_ecs/src/schedule/config.rs b/crates/bevy_ecs/src/schedule/config.rs index 50aa7bfe9010a..4ca6b8b33db17 100644 --- a/crates/bevy_ecs/src/schedule/config.rs +++ b/crates/bevy_ecs/src/schedule/config.rs @@ -48,10 +48,23 @@ impl IntoSystemConfigs<()> for BoxedSystem<(), ()> { } } +#[derive(Default, Clone, Copy, PartialEq)] +pub(crate) enum SteppingBehavior { + /// permit this system to be skipped when the + /// [`Schedule`](`crate::schedule::Schedule`) is executing in stepping + /// mode + #[default] + PermitStepping, + /// this system will run regardless of the + /// [`Schedule`](`crate::schedule::Schedule`) stepping mode + IgnoreStepping, +} + pub struct SystemConfig { pub(crate) system: BoxedSystem, pub(crate) graph_info: GraphInfo, pub(crate) conditions: Vec, + pub(super) stepping_behavior: SteppingBehavior, } /// A collection of [`SystemConfig`]. @@ -75,6 +88,7 @@ impl SystemConfigs { ..Default::default() }, conditions: Vec::new(), + stepping_behavior: SteppingBehavior::PermitStepping, }) } @@ -172,6 +186,32 @@ impl SystemConfigs { } } } + + fn ignore_stepping_inner(&mut self) { + match self { + SystemConfigs::SystemConfig(config) => { + config.stepping_behavior = SteppingBehavior::IgnoreStepping; + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.ignore_stepping_inner(); + } + } + } + } + + fn permit_stepping_inner(&mut self) { + match self { + SystemConfigs::SystemConfig(config) => { + config.stepping_behavior = SteppingBehavior::PermitStepping; + } + SystemConfigs::Configs { configs, .. } => { + for config in configs { + config.permit_stepping_inner(); + } + } + } + } } /// Types that can convert into a [`SystemConfigs`]. @@ -260,6 +300,21 @@ where self.into_configs().chain() } + /// Ignore stepping for this set of systems. + /// + /// When stepping mode is enabled, these systems will still be run. + fn ignore_stepping(self) -> SystemConfigs { + self.into_configs().ignore_stepping() + } + + /// Permit stepping for this set of systems. + /// + /// When stepping mode is enabled, these systems will only be run when a + /// step is performed. This is the default for all systems. + fn permit_stepping(self) -> SystemConfigs { + self.into_configs().permit_stepping() + } + /// This used to add the system to `CoreSchedule::Startup`. /// This was a shorthand for `self.in_schedule(CoreSchedule::Startup)`. /// @@ -349,6 +404,16 @@ impl IntoSystemConfigs<()> for SystemConfigs { } self } + + fn ignore_stepping(mut self) -> Self { + self.ignore_stepping_inner(); + self + } + + fn permit_stepping(mut self) -> Self { + self.permit_stepping_inner(); + self + } } pub struct SystemConfigTupleMarker; diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index 5de07a52f5493..67157029560a4 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -59,6 +59,8 @@ pub struct SystemSchedule { pub(super) system_dependents: Vec>, pub(super) sets_with_conditions_of_systems: Vec, pub(super) systems_in_sets_with_conditions: Vec, + pub(super) systems_with_stepping_enabled: FixedBitSet, + pub(super) step_state: StepState, } impl SystemSchedule { @@ -73,8 +75,79 @@ impl SystemSchedule { system_dependents: Vec::new(), sets_with_conditions_of_systems: Vec::new(), systems_in_sets_with_conditions: Vec::new(), + systems_with_stepping_enabled: FixedBitSet::new(), + step_state: StepState::RunAll, } } + + /// Return the index of the next system to be run if this schedule is + /// stepped + pub fn stepping_next_system_index(&self) -> Option { + let index = match self.step_state { + StepState::RunAll => None, + StepState::Wait(i) | StepState::Next(i) | StepState::Remaining(i) => Some(i), + }?; + + self.find_stepping_system(index) + } + + /// This method returns the list of systems to be skipped when the + /// system executor runs, and updates `step_state` to prepare for the + /// next call. + pub fn step(&mut self) -> Option { + match self.step_state { + StepState::RunAll => None, + StepState::Wait(_) => Some(self.systems_with_stepping_enabled.clone()), + StepState::Next(index) => { + let next = self.find_stepping_system(index)?; + + // clone the list of stepping systems, then disable + let mut mask = self.systems_with_stepping_enabled.clone(); + mask.toggle(next); + + // Transition to the wait state. it's safe to set the value + // beyond the number of systems in the schedule. All uses of + // that value use `find_stepping_system`, which will wrap it + // to a safe value. + self.step_state = StepState::Wait(next + 1); + + Some(mask) + } + StepState::Remaining(index) => { + let next = self.find_stepping_system(index)?; + + // We need to mark all systems that observe stepping prior + // to `next` as completed. We do this in three steps: + // + // 1. set the bit for every system below `next` in a bitset + // 2. clear the bits for every system ignoring stepping; + // we do this with a bitwise AND between the mask and + // those systems that are observing stepping + // 3. We set those bits in the completed_systems bitmask by + // using a bitwise OR. + // + let mut mask = FixedBitSet::with_capacity(self.systems.len()); + mask.insert_range(0..next); + mask &= &self.systems_with_stepping_enabled; + + // transition to wait state, starting at the first system + self.step_state = StepState::Wait(0); + + Some(mask) + } + } + } + + /// starting at system index `start`, return the index of the first system + /// that has stepping enabled. + fn find_stepping_system(&self, start: usize) -> Option { + for i in start..self.systems_with_stepping_enabled.len() { + if self.systems_with_stepping_enabled[i] { + return Some(i); + } + } + (0..start).find(|i| self.systems_with_stepping_enabled[*i]) + } } /// Instructs the executor to call [`apply_buffers`](crate::system::System::apply_buffers) @@ -92,3 +165,25 @@ pub(super) fn is_apply_system_buffers(system: &BoxedSystem) -> bool { // deref to use `System::type_id` instead of `Any::type_id` system.as_ref().type_id() == apply_system_buffers.type_id() } + +/// Stepping state, stored in [`SystemSchedule`], used by [`SystemExecutor`] to +/// determine which systems in the schedule should be run. +#[derive(Default, Copy, Clone, PartialEq, Debug)] +pub(super) enum StepState { + /// Run only systems that are ignoring stepping; + /// see [`ignore_stepping`](`super::IntoSystemConfigs::ignore_stepping`) + Wait(usize), + /// Run the next system in the schedule that does not ignore stepping, , + /// along with all systems that [`ignore + /// stepping`](`super::IntoSystemConfigs::ignore_stepping`). + Next(usize), + + /// Run all remaining systems in the schedule that have not yet been run, + /// along with all systems that + /// [`ignore stepping`](`super::IntoSystemConfigs::ignore_stepping`). + Remaining(usize), + + /// Stepping is disabled; run all systems. + #[default] + RunAll, +} diff --git a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs index 80c6f27d2e3d6..29c638ba57cf3 100644 --- a/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/multi_threaded.rs @@ -164,6 +164,29 @@ impl SystemExecutor for MultiThreadedExecutor { } } + // If stepping is enabled, make sure we skip those systems that should + // not be run. + if let Some(mut skipped_systems) = schedule.step() { + // mark skipped systems as completed + self.completed_systems |= &skipped_systems; + self.num_completed_systems = self.completed_systems.count_ones(..); + + // signal the dependencies for each of the skipped systems, as + // though they had run + for system_index in skipped_systems.ones() { + self.signal_dependents(system_index); + } + + // Finally, we need to clear all skipped systems from the ready + // list. + // + // We invert the skipped system mask to get the list of systems + // that should be run. Then we bitwise AND it with the ready list, + // resulting in a list of ready systems that aren't skipped. + skipped_systems.toggle_range(..); + self.ready_systems &= skipped_systems; + } + let thread_executor = world .get_resource::() .map(|e| e.0.clone()); diff --git a/crates/bevy_ecs/src/schedule/executor/simple.rs b/crates/bevy_ecs/src/schedule/executor/simple.rs index 2aaf1777ed033..43829f1afd6c1 100644 --- a/crates/bevy_ecs/src/schedule/executor/simple.rs +++ b/crates/bevy_ecs/src/schedule/executor/simple.rs @@ -34,6 +34,13 @@ impl SystemExecutor for SimpleExecutor { } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + // If stepping is enabled, make sure we skip those systems that should + // not be run. + if let Some(skipped_systems) = schedule.step() { + // mark skipped systems as completed + self.completed_systems |= &skipped_systems; + } + for system_index in 0..schedule.systems.len() { #[cfg(feature = "trace")] let name = schedule.systems[system_index].name(); diff --git a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs index 61b4520684e68..297b0f5ec7e96 100644 --- a/crates/bevy_ecs/src/schedule/executor/single_threaded.rs +++ b/crates/bevy_ecs/src/schedule/executor/single_threaded.rs @@ -44,6 +44,13 @@ impl SystemExecutor for SingleThreadedExecutor { } fn run(&mut self, schedule: &mut SystemSchedule, world: &mut World) { + // If stepping is enabled, make sure we skip those systems that should + // not be run. + if let Some(skipped_systems) = schedule.step() { + // mark skipped systems as completed + self.completed_systems |= &skipped_systems; + } + for system_index in 0..schedule.systems.len() { #[cfg(feature = "trace")] let name = schedule.systems[system_index].name(); @@ -51,6 +58,7 @@ impl SystemExecutor for SingleThreadedExecutor { let should_run_span = info_span!("check_conditions", name = &*name).entered(); let mut should_run = !self.completed_systems.contains(system_index); + for set_idx in schedule.sets_with_conditions_of_systems[system_index].ones() { if self.evaluated_sets.contains(set_idx) { continue; diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 05aaba9f8b33c..2d9d8aebd24ac 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -708,4 +708,239 @@ mod tests { assert!(matches!(result, Err(ScheduleBuildError::Ambiguity))); } } + + mod system_stepping { + use super::*; + use ScheduleEvent::*; + + // We need a ScheduleLabel to put in `ScheduleEvent` within our tests. + // The actual value of the label does not matter to + // `Schedule::handle_event()`, so we're creating one here for testing. + // I also don't want to have these tests dependent on bevy_app. + #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] + pub struct TestSchedule; + + /// Enable stepping for a given `schedule` + fn enable_stepping(schedule: &mut Schedule) { + schedule.handle_event(&EnableStepping(Box::new(TestSchedule))); + assert!(schedule.stepping()); + } + + // step forward a single system in a stepping `schedule` + fn step_system(schedule: &mut Schedule) { + schedule.handle_event(&StepSystem(Box::new(TestSchedule))); + } + + // step forward an entire frame in a stepping `schedule` + fn step_frame(schedule: &mut Schedule) { + schedule.handle_event(&StepFrame(Box::new(TestSchedule))); + } + + // generic systems + fn first_system() {} + fn second_system() {} + + // Build the schedule we're using for testing. Also run it once so it + // builds the SystemSchedule, and clear out our resource. + fn build_stepping_schedule() -> (World, Schedule) { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + // Build a schedule, run it once to ensure the system schedule is + // built. + schedule.add_systems((first_system, second_system.after(first_system))); + schedule.run(&mut world); + + enable_stepping(&mut schedule); + + (world, schedule) + } + + #[test] + fn stepping_systems() { + let (mut _world, mut schedule) = build_stepping_schedule(); + + // make sure none of the systems run + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(0)); + + // now step a single system; only the second system should run + step_system(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(!skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(1)); + + // don't step, but call step() again; all systems should be marked + // as skipped + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(1)); + + // step & run again; the second system should run + step_system(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(skipped_systems.contains(0)); + assert!(!skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(2)); + + // don't step, but call step() again; all systems should be marked + // as skipped + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(2)); + + // step & run again; the frame finished, so only the second system + // should be skipped + step_system(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(!skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(1)); + } + + #[test] + fn stepping_frames() { + let (mut _world, mut schedule) = build_stepping_schedule(); + + // make sure none of the systems run + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(0)); + + // step an entire frame; no systems should be skipped + step_frame(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert_eq!(skipped_systems.count_ones(..), 0); + assert_eq!(schedule.executable().step_state, StepState::Wait(0)); + + // step the frame again to check the state wrapping behavior + step_frame(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert_eq!(skipped_systems.count_ones(..), 0); + assert_eq!(schedule.executable().step_state, StepState::Wait(0)); + + // step a single system + step_system(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(!skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(1)); + + // and then step the rest of the frame; we should skip the first + // system as it was run in the previous step. This ensures we + // correctly run the rest of a partial frame. + step_frame(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(skipped_systems.contains(0)); + assert!(!skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(0)); + } + + #[test] + fn ignore_stepping() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + // build a schedule and enable stepping mode + // To verify that we put the stepping fixedbitset in the correct + // order, we're going to add the systems in reverse order here. + schedule + .set_executor_kind(ExecutorKind::SingleThreaded) + .add_systems(( + second_system, + first_system.before(second_system).ignore_stepping(), + )); + enable_stepping(&mut schedule); + + // run once to build the SystemSchedule + schedule.run(&mut world); + + // make sure we only skip the second system + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(!skipped_systems.contains(0)); + assert!(skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(0)); + + // now step, and neither system should be skipped + step_system(&mut schedule); + let skipped_systems = schedule.executable().step().unwrap(); + assert_eq!(skipped_systems.len(), 2); + assert!(!skipped_systems.contains(0)); + assert!(!skipped_systems.contains(1)); + assert_eq!(schedule.executable().step_state, StepState::Wait(2)); + } + + /// verify the [`SimpleExecutor`] respects the skipped list returned by + /// `SystemSchedule::step()` + #[test] + fn simple_executor() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + world.init_resource::(); + + // build a schedule and enable stepping mode + schedule + .set_executor_kind(ExecutorKind::Simple) + .add_systems(|| panic!("should not run")); + enable_stepping(&mut schedule); + + // this will panic if the executor doesn't follow the skipped list + schedule.run(&mut world); + } + + /// verify the [`SingleThreadedExecutor`] respects the skipped list + /// returned by `SystemSchedule::step()` + #[test] + fn single_threaded_executor() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + // build a schedule and enable stepping mode + schedule + .set_executor_kind(ExecutorKind::SingleThreaded) + .add_systems(|| panic!("should not run")); + enable_stepping(&mut schedule); + + // this will panic if the executor doesn't follow the skipped list + schedule.run(&mut world); + } + + /// verify the [`MultiThreadedExecutor`] respects the skipped list + /// returned by `SystemSchedule::step()` + #[test] + fn multi_threaded_executor() { + let mut world = World::default(); + let mut schedule = Schedule::default(); + + // build a schedule and enable stepping mode + schedule + .set_executor_kind(ExecutorKind::MultiThreaded) + .add_systems(|| panic!("should not run")); + enable_stepping(&mut schedule); + + // this will panic if the executor doesn't follow the skipped list + schedule.run(&mut world); + } + } } diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 303ac8dd29479..a504c13ab53de 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -266,6 +266,15 @@ impl Schedule { &mut self.graph } + /// Returns the [`SystemSchedule`] for this schedule + /// + /// Note: The [`SystemSchedule`] is invalid prior to [`Schedule::run`] being + /// called + #[cfg(test)] + pub fn executable(&mut self) -> &mut SystemSchedule { + &mut self.executable + } + /// Iterates the change ticks of all systems in the schedule and clamps any older than /// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE). /// This prevents overflow and thus prevents false positives. @@ -300,6 +309,125 @@ impl Schedule { system.apply_buffers(world); } } + + /// Check if stepping-mode is enabled on this [`Schedule`] + pub fn stepping(&self) -> bool { + match self.executable.step_state { + StepState::RunAll => false, + StepState::Wait(_) | StepState::Next(_) | StepState::Remaining(_) => true, + } + } + + /// When stepping mode is enabled, this method will return the name of the + /// next system to be run when the schedule is stepped. + pub fn next_step_system_name(&self) -> Option> { + let index = self.executable.stepping_next_system_index()?; + Some(self.executable.systems[index].name()) + } + + /// When stepping mode is enabled, this method will return the [`NodeId`] + /// of the next system to be run when this schedule is stepped. + pub fn next_step_system_node_id(&self) -> Option { + let index = self.executable.stepping_next_system_index()?; + self.executable.system_ids.get(index).copied() + } + + /// Get an iterator for the systems in the schedule in the order they would + /// be executed by the single-threaded executor. + /// + /// If [`Schedule::run`] has not yet been called on this schedule, this + /// method will return [`ScheduleNotReadyError`]. + pub fn executor_systems_order( + &self, + ) -> Result, ScheduleNotReadyError> { + if self.graph.changed { + return Err(ScheduleNotReadyError); + } + + let iter = self + .executable + .systems + .iter() + .zip(&self.executable.system_ids) + .map(|(boxed_system, system_id)| (*system_id, boxed_system)); + + Ok(iter) + } + + /// Check if a given system supports stepping + /// + /// If [`Schedule::run`] has not yet been called on this schedule, this + /// method will return [`ScheduleNotReadyError`]. + pub fn system_permits_stepping( + &self, + id: NodeId, + ) -> Result, ScheduleNotReadyError> { + if self.graph.changed { + return Err(ScheduleNotReadyError); + } + + let result = self + .executable + .system_ids + .iter() + .position(|&i| i == id) + .map(|index| self.executable.systems_with_stepping_enabled[index]); + + Ok(result) + } + + /// Get a system from the executor's schedule by [`NodeId`] + /// + /// return [`ScheduleNotReadyError`]. + /// If [`Schedule::run`] has not yet been called on this schedule, this + /// method will return [`ScheduleNotReadyError`]. + pub fn system_at(&self, id: NodeId) -> Result, ScheduleNotReadyError> { + if self.graph.changed { + return Err(ScheduleNotReadyError); + } + + let system = self + .executable + .system_ids + .iter() + .position(|&i| i == id) + .map(|index| &self.executable.systems[index]); + + Ok(system) + } + + /// apply `event` to the schedule + pub fn handle_event(&mut self, event: &ScheduleEvent) { + use StepState::*; + + match event { + ScheduleEvent::EnableStepping(_) => { + if self.executable.step_state == RunAll { + self.executable.step_state = Wait(0); + } + } + ScheduleEvent::DisableStepping(_) => { + self.executable.step_state = RunAll; + } + ScheduleEvent::ToggleStepping(_) => { + if self.executable.step_state == RunAll { + self.executable.step_state = Wait(0); + } else { + self.executable.step_state = RunAll; + } + } + ScheduleEvent::StepFrame(_) => match self.executable.step_state { + Wait(n) => self.executable.step_state = Remaining(n), + RunAll => warn!("stepping not enabled for this schedule"), + _ => (), + }, + ScheduleEvent::StepSystem(_) => match self.executable.step_state { + Wait(n) => self.executable.step_state = Next(n), + RunAll => warn!("stepping not enabled for this schedule"), + _ => (), + }, + } + } } /// A directed acyclic graph structure. @@ -380,6 +508,7 @@ pub struct ScheduleGraph { system_sets: Vec, system_set_conditions: Vec>>, system_set_ids: HashMap, + system_stepping_enabled: Vec, uninit: Vec<(NodeId, usize)>, hierarchy: Dag, dependency: Dag, @@ -400,6 +529,7 @@ impl ScheduleGraph { system_sets: Vec::new(), system_set_conditions: Vec::new(), system_set_ids: HashMap::new(), + system_stepping_enabled: Vec::new(), uninit: Vec::new(), hierarchy: Dag::new(), dependency: Dag::new(), @@ -641,6 +771,13 @@ impl ScheduleGraph { self.systems.push(SystemNode::new(config.system)); self.system_conditions.push(Some(config.conditions)); + use config::SteppingBehavior::*; + let stepping = match config.stepping_behavior { + PermitStepping => true, + IgnoreStepping => false, + }; + self.system_stepping_enabled.push(stepping); + Ok(id) } @@ -1174,6 +1311,8 @@ impl ScheduleGraph { system_dependents, sets_with_conditions_of_systems, systems_in_sets_with_conditions, + systems_with_stepping_enabled: FixedBitSet::with_capacity(sys_count), + step_state: StepState::default(), }) } @@ -1205,10 +1344,17 @@ impl ScheduleGraph { self.system_set_conditions[id.index()] = Some(conditions); } + let step_state = schedule.step_state; + *schedule = self.build_schedule(components)?; // move systems into new schedule for &id in &schedule.system_ids { + if self.system_stepping_enabled[id.index()] { + schedule + .systems_with_stepping_enabled + .insert(schedule.systems.len()); + } let system = self.systems[id.index()].inner.take().unwrap(); let conditions = self.system_conditions[id.index()].take().unwrap(); schedule.systems.push(system); @@ -1220,6 +1366,19 @@ impl ScheduleGraph { schedule.set_conditions.push(conditions); } + // As the system indices in the schedule may have changed, we need to + // reset any stepping state to point at the first system. + // NOTE: This will cause problems if/when we support adding/removing + // systems to a schedule while running. Each time they add/remove, the + // schedule will be rebuilt, and the step state wil be reset to the + // start of the schedule. As a result, it may be impossible to system + // step past a system that changes the schedule. Frame stepping will + // still work. + schedule.step_state = match step_state { + StepState::Next(_) | StepState::Remaining(_) | StepState::Wait(_) => StepState::Wait(0), + StepState::RunAll => StepState::RunAll, + }; + Ok(()) } } @@ -1562,3 +1721,44 @@ impl ScheduleBuildSettings { } } } + +/// Event used to request changes to [`Schedule`]s. Events for a given schedule +/// are processed by [`Schedule::handle_event`]. +#[derive(Debug)] +pub enum ScheduleEvent { + /// Enable step mode for the specified [`Schedule`]. + EnableStepping(BoxedScheduleLabel), + + /// Disable step mode for the specified [`Schedule`]. + DisableStepping(BoxedScheduleLabel), + + /// Toggle step mode for the specified [`Schedule`]. + ToggleStepping(BoxedScheduleLabel), + + /// Run the next system in the schedule the next time [`Schedule::run`] is + /// called. If all systems in the schedule have been run since step-mode + /// was enabled, the schedule will start over at the first system in the + /// schedule. + // + /// If step-mode has not been enabled using + /// [`ScheduleEvent::EnableStepping`] or [`ScheduleEvent::ToggleStepping`], + /// this event will have no effect. + StepSystem(BoxedScheduleLabel), + + /// Run all remaining systems in the [`Schedule`] the next time + /// [`Schedule::run`] is called. If [`ScheduleEvent::StepSystem`] has been + /// used to step some of the systems in the schedule, only the systems that + /// haven't been run will be run. If no systems within the schedule have + /// been stepped, all systems in the schedule will be run. + /// + /// If step-mode has not been enabled using + /// [`ScheduleEvent::EnableStepping`] or [`ScheduleEvent::ToggleStepping`], + /// this event will have no effect. + StepFrame(BoxedScheduleLabel), +} + +/// Error to denote that [`Schedule::run`] has not yet been called for this +/// schedule. +#[derive(Error, Debug)] +#[error("executable schedule has not been built")] +pub struct ScheduleNotReadyError; diff --git a/crates/bevy_gilrs/src/lib.rs b/crates/bevy_gilrs/src/lib.rs index abbf34307c646..3307bb10c6fe1 100644 --- a/crates/bevy_gilrs/src/lib.rs +++ b/crates/bevy_gilrs/src/lib.rs @@ -20,8 +20,11 @@ impl Plugin for GilrsPlugin { { Ok(gilrs) => { app.insert_non_send_resource(gilrs) - .add_systems(PreStartup, gilrs_event_startup_system) - .add_systems(PreUpdate, gilrs_event_system.before(InputSystem)); + .add_systems(PreStartup, gilrs_event_startup_system.ignore_stepping()) + .add_systems( + PreUpdate, + gilrs_event_system.before(InputSystem).ignore_stepping(), + ); } Err(err) => error!("Failed to start Gilrs. {}", err), } diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index 9da05a2e25caf..05640465b64ae 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -58,13 +58,21 @@ impl Plugin for InputPlugin { .add_event::() .init_resource::>() .init_resource::>() - .add_systems(PreUpdate, keyboard_input_system.in_set(InputSystem)) + .add_systems( + PreUpdate, + keyboard_input_system.in_set(InputSystem).ignore_stepping(), + ) // mouse .add_event::() .add_event::() .add_event::() .init_resource::>() - .add_systems(PreUpdate, mouse_button_input_system.in_set(InputSystem)) + .add_systems( + PreUpdate, + mouse_button_input_system + .in_set(InputSystem) + .ignore_stepping(), + ) // gamepad .add_event::() .add_event::() @@ -87,12 +95,18 @@ impl Plugin for InputPlugin { .after(gamepad_event_system) .after(gamepad_connection_system), ) - .in_set(InputSystem), + .in_set(InputSystem) + .ignore_stepping(), ) // touch .add_event::() .init_resource::() - .add_systems(PreUpdate, touch_screen_input_system.in_set(InputSystem)); + .add_systems( + PreUpdate, + touch_screen_input_system + .in_set(InputSystem) + .ignore_stepping(), + ); // Register common types app.register_type::(); diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index bf47c70450994..22e6ea13f58f6 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -226,7 +226,8 @@ impl Plugin for PbrPlugin { // because that resets entity ComputedVisibility for the first view // which would override any results from this otherwise .after(VisibilitySystems::CheckVisibility), - ), + ) + .ignore_stepping(), ); app.world @@ -260,7 +261,8 @@ impl Plugin for PbrPlugin { ( render::extract_clusters.in_set(RenderLightSystems::ExtractClusters), render::extract_lights.in_set(RenderLightSystems::ExtractLights), - ), + ) + .ignore_stepping(), ) .add_systems( Render, @@ -278,7 +280,8 @@ impl Plugin for PbrPlugin { .after(render::prepare_lights) .in_set(RenderLightSystems::PrepareClusters), sort_phase_system::.in_set(RenderSet::PhaseSort), - ), + ) + .ignore_stepping(), ) .init_resource::() .init_resource::() diff --git a/crates/bevy_pbr/src/render/fog.rs b/crates/bevy_pbr/src/render/fog.rs index efa9a4c15f0fe..11c3945d4f99c 100644 --- a/crates/bevy_pbr/src/render/fog.rs +++ b/crates/bevy_pbr/src/render/fog.rs @@ -142,7 +142,12 @@ impl Plugin for FogPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_systems(Render, prepare_fog.in_set(RenderFogSystems::PrepareFog)) + .add_systems( + Render, + prepare_fog + .in_set(RenderFogSystems::PrepareFog) + .ignore_stepping(), + ) .configure_set( Render, RenderFogSystems::PrepareFog.in_set(RenderSet::Prepare), diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index a76ff0f470712..dc9faf061fea8 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -107,14 +107,18 @@ impl Plugin for MeshRenderPlugin { render_app .init_resource::() .init_resource::() - .add_systems(ExtractSchedule, (extract_meshes, extract_skinned_meshes)) + .add_systems( + ExtractSchedule, + (extract_meshes, extract_skinned_meshes).ignore_stepping(), + ) .add_systems( Render, ( prepare_skinned_meshes.in_set(RenderSet::Prepare), queue_mesh_bind_group.in_set(RenderSet::Queue), queue_mesh_view_bind_groups.in_set(RenderSet::Queue), - ), + ) + .ignore_stepping(), ); } } diff --git a/crates/bevy_pbr/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs index 7e885f9231558..7d6a36deca135 100644 --- a/crates/bevy_pbr/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -48,7 +48,10 @@ impl Plugin for WireframePlugin { .add_render_command::() .init_resource::() .init_resource::>() - .add_systems(Render, queue_wireframes.in_set(RenderSet::Queue)); + .add_systems( + Render, + queue_wireframes.in_set(RenderSet::Queue).ignore_stepping(), + ); } } } diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 44d72cb03e886..eb3acc634bbc2 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -29,8 +29,11 @@ impl Plugin for CameraPlugin { if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .add_systems(ExtractSchedule, extract_cameras) - .add_systems(Render, sort_cameras.in_set(RenderSet::Prepare)); + .add_systems(ExtractSchedule, extract_cameras.ignore_stepping()) + .add_systems( + Render, + sort_cameras.in_set(RenderSet::Prepare).ignore_stepping(), + ); let camera_driver_node = CameraDriverNode::new(&mut render_app.world); let mut render_graph = render_app.world.resource_mut::(); render_graph.add_node(crate::main_graph::node::CAMERA_DRIVER, camera_driver_node); diff --git a/crates/bevy_render/src/globals.rs b/crates/bevy_render/src/globals.rs index ef585000b84ce..75c34f67c2e0b 100644 --- a/crates/bevy_render/src/globals.rs +++ b/crates/bevy_render/src/globals.rs @@ -26,8 +26,16 @@ impl Plugin for GlobalsPlugin { render_app .init_resource::() .init_resource::