Skip to content

Commit 609b099

Browse files
ItsDootjames7132
andauthored
Add World::try_run_schedule (#8028)
Co-authored-by: James Liu <[email protected]>
1 parent 67afd21 commit 609b099

File tree

5 files changed

+77
-35
lines changed

5 files changed

+77
-35
lines changed

crates/bevy_app/src/app.rs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,10 @@ impl App {
358358
.run_if(in_state(variant)),
359359
);
360360
}
361-
// These are different for loops to avoid conflicting access to self
362-
for variant in S::variants() {
363-
if self.get_schedule(OnEnter(variant.clone())).is_none() {
364-
self.add_schedule(OnEnter(variant.clone()), Schedule::new());
365-
}
366-
if self.get_schedule(OnExit(variant.clone())).is_none() {
367-
self.add_schedule(OnExit(variant), Schedule::new());
368-
}
369-
}
361+
362+
// The OnEnter, OnExit, and OnTransition schedules are lazily initialized
363+
// (i.e. when the first system is added to them), and World::try_run_schedule is used to fail
364+
// gracefully if they aren't present.
370365

371366
self
372367
}

crates/bevy_ecs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ fixedbitset = "0.4.2"
2727
rustc-hash = "1.1"
2828
downcast-rs = "1.2"
2929
serde = { version = "1", features = ["derive"] }
30+
thiserror = "1.0"
3031

3132
[dev-dependencies]
3233
rand = "0.8"

crates/bevy_ecs/src/schedule/state.rs

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::mem;
44

55
use crate as bevy_ecs;
66
use crate::change_detection::DetectChangesMut;
7-
use crate::schedule::{ScheduleLabel, Schedules, SystemSet};
7+
use crate::schedule::{ScheduleLabel, SystemSet};
88
use crate::system::Resource;
99
use crate::world::World;
1010

@@ -100,15 +100,18 @@ impl<S: States> NextState<S> {
100100
}
101101
}
102102

103-
/// Run the enter schedule for the current state
103+
/// Run the enter schedule (if it exists) for the current state.
104104
pub fn run_enter_schedule<S: States>(world: &mut World) {
105-
world.run_schedule(OnEnter(world.resource::<State<S>>().0.clone()));
105+
world
106+
.try_run_schedule(OnEnter(world.resource::<State<S>>().0.clone()))
107+
.ok();
106108
}
107109

108110
/// If a new state is queued in [`NextState<S>`], this system:
109111
/// - Takes the new state value from [`NextState<S>`] and updates [`State<S>`].
110-
/// - Runs the [`OnExit(exited_state)`] schedule.
111-
/// - Runs the [`OnEnter(entered_state)`] schedule.
112+
/// - Runs the [`OnExit(exited_state)`] schedule, if it exists.
113+
/// - Runs the [`OnTransition { from: exited_state, to: entered_state }`](OnTransition), if it exists.
114+
/// - Runs the [`OnEnter(entered_state)`] schedule, if it exists.
112115
pub fn apply_state_transition<S: States>(world: &mut World) {
113116
// We want to take the `NextState` resource,
114117
// but only mark it as changed if it wasn't empty.
@@ -117,16 +120,15 @@ pub fn apply_state_transition<S: States>(world: &mut World) {
117120
next_state_resource.set_changed();
118121

119122
let exited = mem::replace(&mut world.resource_mut::<State<S>>().0, entered.clone());
120-
world.run_schedule(OnExit(exited.clone()));
121123

122-
let transition_schedule = OnTransition {
123-
from: exited,
124-
to: entered.clone(),
125-
};
126-
if world.resource::<Schedules>().contains(&transition_schedule) {
127-
world.run_schedule(transition_schedule);
128-
}
129-
130-
world.run_schedule(OnEnter(entered));
124+
// Try to run the schedules if they exist.
125+
world.try_run_schedule(OnExit(exited.clone())).ok();
126+
world
127+
.try_run_schedule(OnTransition {
128+
from: exited,
129+
to: entered.clone(),
130+
})
131+
.ok();
132+
world.try_run_schedule(OnEnter(entered)).ok();
131133
}
132134
}

