Skip to content

Commit d28e490

Browse files
chescockalice-i-cecilebushrat011899mockersf
authored
Create a When system param wrapper for skipping systems that fail validation (#18765)
# Objective Create a `When` system param wrapper for skipping systems that fail validation. Currently, the `Single` and `Populated` parameters cause systems to skip when they fail validation, while the `Res` family causes systems to error. Generalize this so that any fallible parameter can be used either to skip a system or to raise an error. A parameter used directly will always raise an error, and a parameter wrapped in `When<P>` will always cause the system to be silently skipped. ~~Note that this changes the behavior for `Single` and `Populated`. The current behavior will be available using `When<Single>` and `When<Populated>`.~~ Fixes #18516 ## Solution Create a `When` system param wrapper that wraps an inner parameter and converts all validation errors to `skipped`. ~~Change the behavior of `Single` and `Populated` to fail by default.~~ ~~Replace in-engine use of `Single` with `When<Single>`. I updated the `fallible_systems` example, but not all of the others. The other examples I looked at appeared to always have one matching entity, and it seemed more clear to use the simpler type in those cases.~~ --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Zachary Harrold <[email protected]> Co-authored-by: François Mockers <[email protected]>
1 parent 5640589 commit d28e490

File tree

3 files changed

+124
-4
lines changed

3 files changed

+124
-4
lines changed

crates/bevy_ecs/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ pub mod prelude {
9494
Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef,
9595
IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem,
9696
Res, ResMut, Single, System, SystemIn, SystemInput, SystemParamBuilder,
97-
SystemParamFunction,
97+
SystemParamFunction, When,
9898
},
9999
world::{
100100
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,

crates/bevy_ecs/src/system/builder.rs

+14-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
query::{QueryData, QueryFilter, QueryState},
88
resource::Resource,
99
system::{
10-
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
10+
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, When,
1111
},
1212
world::{
1313
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
@@ -710,6 +710,19 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
710710
}
711711
}
712712

713+
/// A [`SystemParamBuilder`] for a [`When`].
714+
#[derive(Clone)]
715+
pub struct WhenBuilder<T>(T);
716+
717+
// SAFETY: `WhenBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `When<P>`
718+
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<When<P>>
719+
for WhenBuilder<B>
720+
{
721+
fn build(self, world: &mut World, meta: &mut SystemMeta) -> <When<P> as SystemParam>::State {
722+
self.0.build(world, meta)
723+
}
724+
}
725+
713726
#[cfg(test)]
714727
mod tests {
715728
use crate::{

crates/bevy_ecs/src/system/system_param.rs

+109-2
Original file line numberDiff line numberDiff line change
@@ -1916,6 +1916,112 @@ unsafe impl SystemParam for SystemChangeTick {
19161916
}
19171917
}
19181918

1919+
/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid.
1920+
///
1921+
/// # Example
1922+
///
1923+
/// ```
1924+
/// # use bevy_ecs::prelude::*;
1925+
/// # #[derive(Resource)]
1926+
/// # struct SomeResource;
1927+
/// // This system will fail if `SomeResource` is not present.
1928+
/// fn fails_on_missing_resource(res: Res<SomeResource>) {}
1929+
///
1930+
/// // This system will skip without error if `SomeResource` is not present.
1931+
/// fn skips_on_missing_resource(res: When<Res<SomeResource>>) {
1932+
/// // The inner parameter is available using `Deref`
1933+
/// let some_resource: &SomeResource = &res;
1934+
/// }
1935+
/// # bevy_ecs::system::assert_is_system(skips_on_missing_resource);
1936+
/// ```
1937+
#[derive(Debug)]
1938+
pub struct When<T>(pub T);
1939+
1940+
impl<T> When<T> {
1941+
/// Returns the inner `T`.
1942+
///
1943+
/// The inner value is `pub`, so you can also obtain it by destructuring the parameter:
1944+
///
1945+
/// ```
1946+
/// # use bevy_ecs::prelude::*;
1947+
/// # #[derive(Resource)]
1948+
/// # struct SomeResource;
1949+
/// fn skips_on_missing_resource(When(res): When<Res<SomeResource>>) {
1950+
/// let some_resource: Res<SomeResource> = res;
1951+
/// }
1952+
/// # bevy_ecs::system::assert_is_system(skips_on_missing_resource);
1953+
/// ```
1954+
pub fn into_inner(self) -> T {
1955+
self.0
1956+
}
1957+
}
1958+
1959+
impl<T> Deref for When<T> {
1960+
type Target = T;
1961+
fn deref(&self) -> &Self::Target {
1962+
&self.0
1963+
}
1964+
}
1965+
1966+
impl<T> DerefMut for When<T> {
1967+
fn deref_mut(&mut self) -> &mut Self::Target {
1968+
&mut self.0
1969+
}
1970+
}
1971+
1972+
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
1973+
unsafe impl<T: SystemParam> SystemParam for When<T> {
1974+
type State = T::State;
1975+
1976+
type Item<'world, 'state> = When<T::Item<'world, 'state>>;
1977+
1978+
fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
1979+
T::init_state(world, system_meta)
1980+
}
1981+
1982+
#[inline]
1983+
unsafe fn validate_param(
1984+
state: &Self::State,
1985+
system_meta: &SystemMeta,
1986+
world: UnsafeWorldCell,
1987+
) -> Result<(), SystemParamValidationError> {
1988+
T::validate_param(state, system_meta, world).map_err(|mut e| {
1989+
e.skipped = true;
1990+
e
1991+
})
1992+
}
1993+
1994+
#[inline]
1995+
unsafe fn get_param<'world, 'state>(
1996+
state: &'state mut Self::State,
1997+
system_meta: &SystemMeta,
1998+
world: UnsafeWorldCell<'world>,
1999+
change_tick: Tick,
2000+
) -> Self::Item<'world, 'state> {
2001+
When(T::get_param(state, system_meta, world, change_tick))
2002+
}
2003+
2004+
unsafe fn new_archetype(
2005+
state: &mut Self::State,
2006+
archetype: &Archetype,
2007+
system_meta: &mut SystemMeta,
2008+
) {
2009+
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
2010+
unsafe { T::new_archetype(state, archetype, system_meta) };
2011+
}
2012+
2013+
fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
2014+
T::apply(state, system_meta, world);
2015+
}
2016+
2017+
fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
2018+
T::queue(state, system_meta, world);
2019+
}
2020+
}
2021+
2022+
// SAFETY: Delegates to `T`, which ensures the safety requirements are met
2023+
unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for When<T> {}
2024+
19192025
// SAFETY: When initialized with `init_state`, `get_param` returns an empty `Vec` and does no access.
19202026
// Therefore, `init_state` trivially registers all access, and no accesses can conflict.
19212027
// Note that the safety requirements for non-empty `Vec`s are handled by the `SystemParamBuilder` impl that builds them.
@@ -2699,11 +2805,12 @@ pub struct SystemParamValidationError {
26992805
/// By default, this will result in a panic. See [`crate::error`] for more information.
27002806
///
27012807
/// This is the default behavior, and is suitable for system params that should *always* be valid,
2702-
/// either because sensible fallback behavior exists (like [`Query`] or because
2808+
/// either because sensible fallback behavior exists (like [`Query`]) or because
27032809
/// failures in validation should be considered a bug in the user's logic that must be immediately addressed (like [`Res`]).
27042810
///
27052811
/// If `true`, the system should be skipped.
2706-
/// This is suitable for system params that are intended to only operate in certain application states, such as [`Single`].
2812+
/// This is set by wrapping the system param in [`When`],
2813+
/// and indicates that the system is intended to only operate in certain application states.
27072814
pub skipped: bool,
27082815

27092816
/// A message describing the validation error.

0 commit comments

Comments
 (0)