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