This commit is contained in:
Chris Russell 2025-07-18 09:43:44 -04:00 committed by GitHub
commit c6383356ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 48 additions and 26 deletions

View File

@ -318,7 +318,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,
};
@ -341,12 +341,12 @@ mod tests {
#[derive(Resource, Default)]
struct Counter(u8);
fn set_single_state(mut _single: Single<&TestComponent>, mut state: ResMut<TestState>) {
fn set_single_state(mut _single: When<Single<&TestComponent>>, mut state: ResMut<TestState>) {
state.single_ran = true;
}
fn set_populated_state(
mut _populated: Populated<&TestComponent>,
mut _populated: When<Populated<&TestComponent>>,
mut state: ResMut<TestState>,
) {
state.populated_ran = true;
@ -418,7 +418,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<Single<&TestComponent>>) -> u8 {
42
}
@ -446,7 +446,11 @@ mod tests {
}
// This system should be skipped when run due to no matching entity
fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
fn pipe_in(
_input: In<u8>,
_single: When<Single<&TestComponent>>,
mut counter: ResMut<Counter>,
) {
counter.0 += 1;
}
@ -499,7 +503,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<Single<&TestComponent>>) -> u8 {
42
}
@ -523,7 +527,7 @@ mod tests {
}
// This system should be skipped when run due to no matching entity
fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>) {}
fn pipe_in(_input: In<u8>, _single: When<Single<&TestComponent>>) {}
let mut world = World::new();
let mut schedule = Schedule::default();
@ -555,13 +559,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<Counter>) -> u8 {
fn pipe_out(_single: When<Single<&TestComponent>>, mut counter: ResMut<Counter>) -> u8 {
counter.0 += 1;
42
}
// This system should be skipped when run due to no matching entity
fn pipe_in(_input: In<u8>, _single: Single<&TestComponent>, mut counter: ResMut<Counter>) {
fn pipe_in(
_input: In<u8>,
_single: When<Single<&TestComponent>>,
mut counter: ResMut<Counter>,
) {
counter.0 += 1;
}

View File

@ -2581,9 +2581,11 @@ 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<Single<D, F>>`] instead if zero or one matching entities can exist.
/// Use [`Option<Single<D, F>>`] instead to run the system but get `None` when there are zero or multiple matching entities.
///
/// Use [`When<Single<D, F>>`](crate::system::When) instead to skip the system when there are zero or multiple matching entities.
///
/// See [`Query`] for more details.
///
@ -2632,7 +2634,9 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Single<'w, 's, 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<Populated<D, F>>`](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),

View File

@ -448,10 +448,10 @@ unsafe impl<'a, 'b, D: QueryData + 'static, F: QueryFilter + 'static> SystemPara
match query.single_inner() {
Ok(_) => Ok(()),
Err(QuerySingleError::NoEntities(_)) => Err(
SystemParamValidationError::skipped::<Self>("No matching entities"),
SystemParamValidationError::invalid::<Self>("No matching entities"),
),
Err(QuerySingleError::MultipleEntities(_)) => Err(
SystemParamValidationError::skipped::<Self>("Multiple matching entities"),
SystemParamValidationError::invalid::<Self>("Multiple matching entities"),
),
}
}
@ -509,7 +509,7 @@ unsafe impl<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
state.query_unchecked_with_ticks(world, system_meta.last_run, world.change_tick())
};
if query.is_empty() {
Err(SystemParamValidationError::skipped::<Self>(
Err(SystemParamValidationError::invalid::<Self>(
"No matching entities",
))
} else {
@ -1836,6 +1836,8 @@ unsafe impl<T: ReadOnlySystemParam> ReadOnlySystemParam for Result<T, SystemPara
/// A [`SystemParam`] that wraps another parameter and causes its system to skip instead of failing when the parameter is invalid.
///
/// This is especially useful with [`Single`] and [`Populated`].
///
/// # Example
///
/// ```
@ -2803,12 +2805,6 @@ pub struct SystemParamValidationError {
}
impl SystemParamValidationError {
/// Constructs a `SystemParamValidationError` that skips the system.
/// The parameter name is initialized to the type name of `T`, so a `SystemParam` should usually pass `Self`.
pub fn skipped<T>(message: impl Into<Cow<'static, str>>) -> Self {
Self::new::<T>(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<T>(message: impl Into<Cow<'static, str>>) -> Self {

View File

@ -243,10 +243,10 @@ pub type InputFocusSet = InputFocusSystems;
/// If no entity is focused, sets the focus to the primary window, if any.
pub fn set_initial_focus(
mut input_focus: ResMut<InputFocus>,
window: Single<Entity, With<PrimaryWindow>>,
window: When<Single<Entity, With<PrimaryWindow>>>,
) {
if input_focus.0.is_none() {
input_focus.0 = Some(*window);
input_focus.0 = Some(**window);
}
}

View File

@ -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<Time>) {
for (mut transform, mut target) in &mut *enemies {
fn move_targets(mut enemies: When<Populated<(&mut Transform, &mut Enemy)>>, time: Res<Time>) {
for (mut transform, mut target) in &mut **enemies {
target.rotation += target.rotation_speed * time.delta_secs();
transform.rotation = Quat::from_rotation_z(target.rotation);
let offset = transform.right() * target.radius;
@ -160,4 +160,4 @@ fn track_targets(
/// This system always fails param validation, because we never
/// create an entity with both [`Player`] and [`Enemy`] components.
fn do_nothing_fail_validation(_: Single<(), (With<Player>, With<Enemy>)>) {}
fn do_nothing_fail_validation(_: When<Single<(), (With<Player>, With<Enemy>)>>) {}

View File

@ -0,0 +1,14 @@
---
title: "`Single` and `Populated` now fail instead of skipping"
pull_requests: [19489, 18765]
---
`Single<D, F>` and `Populated<D, F>` now cause systems to fail instead of skip.
The introduction of `When` makes it possible to skip systems for any invalid parameter, such as `When<Res<R>>`.
The change to the behavior of `Single` and `Populated` keeps them consistent with other parameters,
and makes it possible to use them as assertions instead of only as run conditions.
Replace `Single<D, F>` with `When<Single<D, F>>`
and `Populated<D, F>` with `When<Populated<D, F>>`
to restore the old behavior.