crates/bevy_ecs/src/world/error.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
use thiserror::Error;
2+
3+
use crate::schedule::BoxedScheduleLabel;
4+
5+
/// The error type returned by [`World::try_run_schedule`] if the provided schedule does not exist.
6+
///
7+
/// [`World::try_run_schedule`]: crate::world::World::try_run_schedule
8+
#[derive(Error, Debug)]
9+
#[error("The schedule with the label {0:?} was not found.")]
10+
pub struct TryRunScheduleError(pub BoxedScheduleLabel);

crates/bevy_ecs/src/world/mod.rs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod entity_ref;
2+
pub mod error;
23
mod spawn_batch;
34
pub mod unsafe_world_cell;
45
mod world_cell;
@@ -20,6 +21,7 @@ use crate::{
2021
schedule::{Schedule, ScheduleLabel, Schedules},
2122
storage::{ResourceData, Storages},
2223
system::Resource,
24+
world::error::TryRunScheduleError,
2325
};
2426
use bevy_ptr::{OwningPtr, Ptr};
2527
use bevy_utils::tracing::warn;
@@ -1714,6 +1716,47 @@ impl World {
17141716
schedules.insert(label, schedule);
17151717
}
17161718

1719+
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
1720+
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
1721+
///
1722+
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
1723+
/// and system state is cached.
1724+
///
1725+
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
1726+
pub fn try_run_schedule(
1727+
&mut self,
1728+
label: impl ScheduleLabel,
1729+
) -> Result<(), TryRunScheduleError> {
1730+
self.try_run_schedule_ref(&label)
1731+
}
1732+
1733+
/// Attempts to run the [`Schedule`] associated with the `label` a single time,
1734+
/// and returns a [`TryRunScheduleError`] if the schedule does not exist.
1735+
///
1736+
/// Unlike the `try_run_schedule` method, this method takes the label by reference, which can save a clone.
1737+
///
1738+
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
1739+
/// and system state is cached.
1740+
///
1741+
/// For simple testing use cases, call [`Schedule::run(&mut world)`](Schedule::run) instead.
1742+
pub fn try_run_schedule_ref(
1743+
&mut self,
1744+
label: &dyn ScheduleLabel,
1745+
) -> Result<(), TryRunScheduleError> {
1746+
let Some((extracted_label, mut schedule)) = self.resource_mut::<Schedules>().remove_entry(label) else {
1747+
return Err(TryRunScheduleError(label.dyn_clone()));
1748+
};
1749+
1750+
// TODO: move this span to Schedule::run
1751+
#[cfg(feature = "trace")]
1752+
let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered();
1753+
schedule.run(self);
1754+
self.resource_mut::<Schedules>()
1755+
.insert(extracted_label, schedule);
1756+
1757+
Ok(())
1758+
}
1759+
17171760
/// Runs the [`Schedule`] associated with the `label` a single time.
17181761
///
17191762
/// The [`Schedule`] is fetched from the [`Schedules`] resource of the world by its label,
@@ -1741,17 +1784,8 @@ impl World {
17411784
///
17421785
/// Panics if the requested schedule does not exist, or the [`Schedules`] resource was not added.
17431786
pub fn run_schedule_ref(&mut self, label: &dyn ScheduleLabel) {
1744-
let (extracted_label, mut schedule) = self
1745-
.resource_mut::<Schedules>()
1746-
.remove_entry(label)
1747-
.unwrap_or_else(|| panic!("The schedule with the label {label:?} was not found."));
1748-
1749-
// TODO: move this span to Schedule::run
1750-
#[cfg(feature = "trace")]
1751-
let _span = bevy_utils::tracing::info_span!("schedule", name = ?extracted_label).entered();
1752-
schedule.run(self);
1753-
self.resource_mut::<Schedules>()
1754-
.insert(extracted_label, schedule);
1787+
self.try_run_schedule_ref(label)
1788+
.unwrap_or_else(|e| panic!("{}", e));
17551789
}
17561790
}
17571791

0 commit comments

Comments
 (0)