add back all changes

This commit is contained in:
Trashtalk 2025-07-10 22:23:13 +00:00
parent 6dbe3600ed
commit 042337f787
10 changed files with 172 additions and 20 deletions

View File

@ -1734,6 +1734,9 @@ pub struct Components {
components: Vec<Option<ComponentInfo>>, components: Vec<Option<ComponentInfo>>,
indices: TypeIdMap<ComponentId>, indices: TypeIdMap<ComponentId>,
resource_indices: TypeIdMap<ComponentId>, resource_indices: TypeIdMap<ComponentId>,
/// A lookup for the entities on which resources are stored.
/// It uses ComponentIds instead of TypeIds for untyped APIs
pub(crate) resource_entities: HashMap<ComponentId, Entity>,
// This is kept internal and local to verify that no deadlocks can occor. // This is kept internal and local to verify that no deadlocks can occor.
queued: bevy_platform::sync::RwLock<QueuedComponents>, queued: bevy_platform::sync::RwLock<QueuedComponents>,
} }

View File

@ -207,6 +207,7 @@ mod tests {
use crate::{ use crate::{
prelude::World, prelude::World,
query::{Has, With}, query::{Has, With},
resource::IsResource,
}; };
use alloc::{vec, vec::Vec}; use alloc::{vec, vec::Vec};
@ -278,6 +279,9 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.register_disabling_component::<CustomDisabled>(); world.register_disabling_component::<CustomDisabled>();
// We don't want to query resources for this test.
world.register_disabling_component::<IsResource>();
world.spawn_empty(); world.spawn_empty();
world.spawn(Disabled); world.spawn(Disabled);
world.spawn(CustomDisabled); world.spawn(CustomDisabled);

View File

@ -332,6 +332,9 @@ mod tests {
#[test] #[test]
fn builder_or() { fn builder_or() {
let mut world = World::new(); let mut world = World::new();
// We don't want to query resources for this test.
world.register_disabling_component::<IsResource>();
world.spawn((A(0), B(0))); world.spawn((A(0), B(0)));
world.spawn(B(0)); world.spawn(B(0));
world.spawn(C(0)); world.spawn(C(0));

View File

@ -2659,6 +2659,7 @@ mod tests {
use crate::component::Component; use crate::component::Component;
use crate::entity::Entity; use crate::entity::Entity;
use crate::prelude::World; use crate::prelude::World;
use crate::resource::IsResource;
#[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)] #[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)]
struct A(f32); struct A(f32);
@ -2669,6 +2670,9 @@ mod tests {
#[test] #[test]
fn query_iter_sorts() { fn query_iter_sorts() {
let mut world = World::new(); let mut world = World::new();
// We don't want to query resources for this test.
world.register_disabling_component::<IsResource>();
for i in 0..100 { for i in 0..100 {
world.spawn(A(i as f32)); world.spawn(A(i as f32));
world.spawn((A(i as f32), Sparse(i))); world.spawn((A(i as f32), Sparse(i)));

View File

@ -1850,6 +1850,9 @@ mod tests {
#[test] #[test]
fn can_transmute_empty_tuple() { fn can_transmute_empty_tuple() {
let mut world = World::new(); let mut world = World::new();
// We don't want to query resources for this test.
world.register_disabling_component::<IsResource>();
world.register_component::<A>(); world.register_component::<A>();
let entity = world.spawn(A(10)).id(); let entity = world.spawn(A(10)).id();
@ -2207,6 +2210,7 @@ mod tests {
#[test] #[test]
fn query_default_filters_updates_is_dense() { fn query_default_filters_updates_is_dense() {
let mut world = World::new(); let mut world = World::new();
let num_resources = world.components().num_resources();
world.spawn((Table, Sparse)); world.spawn((Table, Sparse));
world.spawn(Table); world.spawn(Table);
world.spawn(Sparse); world.spawn(Sparse);
@ -2214,7 +2218,7 @@ mod tests {
let mut query = QueryState::<()>::new(&mut world); let mut query = QueryState::<()>::new(&mut world);
// There are no sparse components involved thus the query is dense // There are no sparse components involved thus the query is dense
assert!(query.is_dense); assert!(query.is_dense);
assert_eq!(3, query.iter(&world).count()); assert_eq!(3, query.iter(&world).count() - num_resources);
let mut df = DefaultQueryFilters::empty(); let mut df = DefaultQueryFilters::empty();
df.register_disabling_component(world.register_component::<Sparse>()); df.register_disabling_component(world.register_component::<Sparse>());

View File

@ -1,5 +1,10 @@
//! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World). //! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World).
use crate::prelude::Component;
use crate::prelude::ReflectComponent;
use bevy_reflect::prelude::ReflectDefault;
use bevy_reflect::Reflect;
use core::marker::PhantomData;
// The derive macro for the `Resource` trait // The derive macro for the `Resource` trait
pub use bevy_ecs_macros::Resource; pub use bevy_ecs_macros::Resource;
@ -73,3 +78,89 @@ pub use bevy_ecs_macros::Resource;
note = "consider annotating `{Self}` with `#[derive(Resource)]`" note = "consider annotating `{Self}` with `#[derive(Resource)]`"
)] )]
pub trait Resource: Send + Sync + 'static {} pub trait Resource: Send + Sync + 'static {}
/// A marker component for the entity that stores the resource of type `T`.
///
/// This component is automatically inserted when a resource of type `T` is inserted into the world,
/// and can be used to find the entity that stores a particular resource.
///
/// By contrast, the [`IsResource`] component is used to find all entities that store resources,
/// regardless of the type of resource they store.
///
/// This component comes with a hook that ensures that at most one entity has this component for any given `R`:
/// adding this component to an entity (or spawning an entity with this component) will despawn any other entity with this component.
#[derive(Component, Debug)]
#[require(IsResource)]
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Default))]
pub struct ResourceEntity<R: Resource>(#[reflect(ignore)] PhantomData<R>);
impl<R: Resource> Default for ResourceEntity<R> {
fn default() -> Self {
ResourceEntity(PhantomData)
}
}
/// A marker component for entities which store resources.
///
/// By contrast, the [`ResourceEntity<R>`] component is used to find the entity that stores a particular resource.
/// This component is required by the [`ResourceEntity<R>`] component, and will automatically be added.
#[cfg_attr(
feature = "bevy_reflect",
derive(Reflect),
reflect(Component, Default, Debug)
)]
#[derive(Component, Default, Debug)]
pub struct IsResource;
#[cfg(test)]
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
mod tests {
use crate::change_detection::MaybeLocation;
use crate::ptr::PtrMut;
use crate::resource::Resource;
use crate::world::World;
use bevy_platform::prelude::String;
use core::mem::ManuallyDrop;
#[test]
fn unique_resource_entities() {
#[derive(Default, Resource)]
struct TestResource1;
#[derive(Resource)]
#[expect(dead_code, reason = "field needed for testing")]
struct TestResource2(String);
#[derive(Resource)]
#[expect(dead_code, reason = "field needed for testing")]
struct TestResource3(u8);
let mut world = World::new();
let start = world.entities().len();
world.init_resource::<TestResource1>();
assert_eq!(world.entities().len(), start + 1);
world.insert_resource(TestResource2(String::from("Foo")));
assert_eq!(world.entities().len(), start + 2);
// like component registration, which just makes it known to the world that a component exists,
// registering a resource should not spawn an entity.
let id = world.register_resource::<TestResource3>();
assert_eq!(world.entities().len(), start + 2);
unsafe {
// SAFETY
// *
world.insert_resource_by_id(
id,
PtrMut::from(&mut ManuallyDrop::new(20 as u8)).promote(),
MaybeLocation::caller(),
);
}
assert_eq!(world.entities().len(), start + 3);
assert!(world.remove_resource_by_id(id).is_some());
assert_eq!(world.entities().len(), start + 2);
world.remove_resource::<TestResource1>();
assert_eq!(world.entities().len(), start + 1);
// make sure that trying to add a resource twice results, doesn't change the entity count
world.insert_resource(TestResource2(String::from("Bar")));
assert_eq!(world.entities().len(), start + 1);
}
}

View File

@ -216,12 +216,14 @@ impl World {
&mut self.entities &mut self.entities
} }
/// Retrieves the number of [`Entities`] in the world. /// Retrieves the number of [`Entities`] in the world. This count does not include resource entities.
/// ///
/// This is helpful as a diagnostic, but it can also be used effectively in tests. /// This is helpful as a diagnostic, but it can also be used effectively in tests.
#[inline] #[inline]
pub fn num_entities(&self) -> u32 { pub fn entity_count(&self) -> u32 {
self.entities.len() self.entities
.len()
.saturating_sub(self.components.resource_entities.len() as u32)
} }
/// Retrieves this world's [`Archetypes`] collection. /// Retrieves this world's [`Archetypes`] collection.

View File

@ -49,7 +49,13 @@ pub mod prelude {
use bevy_app::prelude::*; use bevy_app::prelude::*;
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use {bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs}; use {
bevy_asset::AssetApp,
bevy_ecs::schedule::IntoScheduleConfigs,
bevy_ecs::{
entity_disabling::DefaultQueryFilters, resource::IsResource, resource::ResourceEntity,
},
};
/// Plugin that provides scene functionality to an [`App`]. /// Plugin that provides scene functionality to an [`App`].
#[derive(Default)] #[derive(Default)]
@ -64,6 +70,8 @@ impl Plugin for ScenePlugin {
.init_resource::<SceneSpawner>() .init_resource::<SceneSpawner>()
.register_type::<SceneRoot>() .register_type::<SceneRoot>()
.register_type::<DynamicSceneRoot>() .register_type::<DynamicSceneRoot>()
.register_type::<IsResource>()
.register_type::<ResourceEntity<DefaultQueryFilters>>()
.add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain());
// Register component hooks for DynamicSceneRoot // Register component hooks for DynamicSceneRoot

View File

@ -609,7 +609,7 @@ mod tests {
assert_eq!(scene_component_a.y, 4.0); assert_eq!(scene_component_a.y, 4.0);
assert_eq!( assert_eq!(
app.world().entity(entity).get::<Children>().unwrap().len(), app.world().entity(entity).get::<Children>().unwrap().len(),
1 3 // two resources-as-entities are also counted
); );
// let's try to delete the scene // let's try to delete the scene

View File

@ -516,9 +516,11 @@ mod tests {
}; };
use bevy_ecs::{ use bevy_ecs::{
entity::{Entity, EntityHashMap}, entity::{Entity, EntityHashMap},
entity_disabling::DefaultQueryFilters,
prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, prelude::{Component, ReflectComponent, ReflectResource, Resource, World},
query::{With, Without}, query::{With, Without},
reflect::AppTypeRegistry, reflect::AppTypeRegistry,
resource::{IsResource, ResourceEntity},
world::FromWorld, world::FromWorld,
}; };
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
@ -611,6 +613,8 @@ mod tests {
registry.register::<MyEntityRef>(); registry.register::<MyEntityRef>();
registry.register::<Entity>(); registry.register::<Entity>();
registry.register::<MyResource>(); registry.register::<MyResource>();
registry.register::<IsResource>();
registry.register::<ResourceEntity<DefaultQueryFilters>>();
} }
world.insert_resource(registry); world.insert_resource(registry);
world world
@ -638,20 +642,20 @@ mod tests {
), ),
}, },
entities: { entities: {
4294967293: ( 4294967291: (
components: { components: {
"bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Bar": (345),
"bevy_scene::serde::tests::Baz": (789), "bevy_scene::serde::tests::Baz": (789),
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
}, },
), ),
4294967294: ( 4294967292: (
components: { components: {
"bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Bar": (345),
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
}, },
), ),
4294967295: ( 4294967293: (
components: { components: {
"bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Foo": (123),
}, },
@ -757,7 +761,7 @@ mod tests {
.write_to_world(&mut dst_world, &mut map) .write_to_world(&mut dst_world, &mut map)
.unwrap(); .unwrap();
assert_eq!(2, deserialized_scene.entities.len()); assert_eq!(4, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene); assert_scene_eq(&scene, &deserialized_scene);
let bar_to_foo = dst_world let bar_to_foo = dst_world
@ -785,7 +789,7 @@ mod tests {
let (scene, deserialized_scene) = roundtrip_ron(&world); let (scene, deserialized_scene) = roundtrip_ron(&world);
assert_eq!(1, deserialized_scene.entities.len()); assert_eq!(3, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene); assert_scene_eq(&scene, &deserialized_scene);
let mut world = create_world(); let mut world = create_world();
@ -815,10 +819,19 @@ mod tests {
assert_eq!( assert_eq!(
vec![ vec![
0, 1, 255, 255, 255, 255, 15, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, 0, 3, 253, 255, 255, 255, 15, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101,
58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121,
67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 2, 3, 102, 102, 166, 63, 205, 204,
108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 254, 255,
255, 255, 15, 1, 30, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101, 115,
111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99, 101, 255,
255, 255, 255, 15, 2, 30, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101,
115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99, 101,
83, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114, 99,
101, 58, 58, 82, 101, 115, 111, 117, 114, 99, 101, 69, 110, 116, 105, 116, 121, 60,
98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 101, 110, 116, 105, 116, 121, 95, 100,
105, 115, 97, 98, 108, 105, 110, 103, 58, 58, 68, 101, 102, 97, 117, 108, 116, 81,
117, 101, 114, 121, 70, 105, 108, 116, 101, 114, 115, 62
], ],
serialized_scene serialized_scene
); );
@ -830,7 +843,7 @@ mod tests {
.deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene)) .deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene))
.unwrap(); .unwrap();
assert_eq!(1, deserialized_scene.entities.len()); assert_eq!(3, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene); assert_scene_eq(&scene, &deserialized_scene);
} }
@ -856,11 +869,21 @@ mod tests {
assert_eq!( assert_eq!(
vec![ vec![
146, 128, 129, 206, 255, 255, 255, 255, 145, 129, 217, 37, 98, 101, 118, 121, 95, 146, 128, 131, 206, 255, 255, 255, 253, 145, 129, 217, 37, 98, 101, 118, 121, 95,
115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 115, 99, 101, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115,
116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1, 116, 115, 58, 58, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 147, 147, 1,
2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112, 2, 3, 146, 202, 63, 166, 102, 102, 202, 64, 108, 204, 205, 129, 165, 84, 117, 112,
108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33, 206, 255,
255, 255, 254, 145, 129, 190, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114,
101, 115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99,
101, 144, 206, 255, 255, 255, 255, 145, 130, 190, 98, 101, 118, 121, 95, 101, 99,
115, 58, 58, 114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82, 101, 115,
111, 117, 114, 99, 101, 144, 217, 83, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58,
114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 82, 101, 115, 111, 117, 114, 99,
101, 69, 110, 116, 105, 116, 121, 60, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58,
101, 110, 116, 105, 116, 121, 95, 100, 105, 115, 97, 98, 108, 105, 110, 103, 58,
58, 68, 101, 102, 97, 117, 108, 116, 81, 117, 101, 114, 121, 70, 105, 108, 116,
101, 114, 115, 62, 144
], ],
buf buf
); );
@ -874,7 +897,7 @@ mod tests {
.deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) .deserialize(&mut rmp_serde::Deserializer::new(&mut reader))
.unwrap(); .unwrap();
assert_eq!(1, deserialized_scene.entities.len()); assert_eq!(3, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene); assert_scene_eq(&scene, &deserialized_scene);
} }
@ -899,13 +922,23 @@ mod tests {
assert_eq!( assert_eq!(
vec![ vec![
0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 253, 255, 255, 255, 0, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101, 0, 0, 0, 0, 0, 0, 0, 37, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 115, 99, 101,
110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 110, 101, 58, 58, 115, 101, 114, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58,
77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 77, 121, 67, 111, 109, 112, 111, 110, 101, 110, 116, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0,
0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 102, 102, 166, 63, 205, 204, 108, 64, 1,
0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108,
100, 33 100, 33, 254, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0,
0, 0, 98, 101, 118, 121, 95, 101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114,
99, 101, 58, 58, 73, 115, 82, 101, 115, 111, 117, 114, 99, 101, 255, 255, 255, 255,
0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 30, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95,
101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 73, 115, 82,
101, 115, 111, 117, 114, 99, 101, 83, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95,
101, 99, 115, 58, 58, 114, 101, 115, 111, 117, 114, 99, 101, 58, 58, 82, 101, 115,
111, 117, 114, 99, 101, 69, 110, 116, 105, 116, 121, 60, 98, 101, 118, 121, 95,
101, 99, 115, 58, 58, 101, 110, 116, 105, 116, 121, 95, 100, 105, 115, 97, 98, 108,
105, 110, 103, 58, 58, 68, 101, 102, 97, 117, 108, 116, 81, 117, 101, 114, 121, 70,
105, 108, 116, 101, 114, 115, 62
], ],
serialized_scene serialized_scene
); );
@ -918,7 +951,7 @@ mod tests {
bincode::serde::seed_decode_from_slice(scene_deserializer, &serialized_scene, config) bincode::serde::seed_decode_from_slice(scene_deserializer, &serialized_scene, config)
.unwrap(); .unwrap();
assert_eq!(1, deserialized_scene.entities.len()); assert_eq!(3, deserialized_scene.entities.len());
assert_scene_eq(&scene, &deserialized_scene); assert_scene_eq(&scene, &deserialized_scene);
} }