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}