Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/entity_disabling.rs
6598 views
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
99
use crate::{
100
component::{ComponentId, Components, StorageType},
101
query::FilteredAccess,
102
world::{FromWorld, World},
103
};
104
use bevy_ecs_macros::{Component, Resource};
105
use smallvec::SmallVec;
106
107
#[cfg(feature = "bevy_reflect")]
108
use {
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
135
pub 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
156
pub 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))]
191
pub 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
197
impl 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
208
impl 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)]
262
mod 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
}
398
399