diff --git a/crates/bevy_core/src/lib.rs b/crates/bevy_core/src/lib.rs index 073460b3b3796..1792115708d8d 100644 --- a/crates/bevy_core/src/lib.rs +++ b/crates/bevy_core/src/lib.rs @@ -37,7 +37,7 @@ pub struct TypeRegistrationPlugin; impl Plugin for TypeRegistrationPlugin { fn build(&self, app: &mut App) { - app.register_type::(); + app.register_type::().register_type::(); } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 7eb5cf1a3bd1b..2f6dc86e2c9fb 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -45,7 +45,9 @@ pub mod prelude { component::Component, entity::{Entity, EntityMapper}, event::{Event, EventReader, EventWriter, Events}, - query::{Added, AnyOf, Changed, Has, Or, QueryBuilder, QueryState, With, Without}, + query::{ + Added, AnyOf, Changed, Disabled, Has, Or, QueryBuilder, QueryState, With, Without, + }, removal_detection::RemovedComponents, schedule::{ apply_deferred, apply_state_transition, common_conditions::*, Condition, @@ -69,7 +71,7 @@ mod tests { change_detection::Ref, component::{Component, ComponentId}, entity::Entity, - query::{Added, Changed, FilteredAccess, QueryFilter, With, Without}, + query::{Added, Changed, Disabled, FilteredAccess, QueryFilter, With, Without}, system::Resource, world::{EntityRef, Mut, World}, }; @@ -1379,6 +1381,7 @@ mod tests { #[test] fn filtered_query_access() { let mut world = World::new(); + let disabled_id = world.init_component::(); let query = world.query_filtered::<&mut A, Changed>(); let mut expected = FilteredAccess::::default(); @@ -1386,6 +1389,7 @@ mod tests { let b_id = world.components.get_id(TypeId::of::()).unwrap(); expected.add_write(a_id); expected.add_read(b_id); + expected.and_without(disabled_id); assert!( query.component_access.eq(&expected), "ComponentId access from query fetch and query filter should be combined" diff --git a/crates/bevy_ecs/src/query/error.rs b/crates/bevy_ecs/src/query/error.rs index 1f1f33f2c4d56..d0099a1dc89c4 100644 --- a/crates/bevy_ecs/src/query/error.rs +++ b/crates/bevy_ecs/src/query/error.rs @@ -23,7 +23,7 @@ pub enum QueryEntityError { /// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState) as a single expected result via /// [`get_single`](crate::system::Query::get_single) or [`get_single_mut`](crate::system::Query::get_single_mut). -#[derive(Debug, Error)] +#[derive(Debug, PartialEq, Eq, Error)] pub enum QuerySingleError { /// No entity fits the query. #[error("No entities fit the query {0}")] diff --git a/crates/bevy_ecs/src/query/mod.rs b/crates/bevy_ecs/src/query/mod.rs index 4cc2b9f3458a8..4049fbcb86da9 100644 --- a/crates/bevy_ecs/src/query/mod.rs +++ b/crates/bevy_ecs/src/query/mod.rs @@ -21,6 +21,52 @@ pub use par_iter::*; pub use state::*; pub use world_query::*; +use crate as bevy_ecs; + +/// A special marker component to disable an entity. +/// +/// Disabled entities do not show up in most queries, unless the query mentions Disabled. +/// +/// ## Example +/// +/// ```rust +/// # use bevy_ecs::prelude::*; +/// # let mut world = World::new(); +/// # +/// // This entity is enabled, since all entities are enabled by default +/// let entity_a = world.spawn_empty().id(); +/// +/// // We disable this entity using a helper method +/// let entity_b = world.spawn_empty().id(); +/// world.disable(entity_b); +/// +/// // It can also be inserted like other component +/// let entity_c = world.spawn(Disabled).id(); +/// +/// // This query does not mention Disabled, so disabled entities are hidden +/// let mut query = world.query::(); +/// assert_eq!(1, query.iter(&world).count()); +/// assert_eq!(Ok(entity_a), query.get_single(&world)); +/// +/// // If our query mentions Disabled, we can find disabled entities like normal +/// // Here we query for only the disabled entities +/// let mut query = world.query_filtered::<(), With>(); +/// assert!(query.get(&world, entity_a).is_err()); +/// assert!(query.get_many(&world, [entity_b, entity_c]).is_ok()); +/// +/// // It also works as part of the query data +/// let mut query = world.query::>(); +/// assert_eq!(Ok([false, true, true]), query.get_many(&world, [entity_a, entity_b, entity_c])); +/// +/// // If we exclude Disabled, it functions the same as the default behavior +/// let mut query = world.query_filtered::>(); +/// assert_eq!(1, query.iter(&world).count()); +/// assert_eq!(Ok(entity_a), query.get_single(&world)); +/// ``` +#[derive(bevy_ecs_macros::Component, Clone, Copy)] +#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +pub struct Disabled; + /// A debug checked version of [`Option::unwrap_unchecked`]. Will panic in /// debug modes if unwrapping a `None` or `Err` value in debug mode, but is /// equivalent to `Option::unwrap_unchecked` or `Result::unwrap_unchecked` diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 84fd805fa6ca9..57b1bbcb8940b 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -2,7 +2,7 @@ use crate::{ archetype::{Archetype, ArchetypeComponentId, ArchetypeGeneration, ArchetypeId}, component::{ComponentId, Tick}, entity::Entity, - prelude::FromWorld, + prelude::{Disabled, FromWorld}, query::{ Access, BatchingStrategy, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, QueryIter, QueryParIter, @@ -146,6 +146,17 @@ impl QueryState { } } +fn contains_disabled( + component_access: &FilteredAccess, + disabled_id: ComponentId, +) -> bool { + component_access.access().has_read(disabled_id) + || component_access.access().has_archetypal(disabled_id) + || component_access.filter_sets.iter().any(|f| { + f.with.contains(disabled_id.index()) || f.without.contains(disabled_id.index()) + }) +} + impl QueryState { /// Creates a new [`QueryState`] from a given [`World`] and inherits the result of `world.id()`. pub fn new(world: &mut World) -> Self { @@ -190,6 +201,12 @@ impl QueryState { // properly considered in a global "cross-query" context (both within systems and across systems). component_access.extend(&filter_component_access); + let disabled_id = world.init_component::(); + let allow_disabled = contains_disabled(&component_access, disabled_id); + if !allow_disabled { + component_access.and_without(disabled_id); + } + Self { world_id: world.id(), archetype_generation: ArchetypeGeneration::initial(), @@ -214,13 +231,20 @@ impl QueryState { let filter_state = F::init_state(builder.world_mut()); D::set_access(&mut fetch_state, builder.access()); + let mut component_access = builder.access().clone(); + let disabled_id = builder.world_mut().init_component::(); + let allow_disabled = contains_disabled(&component_access, disabled_id); + if !allow_disabled { + component_access.and_without(disabled_id); + } + let mut state = Self { world_id: builder.world().id(), archetype_generation: ArchetypeGeneration::initial(), matched_storage_ids: Vec::new(), fetch_state, filter_state, - component_access: builder.access().clone(), + component_access, matched_tables: Default::default(), matched_archetypes: Default::default(), #[cfg(feature = "trace")] @@ -1627,7 +1651,7 @@ impl From> for QueryState>::new(&mut world); let _: QueryState> = query_1.join_filtered(&world, &query_2); } + + #[test] + fn does_exclude_disabled() { + let mut world = World::new(); + let entity_a = world.spawn(A(0)).id(); + let _ = world.spawn((A(1), Disabled)).id(); + let entity_c = world.spawn(A(1)).id(); + world.disable(entity_c); + + let mut query = QueryState::::new(&mut world); + assert_eq!(entity_a, query.single(&world)); + + let mut query = QueryState::>::new(&mut world); + assert_eq!(entity_a, query.single(&world)); + } + + #[test] + fn can_request_disabled() { + let mut world = World::new(); + let _ = world.spawn(A(0)).id(); + let _ = world.spawn((A(1), Disabled)).id(); + let entity_c = world.spawn(A(1)).id(); + world.disable(entity_c); + + let mut query = QueryState::>::new(&mut world); + assert_eq!(2, query.iter(&world).count()); + + let mut query = QueryState::<(Entity, &Disabled)>::new(&mut world); + assert_eq!(2, query.iter(&world).count()); + + let mut query = QueryState::<(Entity, &mut Disabled)>::new(&mut world); + assert_eq!(2, query.iter(&world).count()); + + let mut query = QueryState::<(Entity, Ref)>::new(&mut world); + assert_eq!(2, query.iter(&world).count()); + + let mut query = QueryState::<(Entity, Option<&Disabled>)>::new(&mut world); + assert_eq!(3, query.iter(&world).count()); + + let mut query = QueryState::<(Entity, Has)>::new(&mut world); + assert_eq!(3, query.iter(&world).count()); + } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 3af9a4e9da7f7..5319247ab395e 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -933,6 +933,16 @@ impl EntityCommands<'_> { self.add(despawn); } + /// Enable this entity, see [`Disabled`](crate::prelude::Disabled) for more information. + pub fn enable(&mut self) { + self.add(enable); + } + + /// Disable this entity, see [`Disabled`](crate::prelude::Disabled) for more information. + pub fn disable(&mut self) { + self.add(disable); + } + /// Pushes an [`EntityCommand`] to the queue, which will get executed for the current [`Entity`]. /// /// # Examples @@ -991,6 +1001,10 @@ impl EntityCommands<'_> { /// } /// # bevy_ecs::system::assert_is_system(remove_combat_stats_system); /// ``` + /// + /// # Disabled + /// + /// Calling retain does not remove the [`Disabled`](crate::prelude::Disabled) marker even if it is present. pub fn retain(&mut self) -> &mut Self where T: Bundle, @@ -1084,6 +1098,14 @@ fn despawn(entity: Entity, world: &mut World) { world.despawn(entity); } +fn enable(entity: Entity, world: &mut World) { + world.enable(entity); +} + +fn disable(entity: Entity, world: &mut World) { + world.disable(entity); +} + /// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity. fn insert(bundle: T) -> impl EntityCommand { move |entity: Entity, world: &mut World| { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1308198186c40..c790d6c06ccd4 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4,7 +4,7 @@ use crate::{ change_detection::MutUntyped, component::{Component, ComponentId, ComponentTicks, Components, StorageType}, entity::{Entities, Entity, EntityLocation}, - query::{Access, DebugCheckedUnwrap}, + query::{Access, DebugCheckedUnwrap, Disabled}, removal_detection::RemovedComponentEvents, storage::Storages, world::{Mut, World}, @@ -1136,6 +1136,8 @@ impl<'w> EntityWorldMut<'w> { /// /// See [`EntityCommands::retain`](crate::system::EntityCommands::retain) for more details. pub fn retain(&mut self) -> &mut Self { + let disabled_id = self.world.init_component::(); + let archetypes = &mut self.world.archetypes; let storages = &mut self.world.storages; let components = &mut self.world.components; @@ -1148,7 +1150,7 @@ impl<'w> EntityWorldMut<'w> { let to_remove = &old_archetype .components() - .filter(|c| !retained_bundle_info.components().contains(c)) + .filter(|c| !retained_bundle_info.components().contains(c) && *c != disabled_id) .collect::>(); let remove_bundle = self.world.bundles.init_dynamic_info(components, to_remove); @@ -1269,6 +1271,16 @@ impl<'w> EntityWorldMut<'w> { self.entity } + /// Enables the current entity, see [`Disabled`] for more information. + pub fn enable(&mut self) { + self.remove::(); + } + + /// Disables the current entity, see [`Disabled`] for more information. + pub fn disable(&mut self) { + self.insert(Disabled); + } + /// Gets read-only access to the world that the current entity belongs to. #[inline] pub fn world(&self) -> &World { @@ -2583,6 +2595,25 @@ mod tests { assert_eq!(world.entity(ent).archetype().components().next(), None); } + // Test that calling retain retains `Disabled`. + #[test] + fn retain_disabled() { + #[derive(Component)] + struct Marker; + + let mut world = World::new(); + let ent = world + .spawn((Marker::<1>, Marker::<2>, Marker::<3>, Disabled)) + .id(); + + let disabled_id = world.init_component::(); + world.entity_mut(ent).retain::>(); + assert!(world.entity(ent).contains_id(disabled_id)); + + world.entity_mut(ent).retain::<()>(); + assert!(world.entity(ent).contains_id(disabled_id)); + } + // Test removing some components with `retain`, including components not on the entity. #[test] fn retain_some_components() { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ed6781ffe3596..bc7c6ecee3b14 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -939,6 +939,32 @@ impl World { } } + /// Enables the given `entity`, if it exists and was [`Disabled`](crate::prelude::Disabled). + /// Returns `true` if the `entity` is now enabled and `false` if the `entity` does not exist. + #[inline] + pub fn enable(&mut self, entity: Entity) -> bool { + if let Some(mut entity) = self.get_entity_mut(entity) { + entity.enable(); + true + } else { + warn!("error[B0003]: Could not enable entity {:?} because it doesn't exist in this World.", entity); + false + } + } + + /// Disables the given `entity`, if it exists and didn't have [`Disabled`](crate::prelude::Disabled) yet. + /// Returns `true` if the `entity` is now disabled and `false` if the `entity` does not exist. + #[inline] + pub fn disable(&mut self, entity: Entity) -> bool { + if let Some(mut entity) = self.get_entity_mut(entity) { + entity.disable(); + true + } else { + warn!("error[B0003]: Could not disable entity {:?} because it doesn't exist in this World.", entity); + false + } + } + /// Clears the internal component tracker state. /// /// The world maintains some internal state about changed and removed components. This state diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index 26f26233a0fe5..09a2e962cf9dc 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -135,15 +135,236 @@ impl<'w> DespawnRecursiveExt for EntityWorldMut<'w> { } } +/// Disables the given entity and all its children recursively +#[derive(Debug)] +pub struct DisableRecursive { + /// Target entity + pub entity: Entity, +} + +/// Disables the given entity's children recursively +#[derive(Debug)] +pub struct DisableChildrenRecursive { + /// Target entity + pub entity: Entity, +} + +/// Function for disabling an entity and all its children +pub fn disable_with_children_recursive(world: &mut World, entity: Entity) { + disable_children_recursive(world, entity); + + if !world.disable(entity) { + debug!("Failed to disable entity {:?}", entity); + } +} + +fn disable_children_recursive(world: &mut World, entity: Entity) { + if let Some(children) = world.entity(entity).get::() { + for e in children.0.clone() { + disable_with_children_recursive(world, e); + } + } +} + +impl Command for DisableRecursive { + fn apply(self, world: &mut World) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "command", + name = "DisableRecursive", + entity = bevy_utils::tracing::field::debug(self.entity) + ) + .entered(); + disable_with_children_recursive(world, self.entity); + } +} + +impl Command for DisableChildrenRecursive { + fn apply(self, world: &mut World) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "command", + name = "DisableChildrenRecursive", + entity = bevy_utils::tracing::field::debug(self.entity) + ) + .entered(); + disable_children_recursive(world, self.entity); + } +} + +/// Trait that holds functions for disabling recursively down the hierarchy +pub trait DisableRecursiveExt { + /// Disables the provided entity alongside all descendants. + fn disable_recursive(self); + + /// Disables all descendants of the given entity. + fn disable_descendants(&mut self) -> &mut Self; +} + +impl DisableRecursiveExt for EntityCommands<'_> { + fn disable_recursive(mut self) { + let entity = self.id(); + self.commands().add(DisableRecursive { entity }); + } + + fn disable_descendants(&mut self) -> &mut Self { + let entity = self.id(); + self.commands().add(DisableChildrenRecursive { entity }); + self + } +} + +impl<'w> DisableRecursiveExt for EntityWorldMut<'w> { + fn disable_recursive(self) { + let entity = self.id(); + + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "disable_recursive", + entity = bevy_utils::tracing::field::debug(entity) + ) + .entered(); + + disable_with_children_recursive(self.into_world_mut(), entity); + } + + fn disable_descendants(&mut self) -> &mut Self { + let entity = self.id(); + + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "disable_descendants", + entity = bevy_utils::tracing::field::debug(entity) + ) + .entered(); + + self.world_scope(|world| { + disable_children_recursive(world, entity); + }); + self + } +} + +/// Enables the given entity and all its children recursively +#[derive(Debug)] +pub struct EnableRecursive { + /// Target entity + pub entity: Entity, +} + +/// Enables the given entity's children recursively +#[derive(Debug)] +pub struct EnableChildrenRecursive { + /// Target entity + pub entity: Entity, +} + +/// Function for enabling an entity and all its children +pub fn enable_with_children_recursive(world: &mut World, entity: Entity) { + enable_children_recursive(world, entity); + + if !world.enable(entity) { + debug!("Failed to enable entity {:?}", entity); + } +} + +fn enable_children_recursive(world: &mut World, entity: Entity) { + if let Some(children) = world.entity(entity).get::() { + for e in children.0.clone() { + enable_with_children_recursive(world, e); + } + } +} + +impl Command for EnableRecursive { + fn apply(self, world: &mut World) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "command", + name = "EnableRecursive", + entity = bevy_utils::tracing::field::debug(self.entity) + ) + .entered(); + enable_with_children_recursive(world, self.entity); + } +} + +impl Command for EnableChildrenRecursive { + fn apply(self, world: &mut World) { + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "command", + name = "EnableChildrenRecursive", + entity = bevy_utils::tracing::field::debug(self.entity) + ) + .entered(); + enable_children_recursive(world, self.entity); + } +} + +/// Trait that holds functions for enabling recursively down the hierarchy +pub trait EnableRecursiveExt { + /// Enables the provided entity alongside all descendants. + fn enable_recursive(self); + + /// Enables all descendants of the given entity. + fn enable_descendants(&mut self) -> &mut Self; +} + +impl EnableRecursiveExt for EntityCommands<'_> { + fn enable_recursive(mut self) { + let entity = self.id(); + self.commands().add(EnableRecursive { entity }); + } + + fn enable_descendants(&mut self) -> &mut Self { + let entity = self.id(); + self.commands().add(EnableChildrenRecursive { entity }); + self + } +} + +impl<'w> EnableRecursiveExt for EntityWorldMut<'w> { + fn enable_recursive(self) { + let entity = self.id(); + + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "enable_recursive", + entity = bevy_utils::tracing::field::debug(entity) + ) + .entered(); + + enable_with_children_recursive(self.into_world_mut(), entity); + } + + fn enable_descendants(&mut self) -> &mut Self { + let entity = self.id(); + + #[cfg(feature = "trace")] + let _span = bevy_utils::tracing::info_span!( + "enable_descendants", + entity = bevy_utils::tracing::field::debug(entity) + ) + .entered(); + + self.world_scope(|world| { + enable_children_recursive(world, entity); + }); + self + } +} + #[cfg(test)] mod tests { use bevy_ecs::{ component::Component, + query::Disabled, system::Commands, world::{CommandQueue, World}, }; - use super::DespawnRecursiveExt; + use super::{DespawnRecursiveExt, DisableRecursiveExt, EnableRecursiveExt}; use crate::{child_builder::BuildChildren, components::Children}; #[derive(Component, Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Debug)] @@ -249,6 +470,37 @@ mod tests { assert!(world.get_entity(child).is_none()); } + #[test] + fn disable_enable_recursive() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + let mut commands = Commands::new(&mut queue, &world); + + let grandchild = commands.spawn_empty().id(); + let child = commands.spawn_empty().add_child(grandchild).id(); + let parent = commands.spawn_empty().add_child(child).id(); + + commands.entity(child).add_child(grandchild); + commands.entity(parent).add_child(child); + commands.entity(parent).disable_recursive(); + + queue.apply(&mut world); + + // All entities should be disabled + assert!(world.entity(parent).contains::()); + assert!(world.entity(child).contains::()); + assert!(world.entity(grandchild).contains::()); + + let mut commands = Commands::new(&mut queue, &world); + commands.entity(child).enable_recursive(); + queue.apply(&mut world); + + // Only child and grandchild should be enabled again + assert!(world.entity(parent).contains::()); + assert!(!world.entity(child).contains::()); + assert!(!world.entity(grandchild).contains::()); + } + #[test] fn spawn_children_after_despawn_descendants() { let mut world = World::default();