Skip to content
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ pub mod prelude {
Command, Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef,
IntoSystem, Local, NonSend, NonSendMut, ParamSet, Populated, Query, ReadOnlySystem,
Res, ResMut, Single, System, SystemIn, SystemInput, SystemParamBuilder,
SystemParamFunction,
SystemParamFunction, When,
},
world::{
EntityMut, EntityRef, EntityWorldMut, FilteredResources, FilteredResourcesMut,
Expand Down
15 changes: 14 additions & 1 deletion crates/bevy_ecs/src/system/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
query::{QueryData, QueryFilter, QueryState},
resource::Resource,
system::{
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam,
DynSystemParam, DynSystemParamState, Local, ParamSet, Query, SystemMeta, SystemParam, When,
},
world::{
FilteredResources, FilteredResourcesBuilder, FilteredResourcesMut,
Expand Down Expand Up @@ -710,6 +710,19 @@ unsafe impl<'w, 's, T: FnOnce(&mut FilteredResourcesMutBuilder)>
}
}

/// A [`SystemParamBuilder`] for a [`When`].
#[derive(Clone)]
pub struct WhenBuilder<T>(T);

// SAFETY: `WhenBuilder<B>` builds a state that is valid for `P`, and any state valid for `P` is valid for `When<P>`
unsafe impl<P: SystemParam, B: SystemParamBuilder<P>> SystemParamBuilder<When<P>>
for WhenBuilder<B>
{
fn build(self, world: &mut World, meta: &mut SystemMeta) -> <When<P> as SystemParam>::State {
self.0.build(world, meta)
}
}

#[cfg(test)]
mod tests {
use crate::{
Expand Down
111 changes: 109 additions & 2 deletions crates/bevy_ecs/src/system/system_param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1916,6 +1916,112 @@ unsafe impl SystemParam for SystemChangeTick {
}
}

/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid.
///
/// # Example
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Resource)]
/// # struct SomeResource;
/// // This system will fail if `SomeResource` is not present.
/// fn fails_on_missing_resource(res: Res<SomeResource>) {}
///
/// // This system will skip without error if `SomeResource` is not present.
/// fn skips_on_missing_resource(res: When<Res<SomeResource>>) {
/// // The inner parameter is available using `Deref`
/// let some_resource: &SomeResource = &res;
/// }
/// # bevy_ecs::system::assert_is_system(skips_on_missing_resource);
/// ```
#[derive(Debug)]
pub struct When<T>(pub T);

impl<T> When<T> {
/// Returns the inner `T`.
///
/// The inner value is `pub`, so you can also obtain it by destructuring the parameter:
///
/// ```
/// # use bevy_ecs::prelude::*;
/// # #[derive(Resource)]
/// # struct SomeResource;
/// fn skips_on_missing_resource(When(res): When<Res<SomeResource>>) {
/// let some_resource: Res<SomeResource> = res;
/// }
/// # bevy_ecs::system::assert_is_system(skips_on_missing_resource);
/// ```
pub fn into_inner(self) -> T {
self.0
}
}

impl<T> Deref for When<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<T> DerefMut for When<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

// SAFETY: Delegates to `T`, which ensures the safety requirements are met
unsafe impl<T: SystemParam> SystemParam for When<T> {
type State = T::State;

type Item<'world, 'state> = When<T::Item<'world, 'state>>;

fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State {
T::init_state(world, system_meta)
}

#[inline]
unsafe fn validate_param(
state: &Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell,
) -> Result<(), SystemParamValidationError> {
T::validate_param(state, system_meta, world).map_err(|mut e| {
e.skipped = true;
e
})
}

#[inline]
unsafe fn get_param<'world, 'state>(
state: &'state mut Self::State,
system_meta: &SystemMeta,
world: UnsafeWorldCell<'world>,
change_tick: Tick,
) -> Self::Item<'world, 'state> {
When(T::get_param(state, system_meta, world, change_tick))
}

unsafe fn new_archetype(
state: &mut Self::State,
archetype: &Archetype,
system_meta: &mut SystemMeta,
) {
// SAFETY: The caller ensures that `archetype` is from the World the state was initialized from in `init_state`.
unsafe { T::new_archetype(state, archetype, system_meta) };
}

fn apply(state: &mut Self::State, system_meta: &SystemMeta, world: &mut World) {
T::apply(state, system_meta, world);
}

fn queue(state: &mut Self::State, system_meta: &SystemMeta, world: DeferredWorld) {
T::queue(state, system_meta, world);
}
}

// SAFETY: Delegates to `T`, which ensures the safety requirements are met
unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for When<T> {}

// SAFETY: When initialized with `init_state`, `get_param` returns an empty `Vec` and does no access.
// Therefore, `init_state` trivially registers all access, and no accesses can conflict.
// Note that the safety requirements for non-empty `Vec`s are handled by the `SystemParamBuilder` impl that builds them.
Expand Down Expand Up @@ -2699,11 +2805,12 @@ pub struct SystemParamValidationError {
/// By default, this will result in a panic. See [`crate::error`] for more information.
///
/// This is the default behavior, and is suitable for system params that should *always* be valid,
/// either because sensible fallback behavior exists (like [`Query`] or because
/// either because sensible fallback behavior exists (like [`Query`]) or because
/// failures in validation should be considered a bug in the user's logic that must be immediately addressed (like [`Res`]).
///
/// If `true`, the system should be skipped.
/// This is suitable for system params that are intended to only operate in certain application states, such as [`Single`].
/// This is set by wrapping the system param in [`When`],
/// and indicates that the system is intended to only operate in certain application states.
pub skipped: bool,

/// A message describing the validation error.
Expand Down