From 25cb339a1252965423e67d5768a6d13d79e9722e Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Thu, 17 Jul 2025 15:23:41 -0400 Subject: [PATCH] Don't ignore default query filters for `EntityRef` or `EntityMut` (#20163) # Objective Don't ignore default query filters for `EntityRef` or `EntityMut`. Currently, `Query` will include entities with a `Disabled` component, even though queries like `Query<()>` or `Query` would not. This was noticed in https://github.com/bevyengine/bevy/pull/19711#discussion_r2205981065. ## Solution Change `Access::contains` to completely ignore read access and just look at filters and archetypal access. Filters covers `With`, `Without`, `&`, and `&mut`, while archetypal covers `Has` and `Allows`. Note that `Option<&Disabled>` will no longer count as a use of `Disabled`, though. --- crates/bevy_ecs/src/entity_disabling.rs | 32 +++++++++++++++++-------- crates/bevy_ecs/src/query/access.rs | 7 +++--- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 5d62011174..41a461b77e 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -205,7 +205,7 @@ mod tests { use super::*; use crate::{ - prelude::World, + prelude::{EntityMut, EntityRef, World}, query::{Has, With}, }; use alloc::{vec, vec::Vec}; @@ -278,30 +278,42 @@ mod tests { let mut world = World::new(); world.register_disabling_component::(); + // Use powers of two so we can uniquely identify the set of matching archetypes from the count. world.spawn_empty(); - world.spawn(Disabled); - world.spawn(CustomDisabled); - world.spawn((Disabled, CustomDisabled)); + world.spawn_batch((0..2).map(|_| Disabled)); + world.spawn_batch((0..4).map(|_| CustomDisabled)); + world.spawn_batch((0..8).map(|_| (Disabled, CustomDisabled))); let mut query = world.query::<()>(); assert_eq!(1, query.iter(&world).count()); - let mut query = world.query_filtered::<(), With>(); + let mut query = world.query::(); assert_eq!(1, query.iter(&world).count()); + let mut query = world.query::(); + assert_eq!(1, query.iter(&world).count()); + + let mut query = world.query_filtered::<(), With>(); + assert_eq!(2, query.iter(&world).count()); + let mut query = world.query::>(); - assert_eq!(2, query.iter(&world).count()); + assert_eq!(3, query.iter(&world).count()); let mut query = world.query_filtered::<(), With>(); - assert_eq!(1, query.iter(&world).count()); + assert_eq!(4, query.iter(&world).count()); let mut query = world.query::>(); - assert_eq!(2, query.iter(&world).count()); + assert_eq!(5, query.iter(&world).count()); let mut query = world.query_filtered::<(), (With, With)>(); - assert_eq!(1, query.iter(&world).count()); + assert_eq!(8, query.iter(&world).count()); let mut query = world.query::<(Has, Has)>(); - assert_eq!(4, query.iter(&world).count()); + assert_eq!(15, query.iter(&world).count()); + + // This seems like it ought to count as a mention of `Disabled`, but it does not. + // We don't consider read access, since that would count `EntityRef` as a mention of *all* components. + let mut query = world.query::>(); + assert_eq!(1, query.iter(&world).count()); } } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 0c5b29f715..1f3291d538 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1225,10 +1225,11 @@ impl FilteredAccess { .flat_map(|f| f.without.ones().map(T::get_sparse_set_index)) } - /// Returns true if the index is used by this `FilteredAccess` in any way + /// Returns true if the index is used by this `FilteredAccess` in filters or archetypal access. + /// This includes most ways to access a component, but notably excludes `EntityRef` and `EntityMut` + /// along with anything inside `Option`. pub fn contains(&self, index: T) -> bool { - self.access().has_component_read(index.clone()) - || self.access().has_archetypal(index.clone()) + self.access().has_archetypal(index.clone()) || self.filter_sets.iter().any(|f| { f.with.contains(index.sparse_set_index()) || f.without.contains(index.sparse_set_index())