Skip to content

Commit 6529d2e

Browse files
wainwrightmarkalice-i-cecilejoseph-gio
authored
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 <[email protected]> Co-authored-by: JoJoJet <[email protected]>
1 parent 6ce4bf5 commit 6529d2e

File tree

2 files changed

+159
-1
lines changed

2 files changed

+159
-1
lines changed

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,146 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
11101110
/// SAFETY: [`OptionFetch`] is read only because `T` is read only
11111111
unsafe impl<T: ReadOnlyWorldQuery> ReadOnlyWorldQuery for Option<T> {}
11121112

1113+
/// Returns a bool that describes if an entity has the component `T`.
1114+
///
1115+
/// This can be used in a [`Query`](crate::system::Query) if you want to know whether or not entities
1116+
/// have the component `T` but don't actually care about the component's value.
1117+
///
1118+
/// # Examples
1119+
///
1120+
/// ```
1121+
/// # use bevy_ecs::component::Component;
1122+
/// # use bevy_ecs::query::Has;
1123+
/// # use bevy_ecs::system::IntoSystem;
1124+
/// # use bevy_ecs::system::Query;
1125+
/// #
1126+
/// # #[derive(Component)]
1127+
/// # struct IsHungry;
1128+
/// # #[derive(Component)]
1129+
/// # struct Name { name: &'static str };
1130+
/// #
1131+
/// fn food_entity_system(query: Query<(&Name, Has<IsHungry>) >) {
1132+
/// for (name, is_hungry) in &query {
1133+
/// if is_hungry{
1134+
/// println!("{} would like some food.", name.name);
1135+
/// } else {
1136+
/// println!("{} has had sufficient.", name.name);
1137+
/// }
1138+
/// }
1139+
/// }
1140+
/// # bevy_ecs::system::assert_is_system(food_entity_system);
1141+
/// ```
1142+
///
1143+
/// ```
1144+
/// # use bevy_ecs::component::Component;
1145+
/// # use bevy_ecs::query::Has;
1146+
/// # use bevy_ecs::system::IntoSystem;
1147+
/// # use bevy_ecs::system::Query;
1148+
/// #
1149+
/// # #[derive(Component)]
1150+
/// # struct Alpha{has_beta: bool};
1151+
/// # #[derive(Component)]
1152+
/// # struct Beta { has_alpha: bool };
1153+
/// #
1154+
/// // Unlike `Option<&T>`, `Has<T>` is compatible with `&mut T`
1155+
/// // as it does not actually access any data.
1156+
/// fn alphabet_entity_system(mut alphas: Query<(&mut Alpha, Has<Beta>)>, mut betas: Query<(&mut Beta, Has<Alpha>)>) {
1157+
/// for (mut alpha, has_beta) in alphas.iter_mut() {
1158+
/// alpha.has_beta = has_beta;
1159+
/// }
1160+
/// for (mut beta, has_alpha) in betas.iter_mut() {
1161+
/// beta.has_alpha = has_alpha;
1162+
/// }
1163+
/// }
1164+
/// # bevy_ecs::system::assert_is_system(alphabet_entity_system);
1165+
/// ```
1166+
pub struct Has<T>(PhantomData<T>);
1167+
1168+
// SAFETY: `Self::ReadOnly` is the same as `Self`
1169+
unsafe impl<T: Component> WorldQuery for Has<T> {
1170+
type Fetch<'w> = bool;
1171+
type Item<'w> = bool;
1172+
type ReadOnly = Self;
1173+
type State = ComponentId;
1174+
1175+
fn shrink<'wlong: 'wshort, 'wshort>(item: Self::Item<'wlong>) -> Self::Item<'wshort> {
1176+
item
1177+
}
1178+
1179+
const IS_DENSE: bool = {
1180+
match T::Storage::STORAGE_TYPE {
1181+
StorageType::Table => true,
1182+
StorageType::SparseSet => false,
1183+
}
1184+
};
1185+
1186+
const IS_ARCHETYPAL: bool = true;
1187+
1188+
#[inline]
1189+
unsafe fn init_fetch<'w>(
1190+
_world: UnsafeWorldCell<'w>,
1191+
_state: &Self::State,
1192+
_last_run: Tick,
1193+
_this_run: Tick,
1194+
) -> Self::Fetch<'w> {
1195+
false
1196+
}
1197+
1198+
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w> {
1199+
*fetch
1200+
}
1201+
1202+
#[inline]
1203+
unsafe fn set_archetype<'w>(
1204+
fetch: &mut Self::Fetch<'w>,
1205+
state: &Self::State,
1206+
archetype: &'w Archetype,
1207+
_table: &Table,
1208+
) {
1209+
*fetch = archetype.contains(*state);
1210+
}
1211+
1212+
#[inline]
1213+
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
1214+
*fetch = table.has_column(*state);
1215+
}
1216+
1217+
#[inline(always)]
1218+
unsafe fn fetch<'w>(
1219+
fetch: &mut Self::Fetch<'w>,
1220+
_entity: Entity,
1221+
_table_row: TableRow,
1222+
) -> Self::Item<'w> {
1223+
*fetch
1224+
}
1225+
1226+
fn update_component_access(_state: &Self::State, _access: &mut FilteredAccess<ComponentId>) {
1227+
// Do nothing as presence of `Has<T>` never affects whether two queries are disjoint
1228+
}
1229+
1230+
fn update_archetype_component_access(
1231+
_state: &Self::State,
1232+
_archetype: &Archetype,
1233+
_access: &mut Access<ArchetypeComponentId>,
1234+
) {
1235+
}
1236+
1237+
fn init_state(world: &mut World) -> ComponentId {
1238+
world.init_component::<T>()
1239+
}
1240+
1241+
fn matches_component_set(
1242+
_state: &Self::State,
1243+
_set_contains_id: &impl Fn(ComponentId) -> bool,
1244+
) -> bool {
1245+
// `Has<T>` always matches
1246+
true
1247+
}
1248+
}
1249+
1250+
/// SAFETY: [`Has`] is read only
1251+
unsafe impl<T: Component> ReadOnlyWorldQuery for Has<T> {}
1252+
11131253
macro_rules! impl_tuple_fetch {
11141254
($(($name: ident, $state: ident)),*) => {
11151255
#[allow(non_snake_case)]

crates/bevy_ecs/src/query/mod.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ impl<T> DebugCheckedUnwrap for Option<T> {
6464
mod tests {
6565
use super::{ReadOnlyWorldQuery, WorldQuery};
6666
use crate::prelude::{AnyOf, Changed, Entity, Or, QueryState, With, Without};
67-
use crate::query::{ArchetypeFilter, QueryCombinationIter};
67+
use crate::query::{ArchetypeFilter, Has, QueryCombinationIter};
6868
use crate::schedule::{IntoSystemConfigs, Schedule};
6969
use crate::system::{IntoSystem, Query, System, SystemState};
7070
use crate::{self as bevy_ecs, component::Component, world::World};
@@ -476,6 +476,24 @@ mod tests {
476476
);
477477
}
478478

479+
#[test]
480+
fn has_query() {
481+
let mut world = World::new();
482+
483+
world.spawn((A(1), B(1)));
484+
world.spawn(A(2));
485+
world.spawn((A(3), B(1)));
486+
world.spawn(A(4));
487+
488+
let values: Vec<(&A, bool)> = world.query::<(&A, Has<B>)>().iter(&world).collect();
489+
490+
// The query seems to put the components with B first
491+
assert_eq!(
492+
values,
493+
vec![(&A(1), true), (&A(3), true), (&A(2), false), (&A(4), false),]
494+
);
495+
}
496+
479497
#[test]
480498
#[should_panic = "&mut bevy_ecs::query::tests::A conflicts with a previous access in this query."]
481499
fn self_conflicting_worldquery() {

0 commit comments

Comments
 (0)