Path: blob/main/crates/bevy_ecs/src/entity_disabling.rs
6598 views
//! Disabled entities do not show up in queries unless the query explicitly mentions them.1//!2//! Entities which are disabled in this way are not removed from the [`World`],3//! and their relationships remain intact.4//! In many cases, you may want to disable entire trees of entities at once,5//! using [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive).6//!7//! While Bevy ships with a built-in [`Disabled`] component, you can also create your own8//! disabling components, which will operate in the same way but can have distinct semantics.9//!10//! ```11//! use bevy_ecs::prelude::*;12//!13//! // Our custom disabling component!14//! #[derive(Component, Clone)]15//! struct Prefab;16//!17//! #[derive(Component)]18//! struct A;19//!20//! let mut world = World::new();21//! world.register_disabling_component::<Prefab>();22//! world.spawn((A, Prefab));23//! world.spawn((A,));24//! world.spawn((A,));25//!26//! let mut normal_query = world.query::<&A>();27//! assert_eq!(2, normal_query.iter(&world).count());28//!29//! let mut prefab_query = world.query_filtered::<&A, With<Prefab>>();30//! assert_eq!(1, prefab_query.iter(&world).count());31//!32//! let mut maybe_prefab_query = world.query::<(&A, Has<Prefab>)>();33//! assert_eq!(3, maybe_prefab_query.iter(&world).count());34//! ```35//!36//! ## Default query filters37//!38//! In Bevy, entity disabling is implemented through the construction of a global "default query filter" resource.39//! Queries which do not explicitly mention the disabled component will not include entities with that component.40//! If an entity has multiple disabling components, it will only be included in queries that mention all of them.41//!42//! For example, `Query<&Position>` will not include entities with the [`Disabled`] component,43//! even if they have a `Position` component,44//! but `Query<&Position, With<Disabled>>` or `Query<(&Position, Has<Disabled>)>` will see them.45//!46//! The [`Allow`](crate::query::Allow) query filter is designed to be used with default query filters,47//! and ensures that the query will include entities both with and without the specified disabling component.48//!49//! Entities with disabling components are still present in the [`World`] and can be accessed directly,50//! using methods on [`World`] or [`Commands`](crate::prelude::Commands).51//!52//! As default query filters are implemented through a resource,53//! it's possible to temporarily ignore any default filters by using [`World::resource_scope`](crate::prelude::World).54//!55//! ```56//! use bevy_ecs::prelude::*;57//! use bevy_ecs::entity_disabling::{DefaultQueryFilters, Disabled};58//!59//! let mut world = World::default();60//!61//! #[derive(Component)]62//! struct CustomDisabled;63//!64//! world.register_disabling_component::<CustomDisabled>();65//!66//! world.spawn(Disabled);67//! world.spawn(CustomDisabled);68//!69//! // resource_scope removes DefaultQueryFilters temporarily before re-inserting into the world.70//! world.resource_scope(|world: &mut World, _: Mut<DefaultQueryFilters>| {71//! // within this scope, we can query like no components are disabled.72//! assert_eq!(world.query::<&Disabled>().query(&world).count(), 1);73//! assert_eq!(world.query::<&CustomDisabled>().query(&world).count(), 1);74//! assert_eq!(world.query::<()>().query(&world).count(), world.entities().len() as usize);75//! })76//! ```77//!78//! ### Warnings79//!80//! Currently, only queries for which the cache is built after enabling a default query filter will have entities81//! with those components filtered. As a result, they should generally only be modified before the82//! app starts.83//!84//! Because filters are applied to all queries they can have performance implication for85//! the enire [`World`], especially when they cause queries to mix sparse and table components.86//! See [`Query` performance] for more info.87//!88//! Custom disabling components can cause significant interoperability issues within the ecosystem,89//! as users must be aware of each disabling component in use.90//! Libraries should think carefully about whether they need to use a new disabling component,91//! and clearly communicate their presence to their users to avoid the new for library compatibility flags.92//!93//! [`With`]: crate::prelude::With94//! [`Has`]: crate::prelude::Has95//! [`World`]: crate::prelude::World96//! [`Query` performance]: crate::prelude::Query#performance9798use crate::{99component::{ComponentId, Components, StorageType},100query::FilteredAccess,101world::{FromWorld, World},102};103use bevy_ecs_macros::{Component, Resource};104use smallvec::SmallVec;105106#[cfg(feature = "bevy_reflect")]107use {108crate::reflect::ReflectComponent, bevy_reflect::std_traits::ReflectDefault,109bevy_reflect::Reflect,110};111112/// A marker component for disabled entities.113///114/// Semantically, this component is used to mark entities that are temporarily disabled (typically for gameplay reasons),115/// but will likely be re-enabled at some point.116///117/// Like all disabling components, this only disables the entity itself,118/// not its children or other entities that reference it.119/// To disable an entire tree of entities, use [`EntityCommands::insert_recursive`](crate::prelude::EntityCommands::insert_recursive).120///121/// Every [`World`] has a default query filter that excludes entities with this component,122/// registered in the [`DefaultQueryFilters`] resource.123/// See [the module docs] for more info.124///125/// [the module docs]: crate::entity_disabling126#[derive(Component, Clone, Debug, Default)]127#[cfg_attr(128feature = "bevy_reflect",129derive(Reflect),130reflect(Component),131reflect(Debug, Clone, Default)132)]133// This component is registered as a disabling component during World::bootstrap134pub struct Disabled;135136/// A marker component for internal entities.137///138/// This component is used to mark entities as being internal to the engine.139/// These entities should be hidden from the developer's view by default,140/// as they are both noisy and expose confusing implementation details.141/// Internal entities are hidden from queries using [`DefaultQueryFilters`].142/// For more information, see [the module docs].143/// We strongly advise against altering, removing or relying on entities tagged with this component in any way.144/// These are "internal implementation details", and may not be robust to these changes or stable across minor Bevy versions.145///146/// [the module docs]: crate::entity_disabling147#[derive(Component, Clone, Debug, Default)]148#[cfg_attr(149feature = "bevy_reflect",150derive(Reflect),151reflect(Component),152reflect(Debug, Clone, Default)153)]154// This component is registered as a disabling component during World::bootstrap155pub struct Internal;156157/// Default query filters work by excluding entities with certain components from most queries.158///159/// If a query does not explicitly mention a given disabling component, it will not include entities with that component.160/// To be more precise, this checks if the query's [`FilteredAccess`] contains the component,161/// and if it does not, adds a [`Without`](crate::prelude::Without) filter for that component to the query.162///163/// [`Allow`](crate::query::Allow) and [`Has`](crate::prelude::Has) can be used to include entities164/// with and without the disabling component.165/// [`Allow`](crate::query::Allow) is a [`QueryFilter`](crate::query::QueryFilter) and will simply change166/// the list of shown entities, while [`Has`](crate::prelude::Has) is a [`QueryData`](crate::query::QueryData)167/// and will allow you to see if each entity has the disabling component or not.168///169/// This resource is initialized in the [`World`] whenever a new world is created,170/// with the [`Disabled`] component as a disabling component.171///172/// Note that you can remove default query filters by overwriting the [`DefaultQueryFilters`] resource.173/// This can be useful as a last resort escape hatch, but is liable to break compatibility with other libraries.174///175/// See the [module docs](crate::entity_disabling) for more info.176///177///178/// # Warning179///180/// Default query filters are a global setting that affects all queries in the [`World`],181/// and incur a small performance cost for each query.182///183/// They can cause significant interoperability issues within the ecosystem,184/// as users must be aware of each disabling component in use.185///186/// Think carefully about whether you need to use a new disabling component,187/// and clearly communicate their presence in any libraries you publish.188#[derive(Resource, Debug)]189#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]190pub struct DefaultQueryFilters {191// We only expect a few components per application to act as disabling components, so we use a SmallVec here192// to avoid heap allocation in most cases.193disabling: SmallVec<[ComponentId; 4]>,194}195196impl FromWorld for DefaultQueryFilters {197fn from_world(world: &mut World) -> Self {198let mut filters = DefaultQueryFilters::empty();199let disabled_component_id = world.register_component::<Disabled>();200filters.register_disabling_component(disabled_component_id);201let internal_component_id = world.register_component::<Internal>();202filters.register_disabling_component(internal_component_id);203filters204}205}206207impl DefaultQueryFilters {208/// Creates a new, completely empty [`DefaultQueryFilters`].209///210/// This is provided as an escape hatch; in most cases you should initialize this using [`FromWorld`],211/// which is automatically called when creating a new [`World`].212#[must_use]213pub fn empty() -> Self {214DefaultQueryFilters {215disabling: SmallVec::new(),216}217}218219/// Adds this [`ComponentId`] to the set of [`DefaultQueryFilters`],220/// causing entities with this component to be excluded from queries.221///222/// This method is idempotent, and will not add the same component multiple times.223///224/// # Warning225///226/// This method should only be called before the app starts, as it will not affect queries227/// initialized before it is called.228///229/// As discussed in the [module docs](crate::entity_disabling), this can have performance implications,230/// as well as create interoperability issues, and should be used with caution.231pub fn register_disabling_component(&mut self, component_id: ComponentId) {232if !self.disabling.contains(&component_id) {233self.disabling.push(component_id);234}235}236237/// Get an iterator over all of the components which disable entities when present.238pub fn disabling_ids(&self) -> impl Iterator<Item = ComponentId> {239self.disabling.iter().copied()240}241242/// Modifies the provided [`FilteredAccess`] to include the filters from this [`DefaultQueryFilters`].243pub(super) fn modify_access(&self, component_access: &mut FilteredAccess) {244for component_id in self.disabling_ids() {245if !component_access.contains(component_id) {246component_access.and_without(component_id);247}248}249}250251pub(super) fn is_dense(&self, components: &Components) -> bool {252self.disabling_ids().all(|component_id| {253components254.get_info(component_id)255.is_some_and(|info| info.storage_type() == StorageType::Table)256})257}258}259260#[cfg(test)]261mod tests {262263use super::*;264use crate::{265observer::Observer,266prelude::{Add, EntityMut, EntityRef, On, World},267query::{Has, With},268system::SystemIdMarker,269};270use alloc::{vec, vec::Vec};271272#[test]273fn filters_modify_access() {274let mut filters = DefaultQueryFilters::empty();275filters.register_disabling_component(ComponentId::new(1));276277// A component access with an unrelated component278let mut component_access = FilteredAccess::default();279component_access280.access_mut()281.add_component_read(ComponentId::new(2));282283let mut applied_access = component_access.clone();284filters.modify_access(&mut applied_access);285assert_eq!(0, applied_access.with_filters().count());286assert_eq!(287vec![ComponentId::new(1)],288applied_access.without_filters().collect::<Vec<_>>()289);290291// We add a with filter, now we expect to see both filters292component_access.and_with(ComponentId::new(4));293294let mut applied_access = component_access.clone();295filters.modify_access(&mut applied_access);296assert_eq!(297vec![ComponentId::new(4)],298applied_access.with_filters().collect::<Vec<_>>()299);300assert_eq!(301vec![ComponentId::new(1)],302applied_access.without_filters().collect::<Vec<_>>()303);304305let copy = component_access.clone();306// We add a rule targeting a default component, that filter should no longer be added307component_access.and_with(ComponentId::new(1));308309let mut applied_access = component_access.clone();310filters.modify_access(&mut applied_access);311assert_eq!(312vec![ComponentId::new(1), ComponentId::new(4)],313applied_access.with_filters().collect::<Vec<_>>()314);315assert_eq!(0, applied_access.without_filters().count());316317// Archetypal access should also filter rules318component_access = copy.clone();319component_access320.access_mut()321.add_archetypal(ComponentId::new(1));322323let mut applied_access = component_access.clone();324filters.modify_access(&mut applied_access);325assert_eq!(326vec![ComponentId::new(4)],327applied_access.with_filters().collect::<Vec<_>>()328);329assert_eq!(0, applied_access.without_filters().count());330}331332#[derive(Component)]333struct CustomDisabled;334335#[test]336fn multiple_disabling_components() {337let mut world = World::new();338world.register_disabling_component::<CustomDisabled>();339340// Use powers of two so we can uniquely identify the set of matching archetypes from the count.341world.spawn_empty();342world.spawn_batch((0..2).map(|_| Disabled));343world.spawn_batch((0..4).map(|_| CustomDisabled));344world.spawn_batch((0..8).map(|_| (Disabled, CustomDisabled)));345346let mut query = world.query::<()>();347assert_eq!(1, query.iter(&world).count());348349let mut query = world.query::<EntityRef>();350assert_eq!(1, query.iter(&world).count());351352let mut query = world.query::<EntityMut>();353assert_eq!(1, query.iter(&world).count());354355let mut query = world.query_filtered::<(), With<Disabled>>();356assert_eq!(2, query.iter(&world).count());357358let mut query = world.query::<Has<Disabled>>();359assert_eq!(3, query.iter(&world).count());360361let mut query = world.query_filtered::<(), With<CustomDisabled>>();362assert_eq!(4, query.iter(&world).count());363364let mut query = world.query::<Has<CustomDisabled>>();365assert_eq!(5, query.iter(&world).count());366367let mut query = world.query_filtered::<(), (With<Disabled>, With<CustomDisabled>)>();368assert_eq!(8, query.iter(&world).count());369370let mut query = world.query::<(Has<Disabled>, Has<CustomDisabled>)>();371assert_eq!(15, query.iter(&world).count());372373// This seems like it ought to count as a mention of `Disabled`, but it does not.374// We don't consider read access, since that would count `EntityRef` as a mention of *all* components.375let mut query = world.query::<Option<&Disabled>>();376assert_eq!(1, query.iter(&world).count());377}378379#[test]380fn internal_entities() {381let mut world = World::default();382world.register_system(|| {});383let mut query = world.query::<()>();384assert_eq!(query.iter(&world).count(), 0);385let mut query = world.query_filtered::<&SystemIdMarker, With<Internal>>();386assert_eq!(query.iter(&world).count(), 1);387388#[derive(Component)]389struct A;390world.add_observer(|_: On<Add, A>| {});391let mut query = world.query::<()>();392assert_eq!(query.iter(&world).count(), 0);393let mut query = world.query_filtered::<&Observer, With<Internal>>();394assert_eq!(query.iter(&world).count(), 1);395}396}397398399