From 54b267982e63853efa9d0eaf99a777a8176282de Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Mon, 31 Mar 2025 10:11:56 -0400 Subject: [PATCH 1/4] Have Single and Populated fail instead of skipping. --- crates/bevy_ecs/src/schedule/executor/mod.rs | 22 +++++++++++-------- crates/bevy_ecs/src/system/query.rs | 8 ++++--- crates/bevy_ecs/src/system/system_param.rs | 14 +++++------- crates/bevy_input_focus/src/lib.rs | 4 ++-- examples/ecs/fallible_params.rs | 6 ++--- .../single_and_populated_fail.md | 14 ++++++++++++ 6 files changed, 42 insertions(+), 26 deletions(-) create mode 100644 release-content/migration-guides/single_and_populated_fail.md diff --git a/crates/bevy_ecs/src/schedule/executor/mod.rs b/crates/bevy_ecs/src/schedule/executor/mod.rs index ef7c639038..ad2524126e 100644 --- a/crates/bevy_ecs/src/schedule/executor/mod.rs +++ b/crates/bevy_ecs/src/schedule/executor/mod.rs @@ -324,7 +324,7 @@ mod tests { use crate::{ prelude::{Component, In, IntoSystem, Resource, Schedule}, schedule::ExecutorKind, - system::{Populated, Res, ResMut, Single}, + system::{Populated, Res, ResMut, Single, When}, world::World, }; @@ -347,12 +347,12 @@ mod tests { #[derive(Resource, Default)] struct Counter(u8); - fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut) { + fn set_single_state(mut _single: When>, mut state: ResMut) { state.single_ran = true; } fn set_populated_state( - mut _populated: Populated<&TestComponent>, + mut _populated: When>, mut state: ResMut, ) { state.populated_ran = true; @@ -424,7 +424,7 @@ mod tests { #[test] fn piped_systems_first_system_skipped() { // This system should be skipped when run due to no matching entity - fn pipe_out(_single: Single<&TestComponent>) -> u8 { + fn pipe_out(_single: When>) -> u8 { 42 } @@ -451,7 +451,7 @@ mod tests { } // This system should be skipped when run due to no matching entity - fn pipe_in(_input: In, _single: Single<&TestComponent>) {} + fn pipe_in(_input: In, _single: When>) {} let mut world = World::new(); world.init_resource::(); @@ -502,7 +502,7 @@ mod tests { #[test] fn piped_system_skip_and_panic() { // This system should be skipped when run due to no matching entity - fn pipe_out(_single: Single<&TestComponent>) -> u8 { + fn pipe_out(_single: When>) -> u8 { 42 } @@ -526,7 +526,7 @@ mod tests { } // This system should be skipped when run due to no matching entity - fn pipe_in(_input: In, _single: Single<&TestComponent>) {} + fn pipe_in(_input: In, _single: When>) {} let mut world = World::new(); let mut schedule = Schedule::default(); @@ -558,13 +558,17 @@ mod tests { fn piped_system_skip_and_skip() { // This system should be skipped when run due to no matching entity - fn pipe_out(_single: Single<&TestComponent>, mut counter: ResMut) -> u8 { + fn pipe_out(_single: When>, mut counter: ResMut) -> u8 { counter.0 += 1; 42 } // This system should be skipped when run due to no matching entity - fn pipe_in(_input: In, _single: Single<&TestComponent>, mut counter: ResMut) { + fn pipe_in( + _input: In, + _single: When>, + mut counter: ResMut, + ) { counter.0 += 1; } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index c67bf7b337..360af6fae4 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -2558,9 +2558,9 @@ impl<'w, 'q, Q: QueryData, F: QueryFilter> From<&'q mut Query<'w, '_, Q, F>> /// [System parameter] that provides access to single entity's components, much like [`Query::single`]/[`Query::single_mut`]. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if zero or more than one matching entity exists. -/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). +/// This will cause the system to fail, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). /// -/// Use [`Option>`] instead if zero or one matching entities can exist. +/// Use [`When>`](crate::system::When) instead to skip the system when there are zero or multiple matching entities. /// /// See [`Query`] for more details. /// @@ -2594,7 +2594,9 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> { /// [System parameter] that works very much like [`Query`] except it always contains at least one matching entity. /// /// This [`SystemParam`](crate::system::SystemParam) fails validation if no matching entities exist. -/// This will cause the system to be skipped, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). +/// This will cause the system to fail, according to the rules laid out in [`SystemParamValidationError`](crate::system::SystemParamValidationError). +/// +/// Use [`When>`](crate::system::When) instead to skip the system when there are zero matching entities. /// /// Much like [`Query::is_empty`] the worst case runtime will be `O(n)` where `n` is the number of *potential* matches. /// This can be notably expensive for queries that rely on non-archetypal filters such as [`Added`](crate::query::Added), diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index df14530f48..ef26682a1b 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -428,10 +428,10 @@ unsafe impl<'a, D: QueryData + 'static, F: QueryFilter + 'static> SystemParam fo match query.single_inner() { Ok(_) => Ok(()), Err(QuerySingleError::NoEntities(_)) => Err( - SystemParamValidationError::skipped::("No matching entities"), + SystemParamValidationError::invalid::("No matching entities"), ), Err(QuerySingleError::MultipleEntities(_)) => Err( - SystemParamValidationError::skipped::("Multiple matching entities"), + SystemParamValidationError::invalid::("Multiple matching entities"), ), } } @@ -480,7 +480,7 @@ unsafe impl SystemParam state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick()) }; if query.is_empty() { - Err(SystemParamValidationError::skipped::( + Err(SystemParamValidationError::invalid::( "No matching entities", )) } else { @@ -1695,6 +1695,8 @@ unsafe impl ReadOnlySystemParam for Result(message: impl Into>) -> Self { - Self::new::(true, message, Cow::Borrowed("")) - } - /// Constructs a `SystemParamValidationError` for an invalid parameter that should be treated as an error. /// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`. pub fn invalid(message: impl Into>) -> Self { diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index 44ff0ef645..15df3ec1c9 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -221,9 +221,9 @@ pub type InputFocusSet = InputFocusSystems; /// Sets the initial focus to the primary window, if any. pub fn set_initial_focus( mut input_focus: ResMut, - window: Single>, + window: When>>, ) { - input_focus.0 = Some(*window); + input_focus.0 = Some(**window); } /// System which dispatches bubbled input events to the focused entity, or to the primary window diff --git a/examples/ecs/fallible_params.rs b/examples/ecs/fallible_params.rs index 94a8007aec..6a32e66c50 100644 --- a/examples/ecs/fallible_params.rs +++ b/examples/ecs/fallible_params.rs @@ -119,8 +119,8 @@ fn user_input( // System that moves the enemies in a circle. // Only runs if there are enemies, due to the `Populated` parameter. -fn move_targets(mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res