Added Has<T> WorldQuery type (#8844)

# Objective

- Fixes #7811 

## Solution

- I added `Has<T>` (and `HasFetch<T>` ) and implemented `WorldQuery`,
`ReadonlyWorldQuery`, and `ArchetypeFilter` it
- I also added documentation with an example and a unit test


I believe I've done everything right but this is my first contribution
and I'm not an ECS expert so someone who is should probably check my
implementation. I based it on what `Or<With<T>,>`, would do. The only
difference is that `Has` does not update component access - adding `Has`
to a query should never affect whether or not it is disjoint with
another query *I think*.

---

## Changelog

## Added
- Added `Has<T>` WorldQuery to find out whether or not an entity has a
particular component.

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com>
This commit is contained in:
Mark Wainwright 2023-06-19 14:56:20 +01:00 committed by GitHub
parent 6ce4bf5181
commit 6529d2e7f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 159 additions and 1 deletions

View file

@ -1110,6 +1110,146 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
/// SAFETY: [`OptionFetch`] is read only because `T` is read only
unsafe impl<T: ReadOnlyWorldQuery> ReadOnlyWorldQuery for Option<T> {}
/// Returns a bool that describes if an entity has the component `T`.
///
/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities
/// have the component `T` but don't actually care about the component's value.
///
/// # Examples
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::query::Has;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::system::Query;
/// #
/// # #[derive(Component)]
/// # struct IsHungry;
/// # #[derive(Component)]
/// # struct Name { name: &'static str };
/// #
/// fn food_entity_system(query: Query<(&Name, Has<IsHungry>) >) {
/// for (name, is_hungry) in &query {
/// if is_hungry{
/// println!("{} would like some food.", name.name);
/// } else {
/// println!("{} has had sufficient.", name.name);
/// }
/// }
/// }
/// # bevy_ecs::system::assert_is_system(food_entity_system);
/// ```
///
/// ```
/// # use bevy_ecs::component::Component;
/// # use bevy_ecs::query::Has;
/// # use bevy_ecs::system::IntoSystem;
/// # use bevy_ecs::system::Query;
/// #
/// # #[derive(Component)]
/// # struct Alpha{has_beta: bool};
/// # #[derive(Component)]
/// # struct Beta { has_alpha: bool };
/// #
/// // Unlike `Option<&T>`, `Has<T>` is compatible with `&mut T`
/// // as it does not actually access any data.
/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has<Beta>)>, mut betas: Query<(&mut Beta, Has<Alpha>)>) {
/// for (mut alpha, has_beta) in alphas.iter_mut() {
/// alpha.has_beta = has_beta;
/// }
/// for (mut beta, has_alpha) in betas.iter_mut() {
/// beta.has_alpha = has_alpha;
/// }
/// }
/// # bevy_ecs::system::assert_is_system(alphabet_entity_system);
/// ```
pub struct Has<T>(PhantomData<T>);
// SAFETY: `Self::ReadOnly` is the same as `Self`
unsafe impl<T: Component> WorldQuery for Has<T> {
type Fetch<'w> = bool;
type Item<'w> = bool;
type ReadOnly = Self;
type State = ComponentId;
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
item
}
const IS_DENSE: bool = {
match T::Storage::STORAGE_TYPE {
StorageType::Table => true,
StorageType::SparseSet => false,
}
};
const IS_ARCHETYPAL: bool = true;
#[inline]
unsafe fn init_fetch<'w>(
_world: UnsafeWorldCell<'w>,
_state: &Self::State,
_last_run: Tick,
_this_run: Tick,
) -> Self::Fetch<'w> {
false
}
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
*fetch
}
#[inline]
unsafe fn set_archetype<'w>(
fetch: &mut Self::Fetch<'w>,
state: &Self::State,
archetype: &'w Archetype,
_table: &Table,
) {
*fetch = archetype.contains(*state);
}
#[inline]
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
*fetch = table.has_column(*state);
}
#[inline(always)]
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: TableRow,
) -> Self::Item<'w> {
*fetch
}
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {
// Do nothing as presence of `Has<T>` never affects whether two queries are disjoint
}
fn update_archetype_component_access(
_state: &Self::State,
_archetype: &Archetype,
_access: &mut Access<ArchetypeComponentId>,
) {
}
fn init_state(world: &mut World) -> ComponentId {
world.init_component::<T>()
}
fn matches_component_set(
_state: &Self::State,
_set_contains_id: &impl Fn(ComponentId) -> bool,
) -> bool {
// `Has<T>` always matches
true
}
}
/// SAFETY: [`Has`] is read only
unsafe impl<T: Component> ReadOnlyWorldQuery for Has<T> {}
macro_rules! impl_tuple_fetch {
($(($name: ident, $state: ident)),*) => {
#[allow(non_snake_case)]

View file

@ -64,7 +64,7 @@ impl<T> DebugCheckedUnwrap for Option<T> {
mod tests {
use super::{ReadOnlyWorldQuery, WorldQuery};
use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without};
use crate::query::{ArchetypeFilter, QueryCombinationIter};
use crate::query::{ArchetypeFilter, Has, QueryCombinationIter};
use crate::schedule::{IntoSystemConfigs, Schedule};
use crate::system::{IntoSystem, Query, System, SystemState};
use crate::{self as bevy_ecs, component::Component, world::World};
@ -476,6 +476,24 @@ mod tests {
);
}
#[test]
fn has_query() {
let mut world = World::new();
world.spawn((A(1), B(1)));
world.spawn(A(2));
world.spawn((A(3), B(1)));
world.spawn(A(4));
let values: Vec<(&A, bool)> = world.query::<(&A, Has<B>)>().iter(&world).collect();
// The query seems to put the components with B first
assert_eq!(
values,
vec![(&A(1), true), (&A(3), true), (&A(2), false), (&A(4), false),]
);
}
#[test]
#[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."]
fn self_conflicting_worldquery() {