Skip to content

Commit 0174d63

Browse files
Add a scope API for world schedules (#8387)
# Objective If you want to execute a schedule on the world using arbitrarily complex behavior, you currently need to use "hokey-pokey strats": remove the schedule from the world, do your thing, and add it back to the world. Not only is this cumbersome, it's potentially error-prone as one might forget to re-insert the schedule. ## Solution Add the `World::{try}schedule_scope{ref}` family of functions, which is a convenient abstraction over hokey pokey strats. This method essentially works the same way as `World::resource_scope`. ### Example ```rust // Run the schedule five times. world.schedule_scope(MySchedule, |world, schedule| { for _ in 0..5 { schedule.run(world); } }); ``` --- ## Changelog Added the `World::schedule_scope` family of methods, which provide a way to get mutable access to a world and one of its schedules at the same time. --------- Co-authored-by: James Liu <[email protected]>
1 parent c7eaedd commit 0174d63

File tree

2 files changed

+135
-27
lines changed

2 files changed

+135
-27
lines changed

crates/bevy_ecs/src/world/mod.rs

Lines changed: 131 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,33 +1721,43 @@ impl World {
17211721
schedules.insert(label, schedule);
17221722
}
17231723

1724-
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
1725-
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
1724+
/// Temporarily removes the schedule associated with `label` from the world,
1725+
/// runs user code, and finally re-adds the schedule.
1726+
/// This returns a [`TryRunScheduleError`] if there is no schedule
1727+
/// associated with `label`.
17261728
///
17271729
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
17281730
/// and system state is cached.
17291731
///
1730-
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
1731-
pub fn try_run_schedule(
1732+
/// For simple cases where you just need to call the schedule once,
1733+
/// consider using [`World::try_run_schedule`] instead.
1734+
/// For other use cases, see the example on [`World::schedule_scope`].
1735+
pub fn try_schedule_scope<R>(
17321736
&mut self,
17331737
label: impl ScheduleLabel,
1734-
) -> Result<(), TryRunScheduleError> {
1735-
self.try_run_schedule_ref(&label)
1738+
f: impl FnOnce(&mut World, &mut Schedule) -> R,
1739+
) -> Result<R, TryRunScheduleError> {
1740+
self.try_schedule_scope_ref(&label, f)
17361741
}
17371742

1738-
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
1739-
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
1743+
/// Temporarily removes the schedule associated with `label` from the world,
1744+
/// runs user code, and finally re-adds the schedule.
1745+
/// This returns a [`TryRunScheduleError`] if there is no schedule
1746+
/// associated with `label`.
17401747
///
17411748
/// Unlike the `try_run_schedule` method, this method takes the label by reference, which can save a clone.
17421749
///
17431750
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
17441751
/// and system state is cached.
17451752
///
1746-
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
1747-
pub fn try_run_schedule_ref(
1753+
/// For simple cases where you just need to call the schedule once,
1754+
/// consider using [`World::try_run_schedule_ref`] instead.
1755+
/// For other use cases, see the example on [`World::schedule_scope`].
1756+
pub fn try_schedule_scope_ref<R>(
17481757
&mut self,
17491758
label: &dyn ScheduleLabel,
1750-
) -> Result<(), TryRunScheduleError> {
1759+
f: impl FnOnce(&mut World, &mut Schedule) -> R,
1760+
) -> Result<R, TryRunScheduleError> {
17511761
let Some((extracted_label, mut schedule))
17521762
= self.get_resource_mut::<Schedules>().and_then(|mut s| s.remove_entry(label))
17531763
else {
@@ -1757,11 +1767,115 @@ impl World {
17571767
// TODO: move this span to Schedule::run
17581768
#[cfg(feature = "trace")]
17591769
let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered();
1760-
schedule.run(self);
1761-
self.resource_mut::<Schedules>()
1770+
let value = f(self, &mut schedule);
1771+
1772+
let old = self
1773+
.resource_mut::<Schedules>()
17621774
.insert(extracted_label, schedule);
1775+
if old.is_some() {
1776+
warn!("Schedule `{label:?}` was inserted during a call to `World::schedule_scope`: its value has been overwritten");
1777+
}
1778+
1779+
Ok(value)
1780+
}
1781+
1782+
/// Temporarily removes the schedule associated with `label` from the world,
1783+
/// runs user code, and finally re-adds the schedule.
1784+
///
1785+
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
1786+
/// and system state is cached.
1787+
///
1788+
/// # Examples
1789+
///
1790+
/// ```
1791+
/// # use bevy_ecs::{prelude::*, schedule::ScheduleLabel};
1792+
/// # #[derive(ScheduleLabel, Debug, Clone, Copy, PartialEq, Eq, Hash)]
1793+
/// # pub struct MySchedule;
1794+
/// # #[derive(Resource)]
1795+
/// # struct Counter(usize);
1796+
/// #
1797+
/// # let mut world = World::new();
1798+
/// # world.insert_resource(Counter(0));
1799+
/// # let mut schedule = Schedule::new();
1800+
/// # schedule.add_systems(tick_counter);
1801+
/// # world.init_resource::<Schedules>();
1802+
/// # world.add_schedule(schedule, MySchedule);
1803+
/// # fn tick_counter(mut counter: ResMut<Counter>) { counter.0 += 1; }
1804+
/// // Run the schedule five times.
1805+
/// world.schedule_scope(MySchedule, |world, schedule| {
1806+
/// for _ in 0..5 {
1807+
/// schedule.run(world);
1808+
/// }
1809+
/// });
1810+
/// # assert_eq!(world.resource::<Counter>().0, 5);
1811+
/// ```
1812+
///
1813+
/// For simple cases where you just need to call the schedule once,
1814+
/// consider using [`World::run_schedule`] instead.
1815+
///
1816+
/// # Panics
1817+
///
1818+
/// If the requested schedule does not exist.
1819+
pub fn schedule_scope<R>(
1820+
&mut self,
1821+
label: impl ScheduleLabel,
1822+
f: impl FnOnce(&mut World, &mut Schedule) -> R,
1823+
) -> R {
1824+
self.schedule_scope_ref(&label, f)
1825+
}
17631826

1764-
Ok(())
1827+
/// Temporarily removes the schedule associated with `label` from the world,
1828+
/// runs user code, and finally re-adds the schedule.
1829+
///
1830+
/// Unlike the `run_schedule` method, this method takes the label by reference, which can save a clone.
1831+
///
1832+
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
1833+
/// and system state is cached.
1834+
///
1835+
/// For simple cases where you just need to call the schedule,
1836+
/// consider using [`World::run_schedule_ref`] instead.
1837+
/// For other use cases, see the example on [`World::schedule_scope`].
1838+
///
1839+
/// # Panics
1840+
///
1841+
/// If the requested schedule does not exist.
1842+
pub fn schedule_scope_ref<R>(
1843+
&mut self,
1844+
label: &dyn ScheduleLabel,
1845+
f: impl FnOnce(&mut World, &mut Schedule) -> R,
1846+
) -> R {
1847+
self.try_schedule_scope_ref(label, f)
1848+
.unwrap_or_else(|e| panic!("{e}"))
1849+
}
1850+
1851+
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
1852+
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
1853+
///
1854+
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
1855+
/// and system state is cached.
1856+
///
1857+
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
1858+
pub fn try_run_schedule(
1859+
&mut self,
1860+
label: impl ScheduleLabel,
1861+
) -> Result<(), TryRunScheduleError> {
1862+
self.try_run_schedule_ref(&label)
1863+
}
1864+
1865+
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
1866+
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
1867+
///
1868+
/// Unlike the `try_run_schedule` method, this method takes the label by reference, which can save a clone.
1869+
///
1870+
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
1871+
/// and system state is cached.
1872+
///
1873+
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
1874+
pub fn try_run_schedule_ref(
1875+
&mut self,
1876+
label: &dyn ScheduleLabel,
1877+
) -> Result<(), TryRunScheduleError> {
1878+
self.try_schedule_scope_ref(label, |world, sched| sched.run(world))
17651879
}
17661880

17671881
/// Runs the [`Schedule`] associated with the `label` a single time.
@@ -1773,7 +1887,7 @@ impl World {
17731887
///
17741888
/// # Panics
17751889
///
1776-
/// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added.
1890+
/// If the requested schedule does not exist.
17771891
pub fn run_schedule(&mut self, label: impl ScheduleLabel) {
17781892
self.run_schedule_ref(&label);
17791893
}
@@ -1789,10 +1903,9 @@ impl World {
17891903
///
17901904
/// # Panics
17911905
///
1792-
/// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added.
1906+
/// If the requested schedule does not exist.
17931907
pub fn run_schedule_ref(&mut self, label: &dyn ScheduleLabel) {
1794-
self.try_run_schedule_ref(label)
1795-
.unwrap_or_else(|e| panic!("{}", e));
1908+
self.schedule_scope_ref(label, |world, sched| sched.run(world));
17961909
}
17971910
}
17981911

crates/bevy_time/src/fixed_timestep.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,11 @@ pub fn run_fixed_update_schedule(world: &mut World) {
107107
fixed_time.tick(delta_time);
108108

109109
// Run the schedule until we run out of accumulated time
110-
let mut check_again = true;
111-
while check_again {
112-
let mut fixed_time = world.resource_mut::<FixedTime>();
113-
let fixed_time_run = fixed_time.expend().is_ok();
114-
if fixed_time_run {
115-
let _ = world.try_run_schedule(FixedUpdate);
116-
} else {
117-
check_again = false;
110+
let _ = world.try_schedule_scope(FixedUpdate, |world, schedule| {
111+
while world.resource_mut::<FixedTime>().expend().is_ok() {
112+
schedule.run(world);
118113
}
119-
}
114+
});
120115
}
121116

122117
#[cfg(test)]

0 commit comments

Comments
 (0)