Populated
(query) system param (#15488)
# Objective Add a `Populated` system parameter that acts like `Query`, but prevents system from running if there are no matching entities. Fixes: #15302 ## Solution Implement the system param which newtypes the `Query`. The only change is new validation, which fails if query is empty. The new system param is used in `fallible_params` example. ## Testing Ran `fallible_params` example. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
This commit is contained in:
parent
397f20e835
commit
fc93e13c36
@ -58,8 +58,8 @@ pub mod prelude {
|
|||||||
},
|
},
|
||||||
system::{
|
system::{
|
||||||
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
|
Commands, Deferred, EntityCommand, EntityCommands, In, InMut, InRef, IntoSystem, Local,
|
||||||
NonSend, NonSendMut, ParallelCommands, ParamSet, Query, ReadOnlySystem, Res, ResMut,
|
NonSend, NonSendMut, ParallelCommands, ParamSet, Populated, Query, ReadOnlySystem, Res,
|
||||||
Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder,
|
ResMut, Resource, Single, System, SystemIn, SystemInput, SystemParamBuilder,
|
||||||
SystemParamFunction,
|
SystemParamFunction,
|
||||||
},
|
},
|
||||||
world::{
|
world::{
|
||||||
|
@ -33,6 +33,15 @@ use core::{
|
|||||||
///
|
///
|
||||||
/// [`World`]: crate::world::World
|
/// [`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<Single>`] - 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
|
/// # System parameter declaration
|
||||||
///
|
///
|
||||||
/// A query should always be declared as a system parameter.
|
/// 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
|
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<D: QueryData, F: QueryFilter> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -26,6 +26,8 @@ use core::{
|
|||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::Populated;
|
||||||
|
|
||||||
/// A parameter that can be used in a [`System`](super::System).
|
/// A parameter that can be used in a [`System`](super::System).
|
||||||
///
|
///
|
||||||
/// # Derive
|
/// # 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<D: QueryData + 'static, F: QueryFilter + 'static> SystemParam
|
||||||
|
for Populated<'_, '_, D, F>
|
||||||
|
{
|
||||||
|
type State = QueryState<D, F>;
|
||||||
|
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.
|
/// 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
|
/// Allows systems to safely access and interact with up to 8 mutually exclusive [`SystemParam`]s, such as
|
||||||
|
@ -2,9 +2,10 @@
|
|||||||
//! from running if their acquiry conditions aren't met.
|
//! from running if their acquiry conditions aren't met.
|
||||||
//!
|
//!
|
||||||
//! Fallible parameters include:
|
//! Fallible parameters include:
|
||||||
//! - [`Res<R>`], [`ResMut<R>`] - If resource doesn't exist.
|
//! - [`Res<R>`], [`ResMut<R>`] - Resource has to exist.
|
||||||
//! - [`Single<D, F>`] - If there is no or more than one entities matching.
|
//! - [`Single<D, F>`] - There must be exactly one matching entity.
|
||||||
//! - [`Option<Single<D, F>>`] - If there are more than one entities matching.
|
//! - [`Option<Single<D, F>>`] - There must be zero or one matching entity.
|
||||||
|
//! - [`Populated<D, F>`] - There must be at least one matching entity.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
@ -105,9 +106,9 @@ fn user_input(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// System that moves the enemies in a circle.
|
// System that moves the enemies in a circle.
|
||||||
// TODO: Use [`NonEmptyQuery`] when it exists.
|
// Only runs if there are enemies.
|
||||||
fn move_targets(mut enemies: Query<(&mut Transform, &mut Enemy)>, time: Res<Time>) {
|
fn move_targets(mut enemies: Populated<(&mut Transform, &mut Enemy)>, time: Res<Time>) {
|
||||||
for (mut transform, mut target) in &mut enemies {
|
for (mut transform, mut target) in &mut *enemies {
|
||||||
target.rotation += target.rotation_speed * time.delta_seconds();
|
target.rotation += target.rotation_speed * time.delta_seconds();
|
||||||
transform.rotation = Quat::from_rotation_z(target.rotation);
|
transform.rotation = Quat::from_rotation_z(target.rotation);
|
||||||
let offset = transform.right() * target.radius;
|
let offset = transform.right() * target.radius;
|
||||||
|
Loading…
Reference in New Issue
Block a user