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 <contact@jamessliu.com>
			
			
This commit is contained in:
		
							parent
							
								
									c7eaedd6a1
								
							
						
					
					
						commit
						0174d632a5
					
				@ -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<R>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        label: impl ScheduleLabel,
 | 
			
		||||
        f: impl FnOnce(&mut World, &mut Schedule) -> R,
 | 
			
		||||
    ) -> Result<R, TryRunScheduleError> {
 | 
			
		||||
        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<R>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        label: &dyn ScheduleLabel,
 | 
			
		||||
        f: impl FnOnce(&mut World, &mut Schedule) -> R,
 | 
			
		||||
    ) -> Result<R, TryRunScheduleError> {
 | 
			
		||||
        let Some((extracted_label, mut schedule))
 | 
			
		||||
            = self.get_resource_mut::<Schedules>().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::<Schedules>()
 | 
			
		||||
            .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::<Schedules>();
 | 
			
		||||
    /// # world.add_schedule(schedule, MySchedule);
 | 
			
		||||
    /// # fn tick_counter(mut counter: ResMut<Counter>) { 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::<Counter>().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<R>(
 | 
			
		||||
        &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<R>(
 | 
			
		||||
        &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::<Schedules>().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::<Schedules>()
 | 
			
		||||
            .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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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::<FixedTime>();
 | 
			
		||||
        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::<FixedTime>().expend().is_ok() {
 | 
			
		||||
            schedule.run(world);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user