diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index fa529a96ee..c14b4ab8d4 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1721,6 +1721,133 @@ impl World { schedules.insert(label, schedule); } + /// Temporarily removes the schedule associated with `label` from the world, + /// runs user code, and finally re-adds the schedule. + /// This returns a [`TryRunScheduleError`] if there is no schedule + /// associated with `label`. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// For simple cases where you just need to call the schedule once, + /// consider using [`World::try_run_schedule`] instead. + /// For other use cases, see the example on [`World::schedule_scope`]. + pub fn try_schedule_scope( + &mut self, + label: impl ScheduleLabel, + f: impl FnOnce(&mut World, &mut Schedule) -> R, + ) -> Result { + self.try_schedule_scope_ref(&label, f) + } + + /// Temporarily removes the schedule associated with `label` from the world, + /// runs user code, and finally re-adds the schedule. + /// This returns a [`TryRunScheduleError`] if there is no schedule + /// associated with `label`. + /// + /// Unlike the `try_run_schedule` method, this method takes the label by reference, which can save a clone. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// For simple cases where you just need to call the schedule once, + /// consider using [`World::try_run_schedule_ref`] instead. + /// For other use cases, see the example on [`World::schedule_scope`]. + pub fn try_schedule_scope_ref( + &mut self, + label: &dyn ScheduleLabel, + f: impl FnOnce(&mut World, &mut Schedule) -> R, + ) -> Result { + let Some((extracted_label, mut schedule)) + = self.get_resource_mut::().and_then(|mut s| s.remove_entry(label)) + else { + return Err(TryRunScheduleError(label.dyn_clone())); + }; + + // TODO: move this span to Schedule::run + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered(); + let value = f(self, &mut schedule); + + let old = self + .resource_mut::() + .insert(extracted_label, schedule); + if old.is_some() { + warn!("Schedule `{label:?}` was inserted during a call to `World::schedule_scope`: its value has been overwritten"); + } + + Ok(value) + } + + /// Temporarily removes the schedule associated with `label` from the world, + /// runs user code, and finally re-adds the schedule. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// # Examples + /// + /// ``` + /// # use bevy_ecs::{prelude::*, schedule::ScheduleLabel}; + /// # #[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)] + /// # pub struct MySchedule; + /// # #[derive(Resource)] + /// # struct Counter(usize); + /// # + /// # let mut world = World::new(); + /// # world.insert_resource(Counter(0)); + /// # let mut schedule = Schedule::new(); + /// # schedule.add_systems(tick_counter); + /// # world.init_resource::(); + /// # world.add_schedule(schedule, MySchedule); + /// # fn tick_counter(mut counter: ResMut) { counter.0 += 1; } + /// // Run the schedule five times. + /// world.schedule_scope(MySchedule, |world, schedule| { + /// for _ in 0..5 { + /// schedule.run(world); + /// } + /// }); + /// # assert_eq!(world.resource::().0, 5); + /// ``` + /// + /// For simple cases where you just need to call the schedule once, + /// consider using [`World::run_schedule`] instead. + /// + /// # Panics + /// + /// If the requested schedule does not exist. + pub fn schedule_scope( + &mut self, + label: impl ScheduleLabel, + f: impl FnOnce(&mut World, &mut Schedule) -> R, + ) -> R { + self.schedule_scope_ref(&label, f) + } + + /// Temporarily removes the schedule associated with `label` from the world, + /// runs user code, and finally re-adds the schedule. + /// + /// Unlike the `run_schedule` method, this method takes the label by reference, which can save a clone. + /// + /// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label, + /// and system state is cached. + /// + /// For simple cases where you just need to call the schedule, + /// consider using [`World::run_schedule_ref`] instead. + /// For other use cases, see the example on [`World::schedule_scope`]. + /// + /// # Panics + /// + /// If the requested schedule does not exist. + pub fn schedule_scope_ref( + &mut self, + label: &dyn ScheduleLabel, + f: impl FnOnce(&mut World, &mut Schedule) -> R, + ) -> R { + self.try_schedule_scope_ref(label, f) + .unwrap_or_else(|e| panic!("{e}")) + } + /// Attempts to run the [`Schedule`] associated with the `label` a single time, /// and returns a [`TryRunScheduleError`] if the schedule does not exist. /// @@ -1748,20 +1875,7 @@ impl World { &mut self, label: &dyn ScheduleLabel, ) -> Result<(), TryRunScheduleError> { - let Some((extracted_label, mut schedule)) - = self.get_resource_mut::().and_then(|mut s| s.remove_entry(label)) - else { - return Err(TryRunScheduleError(label.dyn_clone())); - }; - - // TODO: move this span to Schedule::run - #[cfg(feature = "trace")] - let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered(); - schedule.run(self); - self.resource_mut::() - .insert(extracted_label, schedule); - - Ok(()) + self.try_schedule_scope_ref(label, |world, sched| sched.run(world)) } /// Runs the [`Schedule`] associated with the `label` a single time. @@ -1773,7 +1887,7 @@ impl World { /// /// # Panics /// - /// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added. + /// If the requested schedule does not exist. pub fn run_schedule(&mut self, label: impl ScheduleLabel) { self.run_schedule_ref(&label); } @@ -1789,10 +1903,9 @@ impl World { /// /// # Panics /// - /// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added. + /// If the requested schedule does not exist. pub fn run_schedule_ref(&mut self, label: &dyn ScheduleLabel) { - self.try_run_schedule_ref(label) - .unwrap_or_else(|e| panic!("{}", e)); + self.schedule_scope_ref(label, |world, sched| sched.run(world)); } } diff --git a/crates/bevy_time/src/fixed_timestep.rs b/crates/bevy_time/src/fixed_timestep.rs index f8df270565..66be2f41ef 100644 --- a/crates/bevy_time/src/fixed_timestep.rs +++ b/crates/bevy_time/src/fixed_timestep.rs @@ -107,16 +107,11 @@ pub fn run_fixed_update_schedule(world: &mut World) { fixed_time.tick(delta_time); // Run the schedule until we run out of accumulated time - let mut check_again = true; - while check_again { - let mut fixed_time = world.resource_mut::(); - let fixed_time_run = fixed_time.expend().is_ok(); - if fixed_time_run { - let _ = world.try_run_schedule(FixedUpdate); - } else { - check_again = false; + let _ = world.try_schedule_scope(FixedUpdate, |world, schedule| { + while world.resource_mut::().expend().is_ok() { + schedule.run(world); } - } + }); } #[cfg(test)]