bevy_ecs/
entity_disabling.rs

1//! Disabled entities do not show up in queries unless the query explicitly mentions them.
2//!
3//! Entities which are disabled in this way are not removed from the [`World`],
4//! and their relationships remain intact.
5//! In many cases, you may want to disable entire trees of entities at once,
6//! using [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive).
7//!
8//! While Bevy ships with a built-in [`Disabled`] component, you can also create your own
9//! disabling components, which will operate in the same way but can have distinct semantics.
10//!
11//! ```
12//! use bevy_ecs::prelude::*;
13//!
14//! // Our custom disabling component!
15//! #[derive(Component, Clone)]
16//! struct Prefab;
17//!
18//! #[derive(Component)]
19//! struct A;
20//!
21//! let mut world = World::new();
22//! world.register_disabling_component::<Prefab>();
23//! world.spawn((A, Prefab));
24//! world.spawn((A,));
25//! world.spawn((A,));
26//!
27//! let mut normal_query = world.query::<&A>();
28//! assert_eq!(2, normal_query.iter(&world).count());
29//!
30//! let mut prefab_query = world.query_filtered::<&A, With<Prefab>>();
31//! assert_eq!(1, prefab_query.iter(&world).count());
32//!
33//! let mut maybe_prefab_query = world.query::<(&A, Has<Prefab>)>();
34//! assert_eq!(3, maybe_prefab_query.iter(&world).count());
35//! ```
36//!
37//! ## Default query filters
38//!
39//! In Bevy, entity disabling is implemented through the construction of a global "default query filter" resource.
40//! Queries which do not explicitly mention the disabled component will not include entities with that component.
41//! If an entity has multiple disabling components, it will only be included in queries that mention all of them.
42//!
43//! For example, `Query<&Position>` will not include entities with the [`Disabled`] component,
44//! even if they have a `Position` component,
45//! but `Query<&Position, With<Disabled>>` or `Query<(&Position, Has<Disabled>)>` will see them.
46//!
47//! The [`Allow`](crate::query::Allow) query filter is designed to be used with default query filters,
48//! and ensures that the query will include entities both with and without the specified disabling component.
49//!
50//! Entities with disabling components are still present in the [`World`] and can be accessed directly,
51//! using methods on [`World`] or [`Commands`](crate::prelude::Commands).
52//!
53//! As default query filters are implemented through a resource,
54//! it's possible to temporarily ignore any default filters by using [`World::resource_scope`](crate::prelude::World).
55//!
56//! ```
57//! use bevy_ecs::prelude::*;
58//! use bevy_ecs::entity_disabling::{DefaultQueryFilters, Disabled};
59//!
60//! let mut world = World::default();
61//!
62//! #[derive(Component)]
63//! struct CustomDisabled;
64//!
65//! world.register_disabling_component::<CustomDisabled>();
66//!
67//! world.spawn(Disabled);
68//! world.spawn(CustomDisabled);
69//!
70//! // resource_scope removes DefaultQueryFilters temporarily before re-inserting into the world.
71//! world.resource_scope(|world: &mut World, _: Mut<DefaultQueryFilters>| {
72//!     // within this scope, we can query like no components are disabled.
73//!     assert_eq!(world.query::<&Disabled>().query(&world).count(), 1);
74//!     assert_eq!(world.query::<&CustomDisabled>().query(&world).count(), 1);
75//!     assert_eq!(world.query::<()>().query(&world).count(), world.entities().len() as usize);
76//! })
77//! ```
78//!
79//! ### Warnings
80//!
81//! Currently, only queries for which the cache is built after enabling a default query filter will have entities
82//! with those components filtered. As a result, they should generally only be modified before the
83//! app starts.
84//!
85//! Because filters are applied to all queries they can have performance implication for
86//! the enire [`World`], especially when they cause queries to mix sparse and table components.
87//! See [`Query` performance] for more info.
88//!
89//! Custom disabling components can cause significant interoperability issues within the ecosystem,
90//! as users must be aware of each disabling component in use.
91//! Libraries should think carefully about whether they need to use a new disabling component,
92//! and clearly communicate their presence to their users to avoid the new for library compatibility flags.
93//!
94//! [`With`]: crate::prelude::With
95//! [`Has`]: crate::prelude::Has
96//! [`World`]: crate::prelude::World
97//! [`Query` performance]: crate::prelude::Query#performance
98
99use crate::{
100    component::{ComponentId, Components, StorageType},
101    query::FilteredAccess,
102    world::{FromWorld, World},
103};
104use bevy_ecs_macros::{Component, Resource};
105use smallvec::SmallVec;
106
107#[cfg(feature = "bevy_reflect")]
108use {
109    crate::reflect::ReflectComponent, bevy_reflect::std_traits::ReflectDefault,
110    bevy_reflect::Reflect,
111};
112
113/// A marker component for disabled entities.
114///
115/// Semantically, this component is used to mark entities that are temporarily disabled (typically for gameplay reasons),
116/// but will likely be re-enabled at some point.
117///
118/// Like all disabling components, this only disables the entity itself,
119/// not its children or other entities that reference it.
120/// To disable an entire tree of entities, use [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive).
121///
122/// Every [`World`] has a default query filter that excludes entities with this component,
123/// registered in the [`DefaultQueryFilters`] resource.
124/// See [the module docs] for more info.
125///
126/// [the module docs]: crate::entity_disabling
127#[derive(Component, Clone, Debug, Default)]
128#[cfg_attr(
129    feature = "bevy_reflect",
130    derive(Reflect),
131    reflect(Component),
132    reflect(Debug, Clone, Default)
133)]
134// This component is registered as a disabling component during World::bootstrap
135pub struct Disabled;
136
137/// A marker component for internal entities.
138///
139/// This component is used to mark entities as being internal to the engine.
140/// These entities should be hidden from the developer's view by default,
141/// as they are both noisy and expose confusing implementation details.
142/// Internal entities are hidden from queries using [`DefaultQueryFilters`].
143/// For more information, see [the module docs].
144/// We strongly advise against altering, removing or relying on entities tagged with this component in any way.
145/// These are "internal implementation details", and may not be robust to these changes or stable across minor Bevy versions.
146///
147/// [the module docs]: crate::entity_disabling
148#[derive(Component, Clone, Debug, Default)]
149#[cfg_attr(
150    feature = "bevy_reflect",
151    derive(Reflect),
152    reflect(Component),
153    reflect(Debug, Clone, Default)
154)]
155// This component is registered as a disabling component during World::bootstrap
156pub struct Internal;
157
158/// Default query filters work by excluding entities with certain components from most queries.
159///
160/// If a query does not explicitly mention a given disabling component, it will not include entities with that component.
161/// To be more precise, this checks if the query's [`FilteredAccess`] contains the component,
162/// and if it does not, adds a [`Without`](crate::prelude::Without) filter for that component to the query.
163///
164/// [`Allow`](crate::query::Allow) and [`Has`](crate::prelude::Has) can be used to include entities
165/// with and without the disabling component.
166/// [`Allow`](crate::query::Allow) is a [`QueryFilter`](crate::query::QueryFilter) and will simply change
167/// the list of shown entities, while [`Has`](crate::prelude::Has) is a [`QueryData`](crate::query::QueryData)
168/// and will allow you to see if each entity has the disabling component or not.
169///
170/// This resource is initialized in the [`World`] whenever a new world is created,
171/// with the [`Disabled`] component as a disabling component.
172///
173/// Note that you can remove default query filters by overwriting the [`DefaultQueryFilters`] resource.
174/// This can be useful as a last resort escape hatch, but is liable to break compatibility with other libraries.
175///
176/// See the [module docs](crate::entity_disabling) for more info.
177///
178///
179/// # Warning
180///
181/// Default query filters are a global setting that affects all queries in the [`World`],
182/// and incur a small performance cost for each query.
183///
184/// They can cause significant interoperability issues within the ecosystem,
185/// as users must be aware of each disabling component in use.
186///
187/// Think carefully about whether you need to use a new disabling component,
188/// and clearly communicate their presence in any libraries you publish.
189#[derive(Resource, Debug)]
190#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
191pub struct DefaultQueryFilters {
192    // We only expect a few components per application to act as disabling components, so we use a SmallVec here
193    // to avoid heap allocation in most cases.
194    disabling: SmallVec<[ComponentId; 4]>,
195}
196
197impl FromWorld for DefaultQueryFilters {
198    fn from_world(world: &mut World) -> Self {
199        let mut filters = DefaultQueryFilters::empty();
200        let disabled_component_id = world.register_component::<Disabled>();
201        filters.register_disabling_component(disabled_component_id);
202        let internal_component_id = world.register_component::<Internal>();
203        filters.register_disabling_component(internal_component_id);
204        filters
205    }
206}
207
208impl DefaultQueryFilters {
209    /// Creates a new, completely empty [`DefaultQueryFilters`].
210    ///
211    /// This is provided as an escape hatch; in most cases you should initialize this using [`FromWorld`],
212    /// which is automatically called when creating a new [`World`].
213    #[must_use]
214    pub fn empty() -> Self {
215        DefaultQueryFilters {
216            disabling: SmallVec::new(),
217        }
218    }
219
220    /// Adds this [`ComponentId`] to the set of [`DefaultQueryFilters`],
221    /// causing entities with this component to be excluded from queries.
222    ///
223    /// This method is idempotent, and will not add the same component multiple times.
224    ///
225    /// # Warning
226    ///
227    /// This method should only be called before the app starts, as it will not affect queries
228    /// initialized before it is called.
229    ///
230    /// As discussed in the [module docs](crate::entity_disabling), this can have performance implications,
231    /// as well as create interoperability issues, and should be used with caution.
232    pub fn register_disabling_component(&mut self, component_id: ComponentId) {
233        if !self.disabling.contains(&component_id) {
234            self.disabling.push(component_id);
235        }
236    }
237
238    /// Get an iterator over all of the components which disable entities when present.
239    pub fn disabling_ids(&self) -> impl Iterator<Item = ComponentId> {
240        self.disabling.iter().copied()
241    }
242
243    /// Modifies the provided [`FilteredAccess`] to include the filters from this [`DefaultQueryFilters`].
244    pub(super) fn modify_access(&self, component_access: &mut FilteredAccess) {
245        for component_id in self.disabling_ids() {
246            if !component_access.contains(component_id) {
247                component_access.and_without(component_id);
248            }
249        }
250    }
251
252    pub(super) fn is_dense(&self, components: &Components) -> bool {
253        self.disabling_ids().all(|component_id| {
254            components
255                .get_info(component_id)
256                .is_some_and(|info| info.storage_type() == StorageType::Table)
257        })
258    }
259}
260
261#[cfg(test)]
262mod tests {
263
264    use super::*;
265    use crate::{
266        observer::Observer,
267        prelude::{Add, EntityMut, EntityRef, On, World},
268        query::{Has, With},
269        system::SystemIdMarker,
270    };
271    use alloc::{vec, vec::Vec};
272
273    #[test]
274    fn filters_modify_access() {
275        let mut filters = DefaultQueryFilters::empty();
276        filters.register_disabling_component(ComponentId::new(1));
277
278        // A component access with an unrelated component
279        let mut component_access = FilteredAccess::default();
280        component_access
281            .access_mut()
282            .add_component_read(ComponentId::new(2));
283
284        let mut applied_access = component_access.clone();
285        filters.modify_access(&mut applied_access);
286        assert_eq!(0, applied_access.with_filters().count());
287        assert_eq!(
288            vec![ComponentId::new(1)],
289            applied_access.without_filters().collect::<Vec<_>>()
290        );
291
292        // We add a with filter, now we expect to see both filters
293        component_access.and_with(ComponentId::new(4));
294
295        let mut applied_access = component_access.clone();
296        filters.modify_access(&mut applied_access);
297        assert_eq!(
298            vec![ComponentId::new(4)],
299            applied_access.with_filters().collect::<Vec<_>>()
300        );
301        assert_eq!(
302            vec![ComponentId::new(1)],
303            applied_access.without_filters().collect::<Vec<_>>()
304        );
305
306        let copy = component_access.clone();
307        // We add a rule targeting a default component, that filter should no longer be added
308        component_access.and_with(ComponentId::new(1));
309
310        let mut applied_access = component_access.clone();
311        filters.modify_access(&mut applied_access);
312        assert_eq!(
313            vec![ComponentId::new(1), ComponentId::new(4)],
314            applied_access.with_filters().collect::<Vec<_>>()
315        );
316        assert_eq!(0, applied_access.without_filters().count());
317
318        // Archetypal access should also filter rules
319        component_access = copy.clone();
320        component_access
321            .access_mut()
322            .add_archetypal(ComponentId::new(1));
323
324        let mut applied_access = component_access.clone();
325        filters.modify_access(&mut applied_access);
326        assert_eq!(
327            vec![ComponentId::new(4)],
328            applied_access.with_filters().collect::<Vec<_>>()
329        );
330        assert_eq!(0, applied_access.without_filters().count());
331    }
332
333    #[derive(Component)]
334    struct CustomDisabled;
335
336    #[test]
337    fn multiple_disabling_components() {
338        let mut world = World::new();
339        world.register_disabling_component::<CustomDisabled>();
340
341        // Use powers of two so we can uniquely identify the set of matching archetypes from the count.
342        world.spawn_empty();
343        world.spawn_batch((0..2).map(|_| Disabled));
344        world.spawn_batch((0..4).map(|_| CustomDisabled));
345        world.spawn_batch((0..8).map(|_| (Disabled, CustomDisabled)));
346
347        let mut query = world.query::<()>();
348        assert_eq!(1, query.iter(&world).count());
349
350        let mut query = world.query::<EntityRef>();
351        assert_eq!(1, query.iter(&world).count());
352
353        let mut query = world.query::<EntityMut>();
354        assert_eq!(1, query.iter(&world).count());
355
356        let mut query = world.query_filtered::<(), With<Disabled>>();
357        assert_eq!(2, query.iter(&world).count());
358
359        let mut query = world.query::<Has<Disabled>>();
360        assert_eq!(3, query.iter(&world).count());
361
362        let mut query = world.query_filtered::<(), With<CustomDisabled>>();
363        assert_eq!(4, query.iter(&world).count());
364
365        let mut query = world.query::<Has<CustomDisabled>>();
366        assert_eq!(5, query.iter(&world).count());
367
368        let mut query = world.query_filtered::<(), (With<Disabled>, With<CustomDisabled>)>();
369        assert_eq!(8, query.iter(&world).count());
370
371        let mut query = world.query::<(Has<Disabled>, Has<CustomDisabled>)>();
372        assert_eq!(15, query.iter(&world).count());
373
374        // This seems like it ought to count as a mention of `Disabled`, but it does not.
375        // We don't consider read access, since that would count `EntityRef` as a mention of *all* components.
376        let mut query = world.query::<Option<&Disabled>>();
377        assert_eq!(1, query.iter(&world).count());
378    }
379
380    #[test]
381    fn internal_entities() {
382        let mut world = World::default();
383        world.register_system(|| {});
384        let mut query = world.query::<()>();
385        assert_eq!(query.iter(&world).count(), 0);
386        let mut query = world.query_filtered::<&SystemIdMarker, With<Internal>>();
387        assert_eq!(query.iter(&world).count(), 1);
388
389        #[derive(Component)]
390        struct A;
391        world.add_observer(|_: On<Add, A>| {});
392        let mut query = world.query::<()>();
393        assert_eq!(query.iter(&world).count(), 0);
394        let mut query = world.query_filtered::<&Observer, With<Internal>>();
395        assert_eq!(query.iter(&world).count(), 1);
396    }
397}