diff --git a/crates/bevy_ecs/src/query/fetch.rs b/crates/bevy_ecs/src/query/fetch.rs index cd0bb152d3..574bb2c4fb 100644 --- a/crates/bevy_ecs/src/query/fetch.rs +++ b/crates/bevy_ecs/src/query/fetch.rs @@ -1110,6 +1110,146 @@ unsafe impl WorldQuery for Option { /// SAFETY: [`OptionFetch`] is read only because `T` is read only unsafe impl ReadOnlyWorldQuery for Option {} +/// 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) >) { +/// 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` is compatible with `&mut T` +/// // as it does not actually access any data. +/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has)>, mut betas: Query<(&mut Beta, Has)>) { +/// 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(PhantomData); + +// SAFETY: `Self::ReadOnly` is the same as `Self` +unsafe impl WorldQuery for Has { + 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) { + // Do nothing as presence of `Has` never affects whether two queries are disjoint + } + + fn update_archetype_component_access( + _state: &Self::State, + _archetype: &Archetype, + _access: &mut Access, + ) { + } + + fn init_state(world: &mut World) -> ComponentId { + world.init_component::() + } + + fn matches_component_set( + _state: &Self::State, + _set_contains_id: &impl Fn(ComponentId) -> bool, + ) -> bool { + // `Has` always matches + true + } +} + +/// SAFETY: [`Has`] is read only +unsafe impl ReadOnlyWorldQuery for Has {} + macro_rules! impl_tuple_fetch { ($(($name: ident, $state: ident)),*) => { #[allow(non_snake_case)] diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 1c6185e4e8..458addde9c 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -64,7 +64,7 @@ impl DebugCheckedUnwrap for Option { 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)>().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() {