Implement AnyOf queries (#2889)
Implements a new Queryable called AnyOf, which will return an item as long as at least one of it's requested Queryables returns something. For example, a `Query<AnyOf<(&A, &B, &C)>>` will return items with type `(Option<&A>, Option<&B>, Option<&C>)`, and will guarantee that for every element at least one of the option s is Some. This is a shorthand for queries like `Query<(Option<&A>, Option<&B>, Option<&C>), Or<(With<A>, With<B>, With&C>)>>`. Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
a0af066af7
commit
7604665880
@ -26,7 +26,7 @@ pub mod prelude {
|
|||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::{EventReader, EventWriter},
|
event::{EventReader, EventWriter},
|
||||||
query::{Added, ChangeTrackers, Changed, Or, QueryState, With, Without},
|
query::{Added, AnyOf, ChangeTrackers, Changed, Or, QueryState, With, Without},
|
||||||
schedule::{
|
schedule::{
|
||||||
AmbiguitySetLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion,
|
AmbiguitySetLabel, ExclusiveSystemDescriptorCoercion, ParallelSystemDescriptorCoercion,
|
||||||
RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel, RunCriteriaPiping,
|
RunCriteria, RunCriteriaDescriptorCoercion, RunCriteriaLabel, RunCriteriaPiping,
|
||||||
|
|||||||
@ -1103,7 +1103,120 @@ macro_rules! impl_tuple_fetch {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The `AnyOf` query parameter fetches entities with any of the component types included in T.
|
||||||
|
///
|
||||||
|
/// `Query<AnyOf<(&A, &B, &mut C)>>` is equivalent to `Query<(Option<&A>, Option<&B>, Option<&mut C>), (Or(With<A>, With<B>, With<C>)>`.
|
||||||
|
/// Each of the components in `T` is returned as an `Option`, as with `Option<A>` queries.
|
||||||
|
/// Entities are guaranteed to have at least one of the components in `T`.
|
||||||
|
pub struct AnyOf<T>(T);
|
||||||
|
|
||||||
|
macro_rules! impl_anytuple_fetch {
|
||||||
|
($(($name: ident, $state: ident)),*) => {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
impl<'w, 's, $($name: Fetch<'w, 's>),*> Fetch<'w, 's> for AnyOf<($(($name, bool),)*)> {
|
||||||
|
type Item = ($(Option<$name::Item>,)*);
|
||||||
|
type State = AnyOf<($($name::State,)*)>;
|
||||||
|
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
unsafe fn init(_world: &World, state: &Self::State, _last_change_tick: u32, _change_tick: u32) -> Self {
|
||||||
|
let ($($name,)*) = &state.0;
|
||||||
|
AnyOf(($(($name::init(_world, $name, _last_change_tick, _change_tick), false),)*))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const IS_DENSE: bool = true $(&& $name::IS_DENSE)*;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn set_archetype(&mut self, _state: &Self::State, _archetype: &Archetype, _tables: &Tables) {
|
||||||
|
let ($($name,)*) = &mut self.0;
|
||||||
|
let ($($state,)*) = &_state.0;
|
||||||
|
$(
|
||||||
|
$name.1 = $state.matches_archetype(_archetype);
|
||||||
|
if $name.1 {
|
||||||
|
$name.0.set_archetype($state, _archetype, _tables);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
unsafe fn set_table(&mut self, _state: &Self::State, _table: &Table) {
|
||||||
|
let ($($name,)*) = &mut self.0;
|
||||||
|
let ($($state,)*) = &_state.0;
|
||||||
|
$(
|
||||||
|
$name.1 = $state.matches_table(_table);
|
||||||
|
if $name.1 {
|
||||||
|
$name.0.set_table($state, _table);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
unsafe fn table_fetch(&mut self, _table_row: usize) -> Self::Item {
|
||||||
|
let ($($name,)*) = &mut self.0;
|
||||||
|
($(
|
||||||
|
$name.1.then(|| $name.0.table_fetch(_table_row)),
|
||||||
|
)*)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
unsafe fn archetype_fetch(&mut self, _archetype_index: usize) -> Self::Item {
|
||||||
|
let ($($name,)*) = &mut self.0;
|
||||||
|
($(
|
||||||
|
$name.1.then(|| $name.0.archetype_fetch(_archetype_index)),
|
||||||
|
)*)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: update_component_access and update_archetype_component_access are called for each item in the tuple
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::unused_unit)]
|
||||||
|
unsafe impl<$($name: FetchState),*> FetchState for AnyOf<($($name,)*)> {
|
||||||
|
fn init(_world: &mut World) -> Self {
|
||||||
|
AnyOf(($($name::init(_world),)*))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_component_access(&self, _access: &mut FilteredAccess<ComponentId>) {
|
||||||
|
let ($($name,)*) = &self.0;
|
||||||
|
$($name.update_component_access(_access);)*
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_archetype_component_access(&self, _archetype: &Archetype, _access: &mut Access<ArchetypeComponentId>) {
|
||||||
|
let ($($name,)*) = &self.0;
|
||||||
|
$(
|
||||||
|
if $name.matches_archetype(_archetype) {
|
||||||
|
$name.update_archetype_component_access(_archetype, _access);
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_archetype(&self, _archetype: &Archetype) -> bool {
|
||||||
|
let ($($name,)*) = &self.0;
|
||||||
|
false $(|| $name.matches_archetype(_archetype))*
|
||||||
|
}
|
||||||
|
|
||||||
|
fn matches_table(&self, _table: &Table) -> bool {
|
||||||
|
let ($($name,)*) = &self.0;
|
||||||
|
false $(|| $name.matches_table(_table))*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($name: WorldQuery),*> WorldQuery for AnyOf<($($name,)*)> {
|
||||||
|
type Fetch = AnyOf<($(($name::Fetch, bool),)*)>;
|
||||||
|
type ReadOnlyFetch = AnyOf<($(($name::ReadOnlyFetch, bool),)*)>;
|
||||||
|
|
||||||
|
type State = AnyOf<($($name::State,)*)>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// SAFETY: each item in the tuple is read only
|
||||||
|
unsafe impl<$($name: ReadOnlyFetch),*> ReadOnlyFetch for AnyOf<($(($name, bool),)*)> {}
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
all_tuples!(impl_tuple_fetch, 0, 15, F, S);
|
all_tuples!(impl_tuple_fetch, 0, 15, F, S);
|
||||||
|
all_tuples!(impl_anytuple_fetch, 0, 15, F, S);
|
||||||
|
|
||||||
/// [`Fetch`] that does not actually fetch anything
|
/// [`Fetch`] that does not actually fetch anything
|
||||||
///
|
///
|
||||||
|
|||||||
@ -12,12 +12,15 @@ pub use state::*;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::AnyOf;
|
||||||
use crate::{self as bevy_ecs, component::Component, world::World};
|
use crate::{self as bevy_ecs, component::Component, world::World};
|
||||||
|
|
||||||
#[derive(Component, Debug, Eq, PartialEq)]
|
#[derive(Component, Debug, Eq, PartialEq)]
|
||||||
struct A(usize);
|
struct A(usize);
|
||||||
#[derive(Component, Debug, Eq, PartialEq)]
|
#[derive(Component, Debug, Eq, PartialEq)]
|
||||||
struct B(usize);
|
struct B(usize);
|
||||||
|
#[derive(Component, Debug, Eq, PartialEq)]
|
||||||
|
struct C(usize);
|
||||||
|
|
||||||
#[derive(Component, Debug, Eq, PartialEq)]
|
#[derive(Component, Debug, Eq, PartialEq)]
|
||||||
#[component(storage = "SparseSet")]
|
#[component(storage = "SparseSet")]
|
||||||
@ -184,4 +187,21 @@ mod tests {
|
|||||||
let values = world.query::<&B>().iter(&world).collect::<Vec<&B>>();
|
let values = world.query::<&B>().iter(&world).collect::<Vec<&B>>();
|
||||||
assert_eq!(values, vec![&B(3)]);
|
assert_eq!(values, vec![&B(3)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn any_query() {
|
||||||
|
let mut world = World::new();
|
||||||
|
|
||||||
|
world.spawn().insert_bundle((A(1), B(2)));
|
||||||
|
world.spawn().insert_bundle((A(2),));
|
||||||
|
world.spawn().insert_bundle((C(3),));
|
||||||
|
|
||||||
|
let values: Vec<(Option<&A>, Option<&B>)> =
|
||||||
|
world.query::<AnyOf<(&A, &B)>>().iter(&world).collect();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
values,
|
||||||
|
vec![(Some(&A(1)), Some(&B(2))), (Some(&A(2)), None),]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user