Add simple Disabled marker (#17514)

# Objective

We have default query filters now, but there is no first-party marker
for entity disabling yet
Fixes #17458

## Solution

Add the marker, cool recursive features and/or potential hook changes
should be follow up work

## Testing

Added a unit test to check that the new marker is enabled by default
This commit is contained in:
NiseVoid 2025-02-02 22:42:25 +01:00 committed by GitHub
parent 75e8e8c0f6
commit 62285a47ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 63 additions and 8 deletions

View File

@ -26,7 +26,17 @@ use crate::{
component::{ComponentId, Components, StorageType},
query::FilteredAccess,
};
use bevy_ecs_macros::Resource;
use bevy_ecs_macros::{Component, Resource};
#[cfg(feature = "bevy_reflect")]
use {crate::reflect::ReflectComponent, bevy_reflect::Reflect};
/// A marker component for disabled entities. See [the module docs] for more info.
///
/// [the module docs]: crate::entity_disabling
#[derive(Component)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))]
pub struct Disabled;
/// The default filters for all queries, these are used to globally exclude entities from queries.
/// See the [module docs](crate::entity_disabling) for more info.
@ -37,10 +47,6 @@ pub struct DefaultQueryFilters {
}
impl DefaultQueryFilters {
#[cfg_attr(
not(test),
expect(dead_code, reason = "No Disabled component exist yet")
)]
/// Set the [`ComponentId`] for the entity disabling marker
pub(crate) fn set_disabled(&mut self, component_id: ComponentId) -> Option<()> {
if self.disabled.is_some() {

View File

@ -134,6 +134,7 @@ mod tests {
change_detection::Ref,
component::{require, Component, ComponentId, RequiredComponents, RequiredComponentsError},
entity::Entity,
entity_disabling::DefaultQueryFilters,
prelude::Or,
query::{Added, Changed, FilteredAccess, QueryFilter, With, Without},
resource::Resource,
@ -1530,6 +1531,8 @@ mod tests {
#[test]
fn filtered_query_access() {
let mut world = World::new();
// We remove entity disabling so it doesn't affect our query filters
world.remove_resource::<DefaultQueryFilters>();
let query = world.query_filtered::<&mut A, Changed<B>>();
let mut expected = FilteredAccess::<ComponentId>::default();

View File

@ -2552,15 +2552,15 @@ mod tests {
fn nothing() {}
assert!(world.iter_resources().count() == 0);
let resources = world.iter_resources().count();
let id = world.register_system_cached(nothing);
assert!(world.iter_resources().count() == 1);
assert_eq!(world.iter_resources().count(), resources + 1);
assert!(world.get_entity(id.entity).is_ok());
let mut commands = Commands::new(&mut queue, &world);
commands.unregister_system_cached(nothing);
queue.apply(&mut world);
assert!(world.iter_resources().count() == 0);
assert_eq!(world.iter_resources().count(), resources);
assert!(world.get_entity(id.entity).is_err());
}

View File

@ -40,6 +40,7 @@ use crate::{
RequiredComponentsError, Tick,
},
entity::{AllocAtWithoutReplacement, Entities, Entity, EntityLocation},
entity_disabling::{DefaultQueryFilters, Disabled},
event::{Event, EventId, Events, SendBatchIds},
observer::Observers,
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
@ -158,6 +159,11 @@ impl World {
let on_despawn = OnDespawn::register_component_id(self);
assert_eq!(ON_DESPAWN, on_despawn);
let disabled = self.register_component::<Disabled>();
let mut filters = DefaultQueryFilters::default();
filters.set_disabled(disabled);
self.insert_resource(filters);
}
/// Creates a new empty [`World`].
///
@ -3267,6 +3273,7 @@ impl World {
/// # struct B(u32);
/// #
/// # let mut world = World::new();
/// # world.remove_resource::<bevy_ecs::entity_disabling::DefaultQueryFilters>();
/// # world.insert_resource(A(1));
/// # world.insert_resource(B(2));
/// let mut total = 0;
@ -3765,6 +3772,7 @@ mod tests {
change_detection::DetectChangesMut,
component::{ComponentDescriptor, ComponentInfo, StorageType},
entity::hash_set::EntityHashSet,
entity_disabling::{DefaultQueryFilters, Disabled},
ptr::OwningPtr,
resource::Resource,
world::error::EntityFetchError,
@ -3954,6 +3962,8 @@ mod tests {
#[test]
fn iter_resources() {
let mut world = World::new();
// Remove DefaultQueryFilters so it doesn't show up in the iterator
world.remove_resource::<DefaultQueryFilters>();
world.insert_resource(TestResource(42));
world.insert_resource(TestResource2("Hello, world!".to_string()));
world.insert_resource(TestResource3);
@ -3980,6 +3990,8 @@ mod tests {
#[test]
fn iter_resources_mut() {
let mut world = World::new();
// Remove DefaultQueryFilters so it doesn't show up in the iterator
world.remove_resource::<DefaultQueryFilters>();
world.insert_resource(TestResource(42));
world.insert_resource(TestResource2("Hello, world!".to_string()));
world.insert_resource(TestResource3);
@ -4446,4 +4458,16 @@ mod tests {
None
);
}
#[test]
fn new_world_has_disabling() {
let mut world = World::new();
world.spawn(Foo);
world.spawn((Foo, Disabled));
assert_eq!(1, world.query::<&Foo>().iter(&world).count());
// If we explicitly remove the resource, no entities should be filtered anymore
world.remove_resource::<DefaultQueryFilters>();
assert_eq!(2, world.query::<&Foo>().iter(&world).count());
}
}

View File

@ -1,7 +1,10 @@
use core::any::TypeId;
use crate::{DynamicEntity, DynamicScene, SceneFilter};
use alloc::collections::BTreeMap;
use bevy_ecs::{
component::{Component, ComponentId},
entity_disabling::DefaultQueryFilters,
prelude::Entity,
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
resource::Resource,
@ -348,9 +351,17 @@ impl<'w> DynamicSceneBuilder<'w> {
/// [`deny_resource`]: Self::deny_resource
#[must_use]
pub fn extract_resources(mut self) -> Self {
let original_world_dqf_id = self
.original_world
.components()
.get_resource_id(TypeId::of::<DefaultQueryFilters>());
let type_registry = self.original_world.resource::<AppTypeRegistry>().read();
for (component_id, _) in self.original_world.storages().resources.iter() {
if Some(component_id) == original_world_dqf_id {
continue;
}
let mut extract_and_push = || {
let type_id = self
.original_world

View File

@ -1,7 +1,10 @@
use core::any::TypeId;
use crate::{DynamicScene, SceneSpawnError};
use bevy_asset::Asset;
use bevy_ecs::{
entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper},
entity_disabling::DefaultQueryFilters,
reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource},
world::World,
};
@ -59,8 +62,16 @@ impl Scene {
) -> Result<(), SceneSpawnError> {
let type_registry = type_registry.read();
let self_dqf_id = self
.world
.components()
.get_resource_id(TypeId::of::<DefaultQueryFilters>());
// Resources archetype
for (component_id, resource_data) in self.world.storages().resources.iter() {
if Some(component_id) == self_dqf_id {
continue;
}
if !resource_data.is_present() {
continue;
}