Skip to content

Commit

Permalink
Added Has<T> WorldQuery type (bevyengine#8844)
Browse files Browse the repository at this point in the history
# Objective

- Fixes bevyengine#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>
  • Loading branch information
3 people authored and james7132 committed Jun 19, 2023
1 parent 80b7878 commit f673ee4
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 1 deletion.
140 changes: 140 additions & 0 deletions crates/bevy_ecs/src/query/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
20 changes: 19 additions & 1 deletion crates/bevy_ecs/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit f673ee4

Please sign in to comment.