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".
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//! Entities with disabling components are still present in the [`World`] and can be accessed directly,
48//! using methods on [`World`] or [`Commands`](crate::prelude::Commands).
49//!
50//! ### Warnings
51//!
52//! Currently, only queries for which the cache is built after enabling a default query filter will have entities
53//! with those components filtered. As a result, they should generally only be modified before the
54//! app starts.
55//!
56//! Because filters are applied to all queries they can have performance implication for
57//! the enire [`World`], especially when they cause queries to mix sparse and table components.
58//! See [`Query` performance] for more info.
59//!
60//! Custom disabling components can cause significant interoperability issues within the ecosystem,
61//! as users must be aware of each disabling component in use.
62//! Libraries should think carefully about whether they need to use a new disabling component,
63//! and clearly communicate their presence to their users to avoid the new for library compatibility flags.
64//!
65//! [`With`]: crate::prelude::With
66//! [`Has`]: crate::prelude::Has
67//! [`World`]: crate::prelude::World
68//! [`Query` performance]: crate::prelude::Query#performance
69
70use crate::{
71    component::{ComponentId, Components, StorageType},
72    query::FilteredAccess,
73    world::{FromWorld, World},
74};
75use bevy_ecs_macros::{Component, Resource};
76use smallvec::SmallVec;
77
78#[cfg(feature = "bevy_reflect")]
79use {
80    crate::reflect::ReflectComponent, bevy_reflect::std_traits::ReflectDefault,
81    bevy_reflect::Reflect,
82};
83
84/// A marker component for disabled entities.
85///
86/// Semantically, this component is used to mark entities that are temporarily disabled (typically for gameplay reasons),
87/// but will likely be re-enabled at some point.
88///
89/// Like all disabling components, this only disables the entity itself,
90/// not its children or other entities that reference it.
91/// To disable an entire tree of entities, use [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive).
92///
93/// Every [`World`] has a default query filter that excludes entities with this component,
94/// registered in the [`DefaultQueryFilters`] resource.
95/// See [the module docs] for more info.
96///
97/// [the module docs]: crate::entity_disabling
98#[derive(Component, Clone, Debug, Default)]
99#[cfg_attr(
100    feature = "bevy_reflect",
101    derive(Reflect),
102    reflect(Component),
103    reflect(Debug, Clone, Default)
104)]
105// This component is registered as a disabling component during World::bootstrap
106pub struct Disabled;
107
108/// Default query filters work by excluding entities with certain components from most queries.
109///
110/// If a query does not explicitly mention a given disabling component, it will not include entities with that component.
111/// To be more precise, this checks if the query's [`FilteredAccess`] contains the component,
112/// and if it does not, adds a [`Without`](crate::prelude::Without) filter for that component to the query.
113///
114/// This resource is initialized in the [`World`] whenever a new world is created,
115/// with the [`Disabled`] component as a disabling component.
116///
117/// Note that you can remove default query filters by overwriting the [`DefaultQueryFilters`] resource.
118/// This can be useful as a last resort escape hatch, but is liable to break compatibility with other libraries.
119///
120/// See the [module docs](crate::entity_disabling) for more info.
121///
122///
123/// # Warning
124///
125/// Default query filters are a global setting that affects all queries in the [`World`],
126/// and incur a small performance cost for each query.
127///
128/// They can cause significant interoperability issues within the ecosystem,
129/// as users must be aware of each disabling component in use.
130///
131/// Think carefully about whether you need to use a new disabling component,
132/// and clearly communicate their presence in any libraries you publish.
133#[derive(Resource, Debug)]
134#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
135pub struct DefaultQueryFilters {
136    // We only expect a few components per application to act as disabling components, so we use a SmallVec here
137    // to avoid heap allocation in most cases.
138    disabling: SmallVec<[ComponentId; 4]>,
139}
140
141impl FromWorld for DefaultQueryFilters {
142    fn from_world(world: &mut World) -> Self {
143        let mut filters = DefaultQueryFilters::empty();
144        let disabled_component_id = world.register_component::<Disabled>();
145        filters.register_disabling_component(disabled_component_id);
146        filters
147    }
148}
149
150impl DefaultQueryFilters {
151    /// Creates a new, completely empty [`DefaultQueryFilters`].
152    ///
153    /// This is provided as an escape hatch; in most cases you should initialize this using [`FromWorld`],
154    /// which is automatically called when creating a new [`World`].
155    #[must_use]
156    pub fn empty() -> Self {
157        DefaultQueryFilters {
158            disabling: SmallVec::new(),
159        }
160    }
161
162    /// Adds this [`ComponentId`] to the set of [`DefaultQueryFilters`],
163    /// causing entities with this component to be excluded from queries.
164    ///
165    /// This method is idempotent, and will not add the same component multiple times.
166    ///
167    /// # Warning
168    ///
169    /// This method should only be called before the app starts, as it will not affect queries
170    /// initialized before it is called.
171    ///
172    /// As discussed in the [module docs](crate::entity_disabling), this can have performance implications,
173    /// as well as create interoperability issues, and should be used with caution.
174    pub fn register_disabling_component(&mut self, component_id: ComponentId) {
175        if !self.disabling.contains(&component_id) {
176            self.disabling.push(component_id);
177        }
178    }
179
180    /// Get an iterator over all of the components which disable entities when present.
181    pub fn disabling_ids(&self) -> impl Iterator<Item = ComponentId> + use<'_> {
182        self.disabling.iter().copied()
183    }
184
185    /// Modifies the provided [`FilteredAccess`] to include the filters from this [`DefaultQueryFilters`].
186    pub(super) fn modify_access(&self, component_access: &mut FilteredAccess<ComponentId>) {
187        for component_id in self.disabling_ids() {
188            if !component_access.contains(component_id) {
189                component_access.and_without(component_id);
190            }
191        }
192    }
193
194    pub(super) fn is_dense(&self, components: &Components) -> bool {
195        self.disabling_ids().all(|component_id| {
196            components
197                .get_info(component_id)
198                .is_some_and(|info| info.storage_type() == StorageType::Table)
199        })
200    }
201}
202
203#[cfg(test)]
204mod tests {
205
206    use super::*;
207    use crate::{
208        prelude::World,
209        query::{Has, With},
210    };
211    use alloc::{vec, vec::Vec};
212
213    #[test]
214    fn filters_modify_access() {
215        let mut filters = DefaultQueryFilters::empty();
216        filters.register_disabling_component(ComponentId::new(1));
217
218        // A component access with an unrelated component
219        let mut component_access = FilteredAccess::<ComponentId>::default();
220        component_access
221            .access_mut()
222            .add_component_read(ComponentId::new(2));
223
224        let mut applied_access = component_access.clone();
225        filters.modify_access(&mut applied_access);
226        assert_eq!(0, applied_access.with_filters().count());
227        assert_eq!(
228            vec![ComponentId::new(1)],
229            applied_access.without_filters().collect::<Vec<_>>()
230        );
231
232        // We add a with filter, now we expect to see both filters
233        component_access.and_with(ComponentId::new(4));
234
235        let mut applied_access = component_access.clone();
236        filters.modify_access(&mut applied_access);
237        assert_eq!(
238            vec![ComponentId::new(4)],
239            applied_access.with_filters().collect::<Vec<_>>()
240        );
241        assert_eq!(
242            vec![ComponentId::new(1)],
243            applied_access.without_filters().collect::<Vec<_>>()
244        );
245
246        let copy = component_access.clone();
247        // We add a rule targeting a default component, that filter should no longer be added
248        component_access.and_with(ComponentId::new(1));
249
250        let mut applied_access = component_access.clone();
251        filters.modify_access(&mut applied_access);
252        assert_eq!(
253            vec![ComponentId::new(1), ComponentId::new(4)],
254            applied_access.with_filters().collect::<Vec<_>>()
255        );
256        assert_eq!(0, applied_access.without_filters().count());
257
258        // Archetypal access should also filter rules
259        component_access = copy.clone();
260        component_access
261            .access_mut()
262            .add_archetypal(ComponentId::new(1));
263
264        let mut applied_access = component_access.clone();
265        filters.modify_access(&mut applied_access);
266        assert_eq!(
267            vec![ComponentId::new(4)],
268            applied_access.with_filters().collect::<Vec<_>>()
269        );
270        assert_eq!(0, applied_access.without_filters().count());
271    }
272
273    #[derive(Component)]
274    struct CustomDisabled;
275
276    #[test]
277    fn multiple_disabling_components() {
278        let mut world = World::new();
279        world.register_disabling_component::<CustomDisabled>();
280
281        world.spawn_empty();
282        world.spawn(Disabled);
283        world.spawn(CustomDisabled);
284        world.spawn((Disabled, CustomDisabled));
285
286        let mut query = world.query::<()>();
287        assert_eq!(1, query.iter(&world).count());
288
289        let mut query = world.query_filtered::<(), With<Disabled>>();
290        assert_eq!(1, query.iter(&world).count());
291
292        let mut query = world.query::<Has<Disabled>>();
293        assert_eq!(2, query.iter(&world).count());
294
295        let mut query = world.query_filtered::<(), With<CustomDisabled>>();
296        assert_eq!(1, query.iter(&world).count());
297
298        let mut query = world.query::<Has<CustomDisabled>>();
299        assert_eq!(2, query.iter(&world).count());
300
301        let mut query = world.query_filtered::<(), (With<Disabled>, With<CustomDisabled>)>();
302        assert_eq!(1, query.iter(&world).count());
303
304        let mut query = world.query::<(Has<Disabled>, Has<CustomDisabled>)>();
305        assert_eq!(4, query.iter(&world).count());
306    }
307}