diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 0d37f61485c94..550e9a20e37df 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -58,8 +58,8 @@ pub mod prelude { }, system::{ Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local, - NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut, - Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder, + NonSend, NonSendMut, ParallelCommands, ParamSet, Populated, Query, ReadOnlySystem, Res, + ResMut, Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder, SystemParamFunction, }, world::{ diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 5e9e3d29ea185..f0116e60da50c 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -33,6 +33,15 @@ use core::{ /// /// [`World`]: crate::world::World /// +/// # Similar parameters +/// +/// [`Query`] has few sibling [`SystemParam`](crate::system::system_param::SystemParam)s, which perform additional validation: +/// - [`Single`] - Exactly one matching query item. +/// - [`Option`] - Zero or one matching query item. +/// - [`Populated`] - At least one matching query item. +/// +/// Those parameters will prevent systems from running if their requirements aren't met. +/// /// # System parameter declaration /// /// A query should always be declared as a system parameter. @@ -1667,3 +1676,36 @@ impl<'w, D: QueryData, F: QueryFilter> Single<'w, D, F> { self.item } } + +/// [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 systems that use this parameter to be skipped. +/// +/// 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) or [`Changed`](crate::query::Changed) +/// which must individually check each query result for a match. +/// +/// See [`Query`] for more details. +pub struct Populated<'w, 's, D: QueryData, F: QueryFilter = ()>(pub(crate) Query<'w, 's, D, F>); + +impl<'w, 's, D: QueryData, F: QueryFilter> Deref for Populated<'w, 's, D, F> { + type Target = Query<'w, 's, D, F>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Populated<'_, '_, D, F> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'w, 's, D: QueryData, F: QueryFilter> Populated<'w, 's, D, F> { + /// Returns the inner item with ownership. + pub fn into_inner(self) -> Query<'w, 's, D, F> { + self.0 + } +} diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 5a1b9c649388d..013d75265a900 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -26,6 +26,8 @@ use core::{ ops::{Deref, DerefMut}, }; +use super::Populated; + /// A parameter that can be used in a [`System`](super::System). /// /// # Derive @@ -497,6 +499,61 @@ unsafe impl<'a, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOn { } +// SAFETY: Relevant query ComponentId and ArchetypeComponentId access is applied to SystemMeta. If +// this Query conflicts with any prior access, a panic will occur. +unsafe impl SystemParam + for Populated<'_, '_, D, F> +{ + type State = QueryState; + type Item<'w, 's> = Populated<'w, 's, D, F>; + + fn init_state(world: &mut World, system_meta: &mut SystemMeta) -> Self::State { + Query::init_state(world, system_meta) + } + + unsafe fn new_archetype( + state: &mut Self::State, + archetype: &Archetype, + system_meta: &mut SystemMeta, + ) { + // SAFETY: Delegate to existing `SystemParam` implementations. + unsafe { Query::new_archetype(state, archetype, system_meta) }; + } + + #[inline] + unsafe fn get_param<'w, 's>( + state: &'s mut Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell<'w>, + change_tick: Tick, + ) -> Self::Item<'w, 's> { + // SAFETY: Delegate to existing `SystemParam` implementations. + let query = unsafe { Query::get_param(state, system_meta, world, change_tick) }; + Populated(query) + } + + #[inline] + unsafe fn validate_param( + state: &Self::State, + system_meta: &SystemMeta, + world: UnsafeWorldCell, + ) -> bool { + state.validate_world(world.id()); + // SAFETY: + // - We have read-only access to the components accessed by query. + // - The world has been validated. + !unsafe { + state.is_empty_unsafe_world_cell(world, system_meta.last_run, world.change_tick()) + } + } +} + +// SAFETY: QueryState is constrained to read-only fetches, so it only reads World. +unsafe impl<'w, 's, D: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> ReadOnlySystemParam + for Populated<'w, 's, D, F> +{ +} + /// A collection of potentially conflicting [`SystemParam`]s allowed by disjoint access. /// /// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as diff --git a/examples/ecs/fallible_params.rs b/examples/ecs/fallible_params.rs index 06fefc04418a3..397e883f9b2b9 100644 --- a/examples/ecs/fallible_params.rs +++ b/examples/ecs/fallible_params.rs @@ -2,9 +2,10 @@ //! from running if their acquiry conditions aren't met. //! //! Fallible parameters include: -//! - [`Res`], [`ResMut`] - If resource doesn't exist. -//! - [`Single`] - If there is no or more than one entities matching. -//! - [`Option>`] - If there are more than one entities matching. +//! - [`Res`], [`ResMut`] - Resource has to exist. +//! - [`Single`] - There must be exactly one matching entity. +//! - [`Option>`] - There must be zero or one matching entity. +//! - [`Populated`] - There must be at least one matching entity. use bevy::prelude::*; use rand::Rng; @@ -105,9 +106,9 @@ fn user_input( } // System that moves the enemies in a circle. -// TODO: Use [`NonEmptyQuery`] when it exists. -fn move_targets(mut enemies: Query<(&mut Transform, &mut Enemy)>, time: Res