Path: blob/main/crates/bevy_ecs/src/entity_disabling.rs
9366 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().count_spawned() 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 entire [`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, ReflectResource},109bevy_reflect::std_traits::ReflectDefault,110bevy_reflect::Reflect,111};112113/// 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_disabling127#[derive(Component, Clone, Debug, Default)]128#[cfg_attr(129feature = "bevy_reflect",130derive(Reflect),131reflect(Component),132reflect(Debug, Clone, Default)133)]134// This component is registered as a disabling component during World::bootstrap135pub struct Disabled;136137/// Default query filters work by excluding entities with certain components from most queries.138///139/// If a query does not explicitly mention a given disabling component, it will not include entities with that component.140/// To be more precise, this checks if the query's [`FilteredAccess`] contains the component,141/// and if it does not, adds a [`Without`](crate::prelude::Without) filter for that component to the query.142///143/// [`Allow`](crate::query::Allow) and [`Has`](crate::prelude::Has) can be used to include entities144/// with and without the disabling component.145/// [`Allow`](crate::query::Allow) is a [`QueryFilter`](crate::query::QueryFilter) and will simply change146/// the list of shown entities, while [`Has`](crate::prelude::Has) is a [`QueryData`](crate::query::QueryData)147/// and will allow you to see if each entity has the disabling component or not.148///149/// This resource is initialized in the [`World`] whenever a new world is created,150/// with the [`Disabled`] component as a disabling component.151///152/// Note that you can remove default query filters by overwriting the [`DefaultQueryFilters`] resource.153/// This can be useful as a last resort escape hatch, but is liable to break compatibility with other libraries.154///155/// See the [module docs](crate::entity_disabling) for more info.156///157///158/// # Warning159///160/// Default query filters are a global setting that affects all queries in the [`World`],161/// and incur a small performance cost for each query.162///163/// They can cause significant interoperability issues within the ecosystem,164/// as users must be aware of each disabling component in use.165///166/// Think carefully about whether you need to use a new disabling component,167/// and clearly communicate their presence in any libraries you publish.168#[derive(Resource, Debug)]169#[cfg_attr(170feature = "bevy_reflect",171derive(bevy_reflect::Reflect),172reflect(Resource)173)]174pub struct DefaultQueryFilters {175// We only expect a few components per application to act as disabling components, so we use a SmallVec here176// to avoid heap allocation in most cases.177disabling: SmallVec<[ComponentId; 4]>,178}179180impl FromWorld for DefaultQueryFilters {181fn from_world(world: &mut World) -> Self {182let mut filters = DefaultQueryFilters::empty();183let disabled_component_id = world.register_component::<Disabled>();184filters.register_disabling_component(disabled_component_id);185filters186}187}188189impl DefaultQueryFilters {190/// Creates a new, completely empty [`DefaultQueryFilters`].191///192/// This is provided as an escape hatch; in most cases you should initialize this using [`FromWorld`],193/// which is automatically called when creating a new [`World`].194#[must_use]195pub fn empty() -> Self {196DefaultQueryFilters {197disabling: SmallVec::new(),198}199}200201/// Adds this [`ComponentId`] to the set of [`DefaultQueryFilters`],202/// causing entities with this component to be excluded from queries.203///204/// This method is idempotent, and will not add the same component multiple times.205///206/// # Warning207///208/// This method should only be called before the app starts, as it will not affect queries209/// initialized before it is called.210///211/// As discussed in the [module docs](crate::entity_disabling), this can have performance implications,212/// as well as create interoperability issues, and should be used with caution.213pub fn register_disabling_component(&mut self, component_id: ComponentId) {214if !self.disabling.contains(&component_id) {215self.disabling.push(component_id);216}217}218219/// Get an iterator over all of the components which disable entities when present.220pub fn disabling_ids(&self) -> impl Iterator<Item = ComponentId> {221self.disabling.iter().copied()222}223224/// Modifies the provided [`FilteredAccess`] to include the filters from this [`DefaultQueryFilters`].225pub(super) fn modify_access(&self, component_access: &mut FilteredAccess) {226for component_id in self.disabling_ids() {227if !component_access.contains(component_id) {228component_access.and_without(component_id);229}230}231}232233pub(super) fn is_dense(&self, components: &Components) -> bool {234self.disabling_ids().all(|component_id| {235components236.get_info(component_id)237.is_some_and(|info| info.storage_type() == StorageType::Table)238})239}240}241242#[cfg(test)]243mod tests {244245use super::*;246use crate::{247prelude::{EntityMut, EntityRef, World},248query::{Has, With},249};250use alloc::{vec, vec::Vec};251252#[test]253fn filters_modify_access() {254let mut filters = DefaultQueryFilters::empty();255filters.register_disabling_component(ComponentId::new(1));256257// A component access with an unrelated component258let mut component_access = FilteredAccess::default();259component_access260.access_mut()261.add_component_read(ComponentId::new(2));262263let mut applied_access = component_access.clone();264filters.modify_access(&mut applied_access);265assert_eq!(0, applied_access.with_filters().count());266assert_eq!(267vec![ComponentId::new(1)],268applied_access.without_filters().collect::<Vec<_>>()269);270271// We add a with filter, now we expect to see both filters272component_access.and_with(ComponentId::new(4));273274let mut applied_access = component_access.clone();275filters.modify_access(&mut applied_access);276assert_eq!(277vec![ComponentId::new(4)],278applied_access.with_filters().collect::<Vec<_>>()279);280assert_eq!(281vec![ComponentId::new(1)],282applied_access.without_filters().collect::<Vec<_>>()283);284285let copy = component_access.clone();286// We add a rule targeting a default component, that filter should no longer be added287component_access.and_with(ComponentId::new(1));288289let mut applied_access = component_access.clone();290filters.modify_access(&mut applied_access);291assert_eq!(292vec![ComponentId::new(1), ComponentId::new(4)],293applied_access.with_filters().collect::<Vec<_>>()294);295assert_eq!(0, applied_access.without_filters().count());296297// Archetypal access should also filter rules298component_access = copy.clone();299component_access300.access_mut()301.add_archetypal(ComponentId::new(1));302303let mut applied_access = component_access.clone();304filters.modify_access(&mut applied_access);305assert_eq!(306vec![ComponentId::new(4)],307applied_access.with_filters().collect::<Vec<_>>()308);309assert_eq!(0, applied_access.without_filters().count());310}311312#[derive(Component)]313struct CustomDisabled;314315#[derive(Component)]316struct Dummy;317318#[test]319fn multiple_disabling_components() {320let mut world = World::new();321world.register_disabling_component::<CustomDisabled>();322323// Use powers of two so we can uniquely identify the set of matching archetypes from the count.324world.spawn(Dummy);325world.spawn_batch((0..2).map(|_| (Dummy, Disabled)));326world.spawn_batch((0..4).map(|_| (Dummy, CustomDisabled)));327world.spawn_batch((0..8).map(|_| (Dummy, Disabled, CustomDisabled)));328329let mut query = world.query::<&Dummy>();330assert_eq!(1, query.iter(&world).count());331332let mut query = world.query_filtered::<EntityRef, With<Dummy>>();333assert_eq!(1, query.iter(&world).count());334335let mut query = world.query_filtered::<EntityMut, With<Dummy>>();336assert_eq!(1, query.iter(&world).count());337338let mut query = world.query_filtered::<&Dummy, With<Disabled>>();339assert_eq!(2, query.iter(&world).count());340341let mut query = world.query_filtered::<Has<Disabled>, With<Dummy>>();342assert_eq!(3, query.iter(&world).count());343344let mut query = world.query_filtered::<&Dummy, With<CustomDisabled>>();345assert_eq!(4, query.iter(&world).count());346347let mut query = world.query_filtered::<Has<CustomDisabled>, With<Dummy>>();348assert_eq!(5, query.iter(&world).count());349350let mut query = world.query_filtered::<&Dummy, (With<Disabled>, With<CustomDisabled>)>();351assert_eq!(8, query.iter(&world).count());352353let mut query = world.query_filtered::<(Has<Disabled>, Has<CustomDisabled>), With<Dummy>>();354assert_eq!(15, query.iter(&world).count());355356// This seems like it ought to count as a mention of `Disabled`, but it does not.357// We don't consider read access, since that would count `EntityRef` as a mention of *all* components.358let mut query = world.query_filtered::<Option<&Disabled>, With<Dummy>>();359assert_eq!(1, query.iter(&world).count());360}361}362363364