1- //! Types for entity disabling.
2- //!
31//! Disabled entities do not show up in queries unless the query explicitly mentions them.
42//!
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).
849//!
9- //! ### Note
50+ //! ### Warnings
1051//!
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
1253//! with those components filtered. As a result, they should generally only be modified before the
1354//! app starts.
1455//!
1556//! Because filters are applied to all queries they can have performance implication for
1657//! the enire [`World`], especially when they cause queries to mix sparse and table components.
1758//! See [`Query` performance] for more info.
1859//!
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+ //!
1965//! [`With`]: crate::prelude::With
2066//! [`Has`]: crate::prelude::Has
2167//! [`World`]: crate::prelude::World
2470use crate :: {
2571 component:: { ComponentId , Components , StorageType } ,
2672 query:: FilteredAccess ,
73+ world:: { FromWorld , World } ,
2774} ;
2875use bevy_ecs_macros:: { Component , Resource } ;
76+ use smallvec:: SmallVec ;
2977
3078#[ cfg( feature = "bevy_reflect" ) ]
3179use { crate :: reflect:: ReflectComponent , bevy_reflect:: Reflect } ;
3280
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.
3493///
3594/// [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
38103pub struct Disabled ;
39104
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+ ///
41117/// 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 ) ]
43131#[ cfg_attr( feature = "bevy_reflect" , derive( bevy_reflect:: Reflect ) ) ]
44132pub 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+ }
46145}
47146
48147impl 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) ;
53174 }
54- self . disabled = Some ( component_id) ;
55- Some ( ( ) )
56175 }
57176
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 ( )
61180 }
62181
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 ( ) {
65185 if !component_access. contains ( component_id) {
66186 component_access. and_without ( component_id) ;
67187 }
68188 }
69189 }
70190
71191 pub ( super ) fn is_dense ( & self , components : & Components ) -> bool {
72- self . ids ( ) . all ( |component_id| {
192+ self . disabling_ids ( ) . all ( |component_id| {
73193 components
74194 . get_info ( component_id)
75195 . is_some_and ( |info| info. storage_type ( ) == StorageType :: Table )
@@ -81,24 +201,16 @@ impl DefaultQueryFilters {
81201mod tests {
82202
83203 use super :: * ;
204+ use crate :: {
205+ prelude:: World ,
206+ query:: { Has , With } ,
207+ } ;
84208 use alloc:: { vec, vec:: Vec } ;
85209
86210 #[ 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 ) ) ;
102214
103215 // A component access with an unrelated component
104216 let mut component_access = FilteredAccess :: < ComponentId > :: default ( ) ;
@@ -107,7 +219,7 @@ mod tests {
107219 . add_component_read ( ComponentId :: new ( 2 ) ) ;
108220
109221 let mut applied_access = component_access. clone ( ) ;
110- filters. apply ( & mut applied_access) ;
222+ filters. modify_access ( & mut applied_access) ;
111223 assert_eq ! ( 0 , applied_access. with_filters( ) . count( ) ) ;
112224 assert_eq ! (
113225 vec![ ComponentId :: new( 1 ) ] ,
@@ -118,7 +230,7 @@ mod tests {
118230 component_access. and_with ( ComponentId :: new ( 4 ) ) ;
119231
120232 let mut applied_access = component_access. clone ( ) ;
121- filters. apply ( & mut applied_access) ;
233+ filters. modify_access ( & mut applied_access) ;
122234 assert_eq ! (
123235 vec![ ComponentId :: new( 4 ) ] ,
124236 applied_access. with_filters( ) . collect:: <Vec <_>>( )
@@ -133,7 +245,7 @@ mod tests {
133245 component_access. and_with ( ComponentId :: new ( 1 ) ) ;
134246
135247 let mut applied_access = component_access. clone ( ) ;
136- filters. apply ( & mut applied_access) ;
248+ filters. modify_access ( & mut applied_access) ;
137249 assert_eq ! (
138250 vec![ ComponentId :: new( 1 ) , ComponentId :: new( 4 ) ] ,
139251 applied_access. with_filters( ) . collect:: <Vec <_>>( )
@@ -147,11 +259,46 @@ mod tests {
147259 . add_archetypal ( ComponentId :: new ( 1 ) ) ;
148260
149261 let mut applied_access = component_access. clone ( ) ;
150- filters. apply ( & mut applied_access) ;
262+ filters. modify_access ( & mut applied_access) ;
151263 assert_eq ! (
152264 vec![ ComponentId :: new( 4 ) ] ,
153265 applied_access. with_filters( ) . collect:: <Vec <_>>( )
154266 ) ;
155267 assert_eq ! ( 0 , applied_access. without_filters( ) . count( ) ) ;
156268 }
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+ }
157304}
0 commit comments