From 042337f787e99612085e606e4ffdb20bb5f64db9 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 10 Jul 2025 22:23:13 +0000 Subject: [PATCH 01/69] add back all changes --- crates/bevy_ecs/src/component.rs | 3 + crates/bevy_ecs/src/entity_disabling.rs | 4 ++ crates/bevy_ecs/src/query/builder.rs | 3 + crates/bevy_ecs/src/query/iter.rs | 4 ++ crates/bevy_ecs/src/query/state.rs | 6 +- crates/bevy_ecs/src/resource.rs | 91 +++++++++++++++++++++++++ crates/bevy_ecs/src/world/mod.rs | 8 ++- crates/bevy_scene/src/lib.rs | 10 ++- crates/bevy_scene/src/scene_spawner.rs | 2 +- crates/bevy_scene/src/serde.rs | 61 +++++++++++++---- 10 files changed, 172 insertions(+), 20 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index cfcde29ab2ff1..ef1e4a4c5e559 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -1734,6 +1734,9 @@ pub struct Components { components: Vec>, indices: TypeIdMap, resource_indices: TypeIdMap, + /// A lookup for the entities on which resources are stored. + /// It uses ComponentIds instead of TypeIds for untyped APIs + pub(crate) resource_entities: HashMap, // This is kept internal and local to verify that no deadlocks can occor. queued: bevy_platform::sync::RwLock, } diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 5d62011174dac..3c5523e4fd2b9 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -207,6 +207,7 @@ mod tests { use crate::{ prelude::World, query::{Has, With}, + resource::IsResource, }; use alloc::{vec, vec::Vec}; @@ -278,6 +279,9 @@ mod tests { let mut world = World::new(); world.register_disabling_component::(); + // We don't want to query resources for this test. + world.register_disabling_component::(); + world.spawn_empty(); world.spawn(Disabled); world.spawn(CustomDisabled); diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index b545caad8f92c..cbef579fbcf80 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -332,6 +332,9 @@ mod tests { #[test] fn builder_or() { let mut world = World::new(); + // We don't want to query resources for this test. + world.register_disabling_component::(); + world.spawn((A(0), B(0))); world.spawn(B(0)); world.spawn(C(0)); diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index eb49204434b6f..0b8a8b9a91745 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2659,6 +2659,7 @@ mod tests { use crate::component::Component; use crate::entity::Entity; use crate::prelude::World; + use crate::resource::IsResource; #[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)] struct A(f32); @@ -2669,6 +2670,9 @@ mod tests { #[test] fn query_iter_sorts() { let mut world = World::new(); + // We don't want to query resources for this test. + world.register_disabling_component::(); + for i in 0..100 { world.spawn(A(i as f32)); world.spawn((A(i as f32), Sparse(i))); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 00d8b6f97085b..e9188ba0bd740 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1850,6 +1850,9 @@ mod tests { #[test] fn can_transmute_empty_tuple() { let mut world = World::new(); + // We don't want to query resources for this test. + world.register_disabling_component::(); + world.register_component::(); let entity = world.spawn(A(10)).id(); @@ -2207,6 +2210,7 @@ mod tests { #[test] fn query_default_filters_updates_is_dense() { let mut world = World::new(); + let num_resources = world.components().num_resources(); world.spawn((Table, Sparse)); world.spawn(Table); world.spawn(Sparse); @@ -2214,7 +2218,7 @@ mod tests { let mut query = QueryState::<()>::new(&mut world); // There are no sparse components involved thus the 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(); df.register_disabling_component(world.register_component::()); diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 7da4f31113f4f..27ce87cffaa48 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -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). +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 pub use bevy_ecs_macros::Resource; @@ -73,3 +78,89 @@ pub use bevy_ecs_macros::Resource; note = "consider annotating `{Self}` with `#[derive(Resource)]`" )] 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(#[reflect(ignore)] PhantomData); + +impl Default for ResourceEntity { + fn default() -> Self { + ResourceEntity(PhantomData) + } +} + +/// A marker component for entities which store resources. +/// +/// By contrast, the [`ResourceEntity`] component is used to find the entity that stores a particular resource. +/// This component is required by the [`ResourceEntity`] 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::(); + 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::(); + 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::(); + 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); + } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 63e5d8543584d..9b27c32eab658 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -216,12 +216,14 @@ impl World { &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. #[inline] - pub fn num_entities(&self) -> u32 { - self.entities.len() + pub fn entity_count(&self) -> u32 { + self.entities + .len() + .saturating_sub(self.components.resource_entities.len() as u32) } /// Retrieves this world's [`Archetypes`] collection. diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 9b0845f80f373..b2936aae39997 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -49,7 +49,13 @@ pub mod prelude { use bevy_app::prelude::*; #[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`]. #[derive(Default)] @@ -64,6 +70,8 @@ impl Plugin for ScenePlugin { .init_resource::() .register_type::() .register_type::() + .register_type::() + .register_type::>() .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); // Register component hooks for DynamicSceneRoot diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 71cd848751894..bbd967527c363 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -609,7 +609,7 @@ mod tests { assert_eq!(scene_component_a.y, 4.0); assert_eq!( app.world().entity(entity).get::().unwrap().len(), - 1 + 3 // two resources-as-entities are also counted ); // let's try to delete the scene diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index cb8206d3dd33f..734f44498654b 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -516,9 +516,11 @@ mod tests { }; use bevy_ecs::{ entity::{Entity, EntityHashMap}, + entity_disabling::DefaultQueryFilters, prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, query::{With, Without}, reflect::AppTypeRegistry, + resource::{IsResource, ResourceEntity}, world::FromWorld, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; @@ -611,6 +613,8 @@ mod tests { registry.register::(); registry.register::(); registry.register::(); + registry.register::(); + registry.register::>(); } world.insert_resource(registry); world @@ -638,20 +642,20 @@ mod tests { ), }, entities: { - 4294967293: ( + 4294967291: ( components: { "bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Baz": (789), "bevy_scene::serde::tests::Foo": (123), }, ), - 4294967294: ( + 4294967292: ( components: { "bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Foo": (123), }, ), - 4294967295: ( + 4294967293: ( components: { "bevy_scene::serde::tests::Foo": (123), }, @@ -757,7 +761,7 @@ mod tests { .write_to_world(&mut dst_world, &mut map) .unwrap(); - assert_eq!(2, deserialized_scene.entities.len()); + assert_eq!(4, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); let bar_to_foo = dst_world @@ -785,7 +789,7 @@ mod tests { 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); let mut world = create_world(); @@ -815,10 +819,19 @@ mod tests { assert_eq!( 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, 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 ); @@ -830,7 +843,7 @@ mod tests { .deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene)) .unwrap(); - assert_eq!(1, deserialized_scene.entities.len()); + assert_eq!(3, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); } @@ -856,11 +869,21 @@ mod tests { assert_eq!( 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, 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, - 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 ); @@ -874,7 +897,7 @@ mod tests { .deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) .unwrap(); - assert_eq!(1, deserialized_scene.entities.len()); + assert_eq!(3, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); } @@ -899,13 +922,23 @@ mod tests { assert_eq!( 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, 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, 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, - 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 ); @@ -918,7 +951,7 @@ mod tests { bincode::serde::seed_decode_from_slice(scene_deserializer, &serialized_scene, config) .unwrap(); - assert_eq!(1, deserialized_scene.entities.len()); + assert_eq!(3, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); } From 64eac9a9a563c4477d5e5cd20d4db135d16a9cec Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 10 Jul 2025 22:39:22 +0000 Subject: [PATCH 02/69] cargo fmt --- crates/bevy_ecs/src/component/info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 9f122dbd82f73..0ccd681964338 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -1,5 +1,5 @@ use alloc::{borrow::Cow, vec::Vec}; -use bevy_platform::{collections::HashSet, sync::PoisonError, collections::HashMap}; +use bevy_platform::{collections::HashMap, collections::HashSet, sync::PoisonError}; use bevy_ptr::OwningPtr; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; From 956c1a669aaf483c7101cbccf40f0d779dd9c76b Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 10 Jul 2025 22:48:11 +0000 Subject: [PATCH 03/69] cargo clippy --- crates/bevy_ecs/src/component/info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 0ccd681964338..c9aa707a74dfa 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -348,7 +348,7 @@ pub struct Components { pub(super) indices: TypeIdMap, pub(super) resource_indices: TypeIdMap, /// A lookup for the entities on which resources are stored. - /// It uses ComponentIds instead of TypeIds for untyped APIs + /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs pub(crate) resource_entities: HashMap, // This is kept internal and local to verify that no deadlocks can occor. pub(super) queued: bevy_platform::sync::RwLock, From 073df4bf9df054318b304469093fb1f47ff13602 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 09:12:20 +0000 Subject: [PATCH 04/69] add entities with resources, auto disabled IsResource --- crates/bevy_ecs/src/entity_disabling.rs | 4 -- crates/bevy_ecs/src/query/builder.rs | 2 - crates/bevy_ecs/src/query/iter.rs | 3 -- crates/bevy_ecs/src/query/state.rs | 4 +- crates/bevy_ecs/src/resource.rs | 5 +++ crates/bevy_ecs/src/world/mod.rs | 53 +++++++++++++++++++++++++ 6 files changed, 59 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 3c5523e4fd2b9..5d62011174dac 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -207,7 +207,6 @@ mod tests { use crate::{ prelude::World, query::{Has, With}, - resource::IsResource, }; use alloc::{vec, vec::Vec}; @@ -279,9 +278,6 @@ mod tests { let mut world = World::new(); world.register_disabling_component::(); - // We don't want to query resources for this test. - world.register_disabling_component::(); - world.spawn_empty(); world.spawn(Disabled); world.spawn(CustomDisabled); diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index cbef579fbcf80..b7705694c993c 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -332,8 +332,6 @@ mod tests { #[test] fn builder_or() { let mut world = World::new(); - // We don't want to query resources for this test. - world.register_disabling_component::(); world.spawn((A(0), B(0))); world.spawn(B(0)); diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 0b8a8b9a91745..1771a464f9e4a 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2659,7 +2659,6 @@ mod tests { use crate::component::Component; use crate::entity::Entity; use crate::prelude::World; - use crate::resource::IsResource; #[derive(Component, Debug, PartialEq, PartialOrd, Clone, Copy)] struct A(f32); @@ -2670,8 +2669,6 @@ mod tests { #[test] fn query_iter_sorts() { let mut world = World::new(); - // We don't want to query resources for this test. - world.register_disabling_component::(); for i in 0..100 { world.spawn(A(i as f32)); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index e9188ba0bd740..fe37a76fe89d6 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1850,8 +1850,6 @@ mod tests { #[test] fn can_transmute_empty_tuple() { let mut world = World::new(); - // We don't want to query resources for this test. - world.register_disabling_component::(); world.register_component::(); let entity = world.spawn(A(10)).id(); @@ -2210,7 +2208,7 @@ mod tests { #[test] fn query_default_filters_updates_is_dense() { let mut world = World::new(); - let num_resources = world.components().num_resources(); + let num_resources = world.resource_count() as usize; world.spawn((Table, Sparse)); world.spawn(Table); world.spawn(Sparse); diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 27ce87cffaa48..de3233429830d 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -112,6 +112,11 @@ impl Default for ResourceEntity { #[derive(Component, Default, Debug)] pub struct IsResource; +/// Used in conjunction with [`ResourceEntity`], when no type information is available. +/// This is used by [`insert_resource_by_id`]. +#[derive(Resource)] +pub(crate) struct TypeErasedResource; + #[cfg(test)] #[expect(clippy::print_stdout, reason = "Allowed in tests.")] mod tests { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index e6c505462d36c..170a0157ad892 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -22,6 +22,7 @@ use crate::{ event::BufferedEvent, lifecycle::{ComponentHooks, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, prelude::{Add, Despawn, Insert, Remove, Replace}, + resource::{IsResource, ResourceEntity, TypeErasedResource}, }; pub use bevy_ecs_macros::FromWorld; use bevy_utils::prelude::DebugName; @@ -169,6 +170,7 @@ impl World { // This sets up `Disabled` as a disabling component, via the FromWorld impl self.init_resource::(); + self.register_disabling_component::(); } /// Creates a new empty [`World`]. /// @@ -226,6 +228,12 @@ impl World { .saturating_sub(self.components.resource_entities.len() as u32) } + /// Retrieves the number of [`Resource`]s in the world. + #[inline] + pub fn resource_count(&self) -> u32 { + self.components.resource_entities.len() as u32 + } + /// Retrieves this world's [`Archetypes`] collection. #[inline] pub fn archetypes(&self) -> &Archetypes { @@ -1702,6 +1710,18 @@ impl World { pub fn init_resource(&mut self) -> ComponentId { let caller = MaybeLocation::caller(); let component_id = self.components_registrator().register_resource::(); + + if !self + .components + .resource_entities + .contains_key(&component_id) + { + let entity = self.spawn(ResourceEntity::::default()).id(); + self.components + .resource_entities + .insert(component_id, entity); + } + if self .storages .resources @@ -1739,6 +1759,17 @@ impl World { caller: MaybeLocation, ) { let component_id = self.components_registrator().register_resource::(); + if !self + .components + .resource_entities + .contains_key(&component_id) + { + let entity = self.spawn(ResourceEntity::::default()).id(); + self.components + .resource_entities + .insert(component_id, entity); + } + OwningPtr::make(value, |ptr| { // SAFETY: component_id was just initialized and corresponds to resource of type R. unsafe { @@ -1806,6 +1837,10 @@ impl World { #[inline] pub fn remove_resource(&mut self) -> Option { let component_id = self.components.get_valid_resource_id(TypeId::of::())?; + if let Some(entity) = self.components.resource_entities.remove(&component_id) { + self.despawn(entity); + } + let (ptr, _, _) = self.storages.resources.get_mut(component_id)?.remove()?; // SAFETY: `component_id` was gotten via looking up the `R` type unsafe { Some(ptr.read::()) } @@ -2709,6 +2744,20 @@ impl World { ) { let change_tick = self.change_tick(); + if !self + .components + .resource_entities + .contains_key(&component_id) + { + // Since we don't know the type, we use a placeholder type. + let entity = self + .spawn(ResourceEntity::::default()) + .id(); + self.components + .resource_entities + .insert(component_id, entity); + } + let resource = self.initialize_resource_internal(component_id); // SAFETY: `value` is valid for `component_id`, ensured by caller unsafe { @@ -3401,6 +3450,10 @@ impl World { /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { + if let Some(entity) = self.components.resource_entities.remove(&component_id) { + self.despawn(entity); + } + self.storages .resources .get_mut(component_id)? From 6c8281d568c8b51cfad8c540b49a6c39f2a396d2 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 09:48:53 +0000 Subject: [PATCH 05/69] fixed and tests --- crates/bevy_ecs/src/world/entity_ref.rs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 90438b8d6e1e6..40e5944532aa0 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -4867,6 +4867,7 @@ mod tests { change_detection::{MaybeLocation, MutUntyped}, component::ComponentId, prelude::*, + resource::IsResource, system::{assert_is_system, RunSystemOnce as _}, world::{error::EntityComponentError, DeferredWorld, FilteredEntityMut, FilteredEntityRef}, }; @@ -5253,7 +5254,7 @@ mod tests { world.spawn(TestComponent(0)).insert(TestComponent2(0)); - let mut query = world.query::>(); + let mut query = world.query::>(); let mut found = false; for entity_ref in query.iter_mut(&mut world) { @@ -5311,7 +5312,10 @@ mod tests { world.run_system_once(system).unwrap(); - fn system(_: Query<&mut TestComponent>, query: Query>) { + fn system( + _: Query<&mut TestComponent>, + query: Query>, + ) { for entity_ref in query.iter() { assert!(matches!( entity_ref.get::(), @@ -5328,7 +5332,7 @@ mod tests { let mut world = World::new(); world.spawn(TestComponent(0)).insert(TestComponent2(0)); - let mut query = world.query::>(); + let mut query = world.query::>(); let mut found = false; for mut entity_mut in query.iter_mut(&mut world) { @@ -5393,7 +5397,10 @@ mod tests { world.run_system_once(system).unwrap(); - fn system(_: Query<&mut TestComponent>, mut query: Query>) { + fn system( + _: Query<&mut TestComponent>, + mut query: Query>, + ) { for mut entity_mut in query.iter_mut() { assert!(entity_mut .get_mut::() From f9dc9f1619eb76e3f9e62d705fa8c5ad09af8f88 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 10:10:34 +0000 Subject: [PATCH 06/69] fixed more tests --- crates/bevy_ecs/src/name.rs | 2 +- crates/bevy_ecs/src/query/builder.rs | 3 +++ crates/bevy_ecs/src/query/state.rs | 7 +++++-- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/name.rs b/crates/bevy_ecs/src/name.rs index 317c8f5017bb5..2887475cc648f 100644 --- a/crates/bevy_ecs/src/name.rs +++ b/crates/bevy_ecs/src/name.rs @@ -278,7 +278,7 @@ mod tests { let mut query = world.query::(); let d1 = query.get(&world, e1).unwrap(); // NameOrEntity Display for entities without a Name should be {index}v{generation} - assert_eq!(d1.to_string(), "0v0"); + assert_eq!(d1.to_string(), "1v0"); let d2 = query.get(&world, e2).unwrap(); // NameOrEntity Display for entities with a Name should be the Name assert_eq!(d2.to_string(), "MyName"); diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index b7705694c993c..33266f0c1c874 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -275,6 +275,7 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { mod tests { use crate::{ prelude::*, + resource::IsResource, world::{EntityMutExcept, EntityRefExcept, FilteredEntityMut, FilteredEntityRef}, }; use std::dbg; @@ -486,6 +487,7 @@ mod tests { let mut query = QueryBuilder::<(FilteredEntityMut, EntityMutExcept)>::new(&mut world) .data::() + .filter::>() .build(); // Removing `EntityMutExcept` just leaves A @@ -497,6 +499,7 @@ mod tests { let mut query = QueryBuilder::<(FilteredEntityMut, EntityRefExcept)>::new(&mut world) .data::() + .filter::>() .build(); // Removing `EntityRefExcept` just leaves A, plus read access diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index fe37a76fe89d6..a89c4c77cafb2 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1777,6 +1777,7 @@ mod tests { component::Component, entity_disabling::DefaultQueryFilters, prelude::*, + resource::IsResource, system::{QueryLens, RunSystemOnce}, world::{FilteredEntityMut, FilteredEntityRef}, }; @@ -2176,6 +2177,7 @@ mod tests { let mut df = DefaultQueryFilters::empty(); df.register_disabling_component(world.register_component::()); world.insert_resource(df); + world.register_disabling_component::(); // Without only matches the first entity let mut query = QueryState::<()>::new(&mut world); @@ -2208,7 +2210,6 @@ mod tests { #[test] fn query_default_filters_updates_is_dense() { let mut world = World::new(); - let num_resources = world.resource_count() as usize; world.spawn((Table, Sparse)); world.spawn(Table); world.spawn(Sparse); @@ -2216,11 +2217,12 @@ mod tests { let mut query = QueryState::<()>::new(&mut world); // There are no sparse components involved thus the query is dense assert!(query.is_dense); - assert_eq!(3, query.iter(&world).count() - num_resources); + assert_eq!(3, query.iter(&world).count()); let mut df = DefaultQueryFilters::empty(); df.register_disabling_component(world.register_component::()); world.insert_resource(df); + world.register_disabling_component::(); let mut query = QueryState::<()>::new(&mut world); // The query doesn't ask for sparse components, but the default filters adds @@ -2231,6 +2233,7 @@ mod tests { let mut df = DefaultQueryFilters::empty(); df.register_disabling_component(world.register_component::()); world.insert_resource(df); + world.register_disabling_component::(); let mut query = QueryState::<()>::new(&mut world); // If the filter is instead a table components, the query can still be dense From 07723acf392df8ce903be065f365bbb1857016d7 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 11:03:09 +0000 Subject: [PATCH 07/69] fixed moore tests --- crates/bevy_ecs/src/lib.rs | 12 ++++++------ crates/bevy_ecs/src/system/system.rs | 4 ++-- crates/bevy_ecs/src/world/mod.rs | 12 +++++++----- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 8a07cdc8e1b92..ce566e96d6de2 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -378,9 +378,9 @@ mod tests { let mut world = World::new(); let e = world.spawn((TableStored("abc"), A(123))).id(); let f = world.spawn((TableStored("def"), A(456))).id(); - assert_eq!(world.entities.len(), 2); + assert_eq!(world.entity_count(), 2); assert!(world.despawn(e)); - assert_eq!(world.entities.len(), 1); + assert_eq!(world.entity_count(), 1); assert!(world.get::(e).is_none()); assert!(world.get::(e).is_none()); assert_eq!(world.get::(f).unwrap().0, "def"); @@ -393,9 +393,9 @@ mod tests { let e = world.spawn((TableStored("abc"), SparseStored(123))).id(); let f = world.spawn((TableStored("def"), SparseStored(456))).id(); - assert_eq!(world.entities.len(), 2); + assert_eq!(world.entity_count(), 2); assert!(world.despawn(e)); - assert_eq!(world.entities.len(), 1); + assert_eq!(world.entity_count(), 1); assert!(world.get::(e).is_none()); assert!(world.get::(e).is_none()); assert_eq!(world.get::(f).unwrap().0, "def"); @@ -1786,7 +1786,7 @@ mod tests { fn try_insert_batch() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw_u32(1).unwrap(); + let e1 = Entity::from_raw_u32(2).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; @@ -1810,7 +1810,7 @@ mod tests { fn try_insert_batch_if_new() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw_u32(1).unwrap(); + let e1 = Entity::from_raw_u32(2).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index aad37c09d01f4..0e90bf2201868 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -494,9 +494,9 @@ mod tests { #[test] fn command_processing() { let mut world = World::new(); - assert_eq!(world.entities.len(), 0); + assert_eq!(world.entity_count(), 0); world.run_system_once(spawn_entity).unwrap(); - assert_eq!(world.entities.len(), 1); + assert_eq!(world.entity_count(), 1); } #[test] diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 170a0157ad892..1cd215e15790c 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3727,7 +3727,7 @@ mod tests { entity::EntityHashSet, entity_disabling::{DefaultQueryFilters, Disabled}, ptr::OwningPtr, - resource::Resource, + resource::{IsResource, Resource}, world::{error::EntityMutableFetchError, DeferredWorld}, }; use alloc::{ @@ -4159,8 +4159,10 @@ mod tests { let iterate_and_count_entities = |world: &World, entity_counters: &mut HashMap<_, _>| { entity_counters.clear(); for entity in world.iter_entities() { - let counter = entity_counters.entry(entity.id()).or_insert(0); - *counter += 1; + if !entity.contains::() { + let counter = entity_counters.entry(entity.id()).or_insert(0); + *counter += 1; + } } }; @@ -4257,9 +4259,9 @@ mod tests { let mut entities = world.iter_entities_mut().collect::>(); entities.sort_by_key(|e| e.get::().map(|a| a.0).or(e.get::().map(|b| b.0))); - let (a, b) = entities.split_at_mut(2); + let (a, b) = entities.split_at_mut(3); core::mem::swap( - &mut a[1].get_mut::().unwrap().0, + &mut a[2].get_mut::().unwrap().0, &mut b[0].get_mut::().unwrap().0, ); assert_eq!(world.entity(a1).get(), Some(&A(0))); From 1ba7af2b28baf3c3901d643c33aa8b0952460fc2 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 11:16:11 +0000 Subject: [PATCH 08/69] fix ci --- crates/bevy_ecs/src/resource.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index de3233429830d..fda419c43401c 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -118,14 +118,12 @@ pub struct IsResource; pub(crate) struct TypeErasedResource; #[cfg(test)] -#[expect(clippy::print_stdout, reason = "Allowed in tests.")] mod tests { use crate::change_detection::MaybeLocation; - use crate::ptr::PtrMut; + use crate::ptr::OwningPtr; use crate::resource::Resource; use crate::world::World; use bevy_platform::prelude::String; - use core::mem::ManuallyDrop; #[test] fn unique_resource_entities() { @@ -150,15 +148,12 @@ mod tests { // registering a resource should not spawn an entity. let id = world.register_resource::(); 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(), - ); - } + OwningPtr::make(20_u8, |ptr| { + // SAFETY: id was just initialized and corresponds to a resource. + unsafe { + world.insert_resource_by_id(id, ptr, 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); From b89c0420faa25350735a11ab8d0120d5bb724a2c Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 11:32:54 +0000 Subject: [PATCH 09/69] fix mooore tests (benches) --- benches/benches/bevy_ecs/world/world_get.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index 81e0bf2b0f511..8efe626a4b542 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -51,9 +51,10 @@ pub fn world_entity(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{entity_count}_entities"), |bencher| { let world = setup::
(entity_count); + let offset = world.resource_count(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { let entity = // SAFETY: Range is exclusive. Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); @@ -74,9 +75,10 @@ pub fn world_get(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{entity_count}_entities_table"), |bencher| { let world = setup::
(entity_count); + let offset = world.resource_count(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { let entity = // SAFETY: Range is exclusive. Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); @@ -86,9 +88,10 @@ pub fn world_get(criterion: &mut Criterion) { }); group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| { let world = setup::(entity_count); + let offset = world.resource_count(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { let entity = // SAFETY: Range is exclusive. Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); @@ -109,10 +112,11 @@ pub fn world_query_get(criterion: &mut Criterion) { for entity_count in RANGE.map(|i| i * 10_000) { group.bench_function(format!("{entity_count}_entities_table"), |bencher| { let mut world = setup::
(entity_count); + let offset = world.resource_count(); let mut query = world.query::<&Table>(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { let entity = // SAFETY: Range is exclusive. Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); @@ -137,9 +141,10 @@ pub fn world_query_get(criterion: &mut Criterion) { &WideTable<4>, &WideTable<5>, )>(); + let offset = world.resource_count(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { let entity = // SAFETY: Range is exclusive. Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); @@ -149,10 +154,11 @@ pub fn world_query_get(criterion: &mut Criterion) { }); group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| { let mut world = setup::(entity_count); + let offset = world.resource_count(); let mut query = world.query::<&Sparse>(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { let entity = // SAFETY: Range is exclusive. Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); @@ -177,9 +183,10 @@ pub fn world_query_get(criterion: &mut Criterion) { &WideSparse<4>, &WideSparse<5>, )>(); + let offset = world.resource_count(); bencher.iter(|| { - for i in 0..entity_count { + for i in offset..(entity_count + offset) { // SAFETY: Range is exclusive. let entity = Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) })); From 9b33933fc5d60d3bf7236e11e8aaf5708de3e323 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 11:48:51 +0000 Subject: [PATCH 10/69] fix docs --- crates/bevy_ecs/src/resource.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index fda419c43401c..06c5a05c30269 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -113,7 +113,7 @@ impl Default for ResourceEntity { pub struct IsResource; /// Used in conjunction with [`ResourceEntity`], when no type information is available. -/// This is used by [`insert_resource_by_id`]. +/// This is used by [`World::insert_resource_by_id`](crate::world::World). #[derive(Resource)] pub(crate) struct TypeErasedResource; From c7b4f1d0b1f3e650daec76736136c83dff870938 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 12:28:51 +0000 Subject: [PATCH 11/69] add migration guide --- .../resources_as_components.md | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 release-content/migration-guides/resources_as_components.md diff --git a/release-content/migration-guides/resources_as_components.md b/release-content/migration-guides/resources_as_components.md new file mode 100644 index 0000000000000..109a48d809bc8 --- /dev/null +++ b/release-content/migration-guides/resources_as_components.md @@ -0,0 +1,25 @@ +--- +title: Resources as Components +pull_requests: [19711] +--- + +Resources are very similair to Components: they are both data that can be stored in the ECS and queried. +The only real difference between them is that querying a resource will return either one or zero resources, whereas querying for a component can return any number of entities that match it. + +Even so, resources and components have always been seperate concepts within the ECS. +This leads to some annoying restrictions. +While components have [`ComponentHooks`](https://docs.rs/bevy/latest/bevy/ecs/component/struct.ComponentHooks.html), it's not possible to add lifecycle hooks to resources. +Moreover, the engine internals contain a lot of duplication because of it. + +This motivates us to transition resources to components, and while most of the public API will stay the same, some breaking changes are inevitable. + +This PR adds a dummy entity alongside every resource. This entity is inserted and removed alongside resources and doesn't do anything (yet). + +This changes `World::entities().len()` as there are more entities than you might expect there to be. For example, a new world, no longer contains zero entities. This is mostly important for unit tests. + +Two methods have been added `World::entity_count()` and `World::resource_count()`. The former returns the number of entities without the resource entities, while the latter returns the number of resources in the world. + +While the marker component `IsResource` is added to [default query filters](https://docs.rs/bevy/latest/bevy/ecs/entity_disabling/struct.DefaultQueryFilters.html), during world creation, resource entities might still show up in broad queries with [`EntityMutExcept`](https://docs.rs/bevy/latest/bevy/ecs/world/struct.EntityMutExcept.html) and [`EntityRefExcept`](https://docs.rs/bevy/latest/bevy/ecs/world/struct.EntityRefExcept.html). +They also show up in `World::iter_entities()`, `World::iter_entities_mut()` and [`QueryBuilder`](https://docs.rs/bevy/latest/bevy/ecs/prelude/struct.QueryBuilder.html). + +Lastly, because of the entity bump, the input and output of the `bevy_scene` crate is not equivalent to the previous version, meaning that it's unadvisable to read in scenes from the previous version into the current one. From 96aef455696ce0d2d767207c0c81c81e95449973 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 12 Jul 2025 12:31:14 +0000 Subject: [PATCH 12/69] fixed spelling errors to prove I'm not AI --- release-content/migration-guides/resources_as_components.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release-content/migration-guides/resources_as_components.md b/release-content/migration-guides/resources_as_components.md index 109a48d809bc8..919dbfbaf7b4a 100644 --- a/release-content/migration-guides/resources_as_components.md +++ b/release-content/migration-guides/resources_as_components.md @@ -3,10 +3,10 @@ title: Resources as Components pull_requests: [19711] --- -Resources are very similair to Components: they are both data that can be stored in the ECS and queried. +Resources are very similar to Components: they are both data that can be stored in the ECS and queried. The only real difference between them is that querying a resource will return either one or zero resources, whereas querying for a component can return any number of entities that match it. -Even so, resources and components have always been seperate concepts within the ECS. +Even so, resources and components have always been separate concepts within the ECS. This leads to some annoying restrictions. While components have [`ComponentHooks`](https://docs.rs/bevy/latest/bevy/ecs/component/struct.ComponentHooks.html), it's not possible to add lifecycle hooks to resources. Moreover, the engine internals contain a lot of duplication because of it. From 57581349b984732360cc43ff52a83a5bb6e0509d Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 13 Jul 2025 18:38:50 +0000 Subject: [PATCH 13/69] addressed comments --- crates/bevy_ecs/src/entity_disabling.rs | 3 +++ crates/bevy_ecs/src/query/state.rs | 5 +---- crates/bevy_ecs/src/world/mod.rs | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 5d62011174dac..5c412ca75e05b 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -70,6 +70,7 @@ use crate::{ component::{ComponentId, Components, StorageType}, query::FilteredAccess, + resource::IsResource, world::{FromWorld, World}, }; use bevy_ecs_macros::{Component, Resource}; @@ -143,6 +144,8 @@ impl FromWorld for DefaultQueryFilters { let mut filters = DefaultQueryFilters::empty(); let disabled_component_id = world.register_component::(); filters.register_disabling_component(disabled_component_id); + let is_resource_component_id = world.register_component::(); + filters.register_disabling_component(is_resource_component_id); filters } } diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index a89c4c77cafb2..afb3f4e022875 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -2174,10 +2174,7 @@ mod tests { world.spawn((B(0), C(0))); world.spawn(C(0)); - let mut df = DefaultQueryFilters::empty(); - df.register_disabling_component(world.register_component::()); - world.insert_resource(df); - world.register_disabling_component::(); + world.register_disabling_component::(); // Without only matches the first entity let mut query = QueryState::<()>::new(&mut world); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1cd215e15790c..4ccd1a15748f0 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -22,7 +22,7 @@ use crate::{ event::BufferedEvent, lifecycle::{ComponentHooks, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, prelude::{Add, Despawn, Insert, Remove, Replace}, - resource::{IsResource, ResourceEntity, TypeErasedResource}, + resource::{ResourceEntity, TypeErasedResource}, }; pub use bevy_ecs_macros::FromWorld; use bevy_utils::prelude::DebugName; @@ -170,7 +170,6 @@ impl World { // This sets up `Disabled` as a disabling component, via the FromWorld impl self.init_resource::(); - self.register_disabling_component::(); } /// Creates a new empty [`World`]. /// From 9824c3a5df04a05889a7423e0ecbeb00c90f5584 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 14 Jul 2025 22:37:34 +0000 Subject: [PATCH 14/69] testing robustness --- crates/bevy_ecs/src/query/state.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index afb3f4e022875..35251fe9d5179 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1777,7 +1777,6 @@ mod tests { component::Component, entity_disabling::DefaultQueryFilters, prelude::*, - resource::IsResource, system::{QueryLens, RunSystemOnce}, world::{FilteredEntityMut, FilteredEntityRef}, }; @@ -2216,10 +2215,9 @@ mod tests { assert!(query.is_dense); assert_eq!(3, query.iter(&world).count()); - let mut df = DefaultQueryFilters::empty(); - df.register_disabling_component(world.register_component::()); + let df = DefaultQueryFilters::from_world(&mut world); world.insert_resource(df); - world.register_disabling_component::(); + world.register_disabling_component::(); let mut query = QueryState::<()>::new(&mut world); // The query doesn't ask for sparse components, but the default filters adds @@ -2227,10 +2225,9 @@ mod tests { assert!(!query.is_dense); assert_eq!(1, query.iter(&world).count()); - let mut df = DefaultQueryFilters::empty(); - df.register_disabling_component(world.register_component::
()); + let df = DefaultQueryFilters::from_world(&mut world); world.insert_resource(df); - world.register_disabling_component::(); + world.register_disabling_component::
(); let mut query = QueryState::<()>::new(&mut world); // If the filter is instead a table components, the query can still be dense From 4d4e91476bf00d10da4c523af8ef1eeeb8568c27 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Wed, 23 Jul 2025 21:03:43 +0000 Subject: [PATCH 15/69] cleanup --- benches/benches/bevy_ecs/world/world_get.rs | 2 -- crates/bevy_ecs/src/query/builder.rs | 4 ---- crates/bevy_ecs/src/query/iter.rs | 1 - crates/bevy_ecs/src/query/state.rs | 2 -- crates/bevy_ecs/src/world/entity_ref.rs | 10 +++++----- crates/bevy_ecs/src/world/mod.rs | 21 +++++++++++---------- 6 files changed, 16 insertions(+), 24 deletions(-) diff --git a/benches/benches/bevy_ecs/world/world_get.rs b/benches/benches/bevy_ecs/world/world_get.rs index 4500e694ab4c6..dc5c2c5caf420 100644 --- a/benches/benches/bevy_ecs/world/world_get.rs +++ b/benches/benches/bevy_ecs/world/world_get.rs @@ -130,7 +130,6 @@ pub fn world_query_get(criterion: &mut Criterion) { &WideTable<4>, &WideTable<5>, )>(); - let offset = world.resource_count(); bencher.iter(|| { for entity in &entities { @@ -165,7 +164,6 @@ pub fn world_query_get(criterion: &mut Criterion) { &WideSparse<4>, &WideSparse<5>, )>(); - let offset = world.resource_count(); bencher.iter(|| { for entity in &entities { diff --git a/crates/bevy_ecs/src/query/builder.rs b/crates/bevy_ecs/src/query/builder.rs index 33266f0c1c874..b545caad8f92c 100644 --- a/crates/bevy_ecs/src/query/builder.rs +++ b/crates/bevy_ecs/src/query/builder.rs @@ -275,7 +275,6 @@ impl<'w, D: QueryData, F: QueryFilter> QueryBuilder<'w, D, F> { mod tests { use crate::{ prelude::*, - resource::IsResource, world::{EntityMutExcept, EntityRefExcept, FilteredEntityMut, FilteredEntityRef}, }; use std::dbg; @@ -333,7 +332,6 @@ mod tests { #[test] fn builder_or() { let mut world = World::new(); - world.spawn((A(0), B(0))); world.spawn(B(0)); world.spawn(C(0)); @@ -487,7 +485,6 @@ mod tests { let mut query = QueryBuilder::<(FilteredEntityMut, EntityMutExcept)>::new(&mut world) .data::() - .filter::>() .build(); // Removing `EntityMutExcept` just leaves A @@ -499,7 +496,6 @@ mod tests { let mut query = QueryBuilder::<(FilteredEntityMut, EntityRefExcept)>::new(&mut world) .data::() - .filter::>() .build(); // Removing `EntityRefExcept` just leaves A, plus read access diff --git a/crates/bevy_ecs/src/query/iter.rs b/crates/bevy_ecs/src/query/iter.rs index 47e228f6966e3..cab2ee9c9391d 100644 --- a/crates/bevy_ecs/src/query/iter.rs +++ b/crates/bevy_ecs/src/query/iter.rs @@ -2669,7 +2669,6 @@ mod tests { #[test] fn query_iter_sorts() { let mut world = World::new(); - for i in 0..100 { world.spawn(A(i as f32)); world.spawn((A(i as f32), Sparse(i))); diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 0b22b23fb260e..14736f8ced91d 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1850,7 +1850,6 @@ mod tests { #[test] fn can_transmute_empty_tuple() { let mut world = World::new(); - world.register_component::(); let entity = world.spawn(A(10)).id(); @@ -2226,7 +2225,6 @@ mod tests { let mut df = DefaultQueryFilters::from_world(&mut world); df.register_disabling_component(world.register_component::
()); world.insert_resource(df); - world.register_disabling_component::
(); let mut query = QueryState::<()>::new(&mut world); // If the filter is instead a table components, the query can still be dense diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 211b21643d082..2a6fb2c0681f8 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -5041,8 +5041,8 @@ mod tests { use crate::{ change_detection::{MaybeLocation, MutUntyped}, component::ComponentId, + entity_disabling::Internal, prelude::*, - resource::IsResource, system::{assert_is_system, RunSystemOnce as _}, world::{error::EntityComponentError, DeferredWorld, FilteredEntityMut, FilteredEntityRef}, }; @@ -5491,7 +5491,7 @@ mod tests { world.spawn(TestComponent(0)).insert(TestComponent2(0)); - let mut query = world.query::>(); + let mut query = world.query::>(); let mut found = false; for entity_ref in query.iter_mut(&mut world) { @@ -5551,7 +5551,7 @@ mod tests { fn system( _: Query<&mut TestComponent>, - query: Query>, + query: Query>, ) { for entity_ref in query.iter() { assert!(matches!( @@ -5569,7 +5569,7 @@ mod tests { let mut world = World::new(); world.spawn(TestComponent(0)).insert(TestComponent2(0)); - let mut query = world.query::>(); + let mut query = world.query::>(); let mut found = false; for mut entity_mut in query.iter_mut(&mut world) { @@ -5636,7 +5636,7 @@ mod tests { fn system( _: Query<&mut TestComponent>, - mut query: Query>, + mut query: Query>, ) { for mut entity_mut in query.iter_mut() { assert!(entity_mut diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index afd376bbe7500..f3f32f1e61e3a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3714,9 +3714,9 @@ mod tests { change_detection::{DetectChangesMut, MaybeLocation}, component::{ComponentCloneBehavior, ComponentDescriptor, ComponentInfo, StorageType}, entity::EntityHashSet, - entity_disabling::{DefaultQueryFilters, Disabled}, + entity_disabling::{DefaultQueryFilters, Disabled, Internal}, ptr::OwningPtr, - resource::{IsResource, Resource}, + resource::Resource, world::{error::EntityMutableFetchError, DeferredWorld}, }; use alloc::{ @@ -4148,11 +4148,9 @@ mod tests { let iterate_and_count_entities = |world: &World, entity_counters: &mut HashMap<_, _>| { entity_counters.clear(); #[expect(deprecated, reason = "remove this test in in 0.17.0")] - for entity in world.iter_entities() { - if !entity.contains::() { - let counter = entity_counters.entry(entity.id()).or_insert(0); - *counter += 1; - } + for entity in world.iter_entities().filter(|e| !e.contains::()) { + let counter = entity_counters.entry(entity.id()).or_insert(0); + *counter += 1; } }; @@ -4250,11 +4248,14 @@ mod tests { assert_eq!(world.entity(b2).get(), Some(&B(4))); #[expect(deprecated, reason = "remove this test in in 0.17.0")] - let mut entities = world.iter_entities_mut().collect::>(); + let mut entities = world + .iter_entities_mut() + .filter(|e| !e.contains::()) + .collect::>(); entities.sort_by_key(|e| e.get::().map(|a| a.0).or(e.get::().map(|b| b.0))); - let (a, b) = entities.split_at_mut(3); + let (a, b) = entities.split_at_mut(2); core::mem::swap( - &mut a[2].get_mut::().unwrap().0, + &mut a[1].get_mut::().unwrap().0, &mut b[0].get_mut::().unwrap().0, ); assert_eq!(world.entity(a1).get(), Some(&A(0))); From 78a06729345a9ec573ad44f0f4728135a5ae14bf Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Wed, 23 Jul 2025 21:35:14 +0000 Subject: [PATCH 16/69] fix more stuff --- crates/bevy_scene/src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 87bb8a7e648dc..6f95e46151676 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -53,7 +53,9 @@ use { bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs, bevy_ecs::{ - entity_disabling::DefaultQueryFilters, resource::IsResource, resource::ResourceEntity, + entity_disabling::{DefaultQueryFilters, Internal}, + resource::IsResource, + resource::ResourceEntity, }, }; @@ -71,6 +73,7 @@ impl Plugin for ScenePlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::>() .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); @@ -130,9 +133,7 @@ mod tests { use bevy_ecs::{ component::Component, entity::Entity, - entity_disabling::Internal, hierarchy::{ChildOf, Children}, - query::Allows, reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; @@ -313,11 +314,7 @@ mod tests { scene .world .insert_resource(world.resource::().clone()); - let entities: Vec = scene - .world - .query_filtered::>() - .iter(&scene.world) - .collect(); + let entities: Vec = scene.world.query::().iter(&scene.world).collect(); DynamicSceneBuilder::from_world(&scene.world) .extract_entities(entities.into_iter()) .build() From 4a2416d4600d04b973fead62a3df993433e7e72c Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 24 Jul 2025 08:21:55 +0000 Subject: [PATCH 17/69] update migration guides --- .../migration-guides/internal_entities.md | 5 +++-- .../migration-guides/resources_as_components.md | 17 ++++++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/release-content/migration-guides/internal_entities.md b/release-content/migration-guides/internal_entities.md index e57d4ba682959..20b18fb63dce6 100644 --- a/release-content/migration-guides/internal_entities.md +++ b/release-content/migration-guides/internal_entities.md @@ -1,10 +1,11 @@ --- title: Internal Entities -pull_requests: [20204] +pull_requests: [20204, 19711] --- Bevy 0.17 introduces internal entities. Entities tagged by the `Internal` component that are hidden from most queries using [`DefaultQueryFilters`](https://docs.rs/bevy/latest/bevy/ecs/entity_disabling/index.html). -Currently, both [`Observer`s](https://docs.rs/bevy/latest/bevy/ecs/observer/struct.Observer.html) and systems that are registered through [`World::register_system`](https://docs.rs/bevy/latest/bevy/prelude/struct.World.html#method.register_system) are considered internal entities. +Currently, [`Observer`s](https://docs.rs/bevy/latest/bevy/ecs/observer/struct.Observer.html) and systems that are registered through [`World::register_system`](https://docs.rs/bevy/latest/bevy/prelude/struct.World.html#method.register_system) are considered internal entities. +The resource entities part of the resources-as-components worked are also marked internal. If you queried them before, add the `Allows` filter to the query to bypass the default filter. diff --git a/release-content/migration-guides/resources_as_components.md b/release-content/migration-guides/resources_as_components.md index 919dbfbaf7b4a..8e0b528bedd15 100644 --- a/release-content/migration-guides/resources_as_components.md +++ b/release-content/migration-guides/resources_as_components.md @@ -4,7 +4,7 @@ pull_requests: [19711] --- Resources are very similar to Components: they are both data that can be stored in the ECS and queried. -The only real difference between them is that querying a resource will return either one or zero resources, whereas querying for a component can return any number of entities that match it. +The only real difference between them is that querying a resource will return either one or zero resources, whereas querying for a component can return any number of matching entities. Even so, resources and components have always been separate concepts within the ECS. This leads to some annoying restrictions. @@ -13,13 +13,16 @@ Moreover, the engine internals contain a lot of duplication because of it. This motivates us to transition resources to components, and while most of the public API will stay the same, some breaking changes are inevitable. -This PR adds a dummy entity alongside every resource. This entity is inserted and removed alongside resources and doesn't do anything (yet). +This PR adds a dummy entity alongside every resource. +This entity is inserted and removed alongside resources and doesn't do anything (yet). -This changes `World::entities().len()` as there are more entities than you might expect there to be. For example, a new world, no longer contains zero entities. This is mostly important for unit tests. +This changes `World::entities().len()` as there are more entities than you might expect there to be. +For example, a new world, no longer contains zero entities. +This is mostly important for unit tests. +If there is any place you are currently using `world.entities().len()`, we recommened you instead use a query `world.query().query(&world).count()`. -Two methods have been added `World::entity_count()` and `World::resource_count()`. The former returns the number of entities without the resource entities, while the latter returns the number of resources in the world. - -While the marker component `IsResource` is added to [default query filters](https://docs.rs/bevy/latest/bevy/ecs/entity_disabling/struct.DefaultQueryFilters.html), during world creation, resource entities might still show up in broad queries with [`EntityMutExcept`](https://docs.rs/bevy/latest/bevy/ecs/world/struct.EntityMutExcept.html) and [`EntityRefExcept`](https://docs.rs/bevy/latest/bevy/ecs/world/struct.EntityRefExcept.html). -They also show up in `World::iter_entities()`, `World::iter_entities_mut()` and [`QueryBuilder`](https://docs.rs/bevy/latest/bevy/ecs/prelude/struct.QueryBuilder.html). +Meanwhile, resource entities are also tagged with `IsResource` and `Internal` marker components. +For more information, checkout the migration guide for internal entities. +In summary, internal entities are added to [default query filters](https://docs.rs/bevy/latest/bevy/ecs/entity_disabling/struct.DefaultQueryFilters.html), and will not show up in most queries. Lastly, because of the entity bump, the input and output of the `bevy_scene` crate is not equivalent to the previous version, meaning that it's unadvisable to read in scenes from the previous version into the current one. From 3942c56ebe2051e7a898cdcab8e88f82c64aebc8 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 24 Jul 2025 08:26:59 +0000 Subject: [PATCH 18/69] spelling --- release-content/migration-guides/resources_as_components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/release-content/migration-guides/resources_as_components.md b/release-content/migration-guides/resources_as_components.md index 8e0b528bedd15..32895e04881bc 100644 --- a/release-content/migration-guides/resources_as_components.md +++ b/release-content/migration-guides/resources_as_components.md @@ -19,7 +19,7 @@ This entity is inserted and removed alongside resources and doesn't do anything This changes `World::entities().len()` as there are more entities than you might expect there to be. For example, a new world, no longer contains zero entities. This is mostly important for unit tests. -If there is any place you are currently using `world.entities().len()`, we recommened you instead use a query `world.query().query(&world).count()`. +If there is any place you are currently using `world.entities().len()`, we recommend you instead use a query `world.query().query(&world).count()`. Meanwhile, resource entities are also tagged with `IsResource` and `Internal` marker components. For more information, checkout the migration guide for internal entities. From 511e0b9125fe97d85c02e6121342eec8219ffad4 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 24 Jul 2025 08:49:04 +0000 Subject: [PATCH 19/69] update resource macros to implement Component --- crates/bevy_ecs/macros/src/component.rs | 14 ++++++++++++-- crates/bevy_ecs/src/resource.rs | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index ce084474a2700..c1131495514ee 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -99,6 +99,11 @@ pub fn derive_buffered_event(input: TokenStream) -> TokenStream { } pub fn derive_resource(input: TokenStream) -> TokenStream { + // The resource derive *also* implements the Component trait + // We generate the Component implementation first, then add the Resource implementation, + // so then we can pick up all of the attributes from the Component derive + let component_derive_token_stream = derive_component(input.clone()); + let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); @@ -110,10 +115,15 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - TokenStream::from(quote! { + let resource_impl_token_stream = TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause { } - }) + }); + + resource_impl_token_stream + .into_iter() + .chain(component_derive_token_stream.into_iter()) + .collect() } /// Component derive syntax is documented on both the macro and the trait. diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index a8e63565bfcdb..7cc185d62b3d4 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -78,7 +78,7 @@ pub use bevy_ecs_macros::Resource; label = "invalid `Resource`", note = "consider annotating `{Self}` with `#[derive(Resource)]`" )] -pub trait Resource: Send + Sync + 'static {} +pub trait Resource: Component {} /// A marker component for the entity that stores the resource of type `T`. /// From d4c98eb13509d7454ca6fc9f8c1bf1ce01e7482d Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 24 Jul 2025 08:55:45 +0000 Subject: [PATCH 20/69] fix tests --- crates/bevy_ecs/src/lib.rs | 2 +- crates/bevy_ecs/src/schedule/mod.rs | 2 +- crates/bevy_ecs/src/system/commands/mod.rs | 2 +- crates/bevy_ecs/src/system/mod.rs | 12 ++++++------ crates/bevy_ecs/src/system/system.rs | 5 ++--- crates/bevy_ecs/src/system/system_registry.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 2 +- crates/bevy_light/src/ambient_light.rs | 2 +- 8 files changed, 14 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index d11b9fc502c96..215c9aae3ac90 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -183,7 +183,7 @@ mod tests { }; use std::sync::Mutex; - #[derive(Component, Resource, Debug, PartialEq, Eq, Hash, Clone, Copy)] + #[derive(Resource, Debug, PartialEq, Eq, Hash, Clone, Copy)] struct A(usize); #[derive(Component, Debug, PartialEq, Eq, Hash, Clone, Copy)] struct B(usize); diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index 2d3c64ddb0136..6ced9337021fa 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -786,7 +786,7 @@ mod tests { #[derive(BufferedEvent)] struct E; - #[derive(Resource, Component)] + #[derive(Resource)] struct RC; fn empty_system() {} diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d786cdb697f3b..e6af57c78448c 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -2423,7 +2423,7 @@ mod tests { } } - #[derive(Component, Resource)] + #[derive(Resource)] struct W(T); fn simple_command(world: &mut World) { diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 26c767b0514df..c058c7d54ec6d 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -432,17 +432,17 @@ mod tests { No, } - #[derive(Component, Resource, Debug, Eq, PartialEq, Default)] + #[derive(Resource, Debug, Eq, PartialEq, Default)] struct A; - #[derive(Component, Resource)] + #[derive(Resource)] struct B; - #[derive(Component, Resource)] + #[derive(Resource)] struct C; - #[derive(Component, Resource)] + #[derive(Resource)] struct D; - #[derive(Component, Resource)] + #[derive(Resource)] struct E; - #[derive(Component, Resource)] + #[derive(Resource)] struct F; #[derive(Component, Debug)] diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 1f2a3ab608fba..564057dda1aba 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -461,10 +461,9 @@ mod tests { #[test] fn run_system_once() { + #[derive(Resource)] struct T(usize); - impl Resource for T {} - fn system(In(n): In, mut commands: Commands) -> usize { commands.insert_resource(T(n)); n + 1 @@ -524,8 +523,8 @@ mod tests { #[test] fn run_system_once_invalid_params() { + #[derive(Resource)] struct T; - impl Resource for T {} fn system(_: Res) {} let mut world = World::default(); diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index cb726a1d358d7..6c2ca804ee70a 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -941,8 +941,8 @@ mod tests { use crate::system::RegisteredSystemError; use alloc::string::ToString; + #[derive(Resource)] struct T; - impl Resource for T {} fn system(_: Res) {} let mut world = World::new(); diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index f3f32f1e61e3a..34c970998116e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3744,7 +3744,7 @@ mod tests { Drop(ID), } - #[derive(Resource, Component)] + #[derive(Resource)] struct MayPanicInDrop { drop_log: Arc>>, expected_panic_flag: Arc, diff --git a/crates/bevy_light/src/ambient_light.rs b/crates/bevy_light/src/ambient_light.rs index 92935e7e06d7a..06422c8b63499 100644 --- a/crates/bevy_light/src/ambient_light.rs +++ b/crates/bevy_light/src/ambient_light.rs @@ -22,7 +22,7 @@ use bevy_reflect::prelude::*; /// ``` /// /// [`LightPlugin`]: crate::LightPlugin -#[derive(Resource, Component, Clone, Debug, Reflect)] +#[derive(Resource, Clone, Debug, Reflect)] #[reflect(Resource, Component, Debug, Default, Clone)] #[require(Camera)] pub struct AmbientLight { From f442ff69e4c69e5e283dfa27f14dcca9b458c579 Mon Sep 17 00:00:00 2001 From: Bird! Date: Fri, 22 Aug 2025 21:06:42 +0200 Subject: [PATCH 21/69] fix imports --- crates/bevy_scene/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 015bb5b68d46f..456a5105df20c 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -133,7 +133,9 @@ mod tests { use bevy_ecs::{ component::Component, entity::Entity, + entity_disabling::Internal, hierarchy::{ChildOf, Children}, + prelude::Allow, reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; From acb539e00202b1a0e0d65df3ea408c27c11ad188 Mon Sep 17 00:00:00 2001 From: Bird! Date: Fri, 22 Aug 2025 21:21:45 +0200 Subject: [PATCH 22/69] second attempt at fixing a test --- crates/bevy_scene/src/lib.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 456a5105df20c..af0037ff4c708 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -133,9 +133,7 @@ mod tests { use bevy_ecs::{ component::Component, entity::Entity, - entity_disabling::Internal, hierarchy::{ChildOf, Children}, - prelude::Allow, reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; @@ -322,11 +320,7 @@ mod tests { scene .world .insert_resource(world.resource::().clone()); - let entities: Vec = scene - .world - .query_filtered::>() - .iter(&scene.world) - .collect(); + let entities: Vec = scene.world.query::().iter(&scene.world).collect(); DynamicSceneBuilder::from_world(&scene.world) .extract_entities(entities.into_iter()) .build() From f43905dcc35fde0f1c3e6c664e81dedd8589eab9 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 8 Sep 2025 17:27:49 +0200 Subject: [PATCH 23/69] removed send resources from Storages --- crates/bevy_ecs/src/component/info.rs | 7 +- crates/bevy_ecs/src/component/register.rs | 2 +- crates/bevy_ecs/src/query/access.rs | 2 +- crates/bevy_ecs/src/storage/mod.rs | 2 - crates/bevy_ecs/src/system/system_param.rs | 12 +- crates/bevy_ecs/src/world/mod.rs | 250 ++++++------------ .../bevy_ecs/src/world/unsafe_world_cell.rs | 44 +-- 7 files changed, 102 insertions(+), 217 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 99fcd419ed2c0..465beedbd8b80 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -349,7 +349,6 @@ impl ComponentDescriptor { pub struct Components { pub(super) components: Vec>, pub(super) indices: TypeIdMap, - pub(super) resource_indices: TypeIdMap, /// A lookup for the entities on which resources are stored. /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs pub(crate) resource_entities: HashMap, @@ -592,7 +591,7 @@ impl Components { /// Type-erased equivalent of [`Components::valid_resource_id()`]. #[inline] pub fn get_valid_resource_id(&self, type_id: TypeId) -> Option { - self.resource_indices.get(&type_id).copied() + self.indices.get(&type_id).copied() } /// Returns the [`ComponentId`] of the given [`Resource`] type `T` if it is fully registered. @@ -670,7 +669,7 @@ impl Components { /// Type-erased equivalent of [`Components::resource_id()`]. #[inline] pub fn get_resource_id(&self, type_id: TypeId) -> Option { - self.resource_indices.get(&type_id).copied().or_else(|| { + self.indices.get(&type_id).copied().or_else(|| { self.queued .read() .unwrap_or_else(PoisonError::into_inner) @@ -728,7 +727,7 @@ impl Components { unsafe { self.register_component_inner(component_id, descriptor); } - let prev = self.resource_indices.insert(type_id, component_id); + let prev = self.indices.insert(type_id, component_id); debug_assert!(prev.is_none()); } diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 5d8ac3ee6e98b..a35f8e223546d 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -321,7 +321,7 @@ impl<'w> ComponentsRegistrator<'w> { type_id: TypeId, descriptor: impl FnOnce() -> ComponentDescriptor, ) -> ComponentId { - if let Some(id) = self.resource_indices.get(&type_id) { + if let Some(id) = self.indices.get(&type_id) { return *id; } diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index 6a0e77a632c03..c3eb63744397d 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1383,7 +1383,7 @@ impl FilteredAccessSet { /// Adds a read access to a resource to the set. pub fn add_unfiltered_resource_read(&mut self, index: ComponentId) { let mut filter = FilteredAccess::default(); - filter.add_resource_read(index); + filter.add_component_read(index); self.add(filter); } diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 2a5a5f184e649..c0ddaed9f51ef 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -41,8 +41,6 @@ pub struct Storages { pub sparse_sets: SparseSets, /// Backing storage for [`Table`] components. pub tables: Tables, - /// Backing storage for resources. - pub resources: Resources, /// Backing storage for `!Send` resources. pub non_send_resources: Resources, } diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index d9adab6fe34b7..b1883b49d7dd1 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -786,11 +786,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to resource metadata. - if unsafe { world.storages() } - .resources - .get(component_id) - .is_some_and(ResourceData::is_present) - { + if world.world().contains_resource_by_id(component_id) { Ok(()) } else { Err(SystemParamValidationError::invalid::( @@ -865,11 +861,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to resource metadata. - if unsafe { world.storages() } - .resources - .get(component_id) - .is_some_and(ResourceData::is_present) - { + if world.world().contains_resource_by_id(component_id) { Ok(()) } else { Err(SystemParamValidationError::invalid::( diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 137aa7133649f..c19f255649ce0 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1702,26 +1702,15 @@ impl World { .resource_entities .contains_key(&component_id) { - let entity = self.spawn(ResourceEntity::::default()).id(); + let value = R::from_world(self); + let entity = self + .spawn_with_caller((value, ResourceEntity::::default()), caller) + .id(); self.components .resource_entities .insert(component_id, entity); } - if self - .storages - .resources - .get(component_id) - .is_none_or(|data| !data.is_present()) - { - let value = R::from_world(self); - OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. - unsafe { - self.insert_resource_by_id(component_id, ptr, caller); - } - }); - } component_id } @@ -1750,18 +1739,13 @@ impl World { .resource_entities .contains_key(&component_id) { - let entity = self.spawn(ResourceEntity::::default()).id(); + let entity = self + .spawn_with_caller((value, ResourceEntity::::default()), caller) + .id(); self.components .resource_entities .insert(component_id, entity); } - - OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. - unsafe { - self.insert_resource_by_id(component_id, ptr, caller); - } - }); } /// Initializes a new non-send resource and returns the [`ComponentId`] created for it. @@ -1823,13 +1807,12 @@ impl World { #[inline] pub fn remove_resource(&mut self) -> Option { let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - if let Some(entity) = self.components.resource_entities.remove(&component_id) { - self.despawn(entity); - } - - let (ptr, _, _) = self.storages.resources.get_mut(component_id)?.remove()?; - // SAFETY: `component_id` was gotten via looking up the `R` type - unsafe { Some(ptr.read::()) } + let entity = self.components.resource_entities.remove(&component_id)?; + let mut entity_ref = self.get_entity_mut(entity).ok()?; + let value = entity_ref.take::()?; + entity_ref.despawn(); + self.components.resource_entities.remove(&component_id); + return Some(value); } /// Removes a `!Send` resource from the world and returns it, if present. @@ -1860,17 +1843,18 @@ impl World { pub fn contains_resource(&self) -> bool { self.components .get_valid_resource_id(TypeId::of::()) - .and_then(|component_id| self.storages.resources.get(component_id)) - .is_some_and(ResourceData::is_present) + .is_some_and(|component_id| self.contains_resource_by_id(component_id)) } /// Returns `true` if a resource with provided `component_id` exists. Otherwise returns `false`. #[inline] pub fn contains_resource_by_id(&self, component_id: ComponentId) -> bool { - self.storages - .resources - .get(component_id) - .is_some_and(ResourceData::is_present) + if let Some(entity) = self.components.resource_entities.get(&component_id) + && let Ok(entity_ref) = self.get_entity(*entity) + { + return entity_ref.contains_id(component_id); + } + return false; } /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. @@ -1912,14 +1896,8 @@ impl World { /// - When called elsewhere, this will check for additions since the last time that [`World::clear_trackers`] /// was called. pub fn is_resource_added_by_id(&self, component_id: ComponentId) -> bool { - self.storages - .resources - .get(component_id) - .is_some_and(|resource| { - resource.get_ticks().is_some_and(|ticks| { - ticks.is_added(self.last_change_tick(), self.read_change_tick()) - }) - }) + self.get_resource_change_ticks_by_id(component_id) + .is_some_and(|ticks| ticks.is_added(self.last_change_tick(), self.read_change_tick())) } /// Returns `true` if a resource of type `R` exists and was modified since the world's @@ -1943,14 +1921,8 @@ impl World { /// - When called elsewhere, this will check for changes since the last time that [`World::clear_trackers`] /// was called. pub fn is_resource_changed_by_id(&self, component_id: ComponentId) -> bool { - self.storages - .resources - .get(component_id) - .is_some_and(|resource| { - resource.get_ticks().is_some_and(|ticks| { - ticks.is_changed(self.last_change_tick(), self.read_change_tick()) - }) - }) + self.get_resource_change_ticks_by_id(component_id) + .is_some_and(|ticks| ticks.is_changed(self.last_change_tick(), self.read_change_tick())) } /// Retrieves the change ticks for the given resource. @@ -1967,10 +1939,9 @@ impl World { &self, component_id: ComponentId, ) -> Option { - self.storages - .resources - .get(component_id) - .and_then(ResourceData::get_ticks) + let entity = self.components.resource_entities.get(&component_id)?; + let entity_ref = self.get_entity(*entity).ok()?; + entity_ref.get_change_ticks_by_id(component_id) } /// Gets a reference to the resource of the given type @@ -2094,27 +2065,23 @@ impl World { func: impl FnOnce() -> R, ) -> Mut<'_, R> { let caller = MaybeLocation::caller(); - let change_tick = self.change_tick(); - let last_change_tick = self.last_change_tick(); - let component_id = self.components_registrator().register_resource::(); - let data = self.initialize_resource_internal(component_id); - if !data.is_present() { - OwningPtr::make(func(), |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. - unsafe { - data.insert(ptr, change_tick, caller); + + if !self.contains_resource_by_id(component_id) { + let value = func(); + if let Some(entity) = self.components.resource_entities.get(&component_id) { + if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { + entity_mut.insert(value); + } else { + self.components.resource_entities.remove(&component_id); + self.insert_resource_with_caller(value, caller); } - }); + } else { + self.insert_resource_with_caller(value, caller); + } } - - // SAFETY: The resource must be present, as we would have inserted it if it was empty. - let data = unsafe { - data.get_mut(last_change_tick, change_tick) - .debug_checked_unwrap() - }; - // SAFETY: The underlying type of the resource is `R`. - unsafe { data.with_type::() } + // SAFETY: If it didn't exist, we've just created the resource. + return unsafe { self.get_resource_mut::().debug_checked_unwrap() }; } /// Gets a mutable reference to the resource of type `T` if it exists, @@ -2152,39 +2119,13 @@ impl World { #[track_caller] pub fn get_resource_or_init(&mut self) -> Mut<'_, R> { let caller = MaybeLocation::caller(); - let change_tick = self.change_tick(); - let last_change_tick = self.last_change_tick(); - let component_id = self.components_registrator().register_resource::(); - if self - .storages - .resources - .get(component_id) - .is_none_or(|data| !data.is_present()) - { + if !self.contains_resource::() { let value = R::from_world(self); - OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. - unsafe { - self.insert_resource_by_id(component_id, ptr, caller); - } - }); + self.insert_resource_with_caller(value, caller); } - - // SAFETY: The resource was just initialized if it was empty. - let data = unsafe { - self.storages - .resources - .get_mut(component_id) - .debug_checked_unwrap() - }; - // SAFETY: The resource must be present, as we would have inserted it if it was empty. - let data = unsafe { - data.get_mut(last_change_tick, change_tick) - .debug_checked_unwrap() - }; - // SAFETY: The underlying type of the resource is `R`. - unsafe { data.with_type::() } + // SAFETY: The resouce either exists or we've just created it. + return unsafe { self.get_resource_mut::().debug_checked_unwrap() }; } /// Gets an immutable reference to the non-send resource of the given type, if it exists. @@ -2620,16 +2561,14 @@ impl World { ) -> Option { let last_change_tick = self.last_change_tick(); let change_tick = self.change_tick(); + let mut caller = MaybeLocation::caller(); // TODO: Fix up caller location. let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let (ptr, mut ticks, mut caller) = self - .storages - .resources - .get_mut(component_id) - .and_then(ResourceData::remove)?; - // Read the value onto the stack to avoid potential mut aliasing. - // SAFETY: `ptr` was obtained from the TypeId of `R`. - let mut value = unsafe { ptr.read::() }; + let entity = self.components.resource_entities.get(&component_id)?; + let mut entity_mut = self.get_entity_mut(*entity).ok()?; + let mut ticks = entity_mut.get_change_ticks_by_id(component_id)?; + let mut value = entity_mut.take::()?; + let value_mut = Mut { value: &mut value, ticks: TicksMut { @@ -2640,20 +2579,15 @@ impl World { }, changed_by: caller.as_mut(), }; - let result = f(self, value_mut); - assert!(!self.contains_resource::(), + + let result = f(unsafe { entity_mut.world_mut() }, value_mut); + assert!(!unsafe { entity_mut.world_mut() }.contains_resource::(), "Resource `{}` was inserted during a call to World::resource_scope.\n\ This is not allowed as the original resource is reinserted to the world after the closure is invoked.", DebugName::type_name::()); - OwningPtr::make(value, |ptr| { - // SAFETY: pointer is of type R - unsafe { - self.storages.resources.get_mut(component_id).map(|info| { - info.insert_with_ticks(ptr, ticks, caller); - }) - } - })?; + // TODO: insert with ticks of value_mut and maybe also caller + entity_mut.insert(value); Some(result) } @@ -2737,8 +2671,6 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { - let change_tick = self.change_tick(); - if !self .components .resource_entities @@ -2746,18 +2678,13 @@ impl World { { // Since we don't know the type, we use a placeholder type. let entity = self - .spawn(ResourceEntity::::default()) + .spawn_with_caller(ResourceEntity::::default(), caller) + .insert_by_id(component_id, value) .id(); self.components .resource_entities .insert(component_id, entity); } - - let resource = self.initialize_resource_internal(component_id); - // SAFETY: `value` is valid for `component_id`, ensured by caller - unsafe { - resource.insert(value, change_tick, caller); - } } /// Inserts a new `!Send` resource with the given `value`. Will replace the value if it already @@ -2789,19 +2716,6 @@ impl World { } } - /// # Panics - /// Panics if `component_id` is not registered as a `Send` component type in this `World` - #[inline] - pub(crate) fn initialize_resource_internal( - &mut self, - component_id: ComponentId, - ) -> &mut ResourceData { - self.flush_components(); - self.storages - .resources - .initialize_with(component_id, &self.components) - } - /// # Panics /// Panics if `component_id` is not registered in this world #[inline] @@ -3059,7 +2973,6 @@ impl World { let Storages { ref mut tables, ref mut sparse_sets, - ref mut resources, ref mut non_send_resources, } = self.storages; @@ -3067,7 +2980,6 @@ impl World { let _span = tracing::info_span!("check component ticks").entered(); tables.check_change_ticks(check); sparse_sets.check_change_ticks(check); - resources.check_change_ticks(check); non_send_resources.check_change_ticks(check); self.entities.check_change_ticks(check); @@ -3106,7 +3018,16 @@ impl World { /// This can easily cause systems expecting certain resources to immediately start panicking. /// Use with caution. pub fn clear_resources(&mut self) { - self.storages.resources.clear(); + let resource_entities: Vec = self + .components + .resource_entities + .values() + .copied() + .collect(); + for entity in resource_entities { + self.despawn(entity); + } + self.components.resource_entities.clear(); self.storages.non_send_resources.clear(); } @@ -3284,18 +3205,13 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { - self.storages - .resources - .iter() - .filter_map(|(component_id, data)| { - // SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with its ID. - let component_info = unsafe { - self.components - .get_info(component_id) - .debug_checked_unwrap() - }; - Some((component_info, data.get_data()?)) - }) + let component_ids: Vec = + self.components.resource_entities.keys().copied().collect(); + component_ids.into_iter().filter_map(|component_id| { + let component_info = self.components().get_info(component_id)?; + let resource = self.get_resource_by_id(component_id)?; + Some((component_info, resource)) + }) } /// Mutably iterates over all resources in the world. @@ -3363,6 +3279,7 @@ impl World { /// # assert_eq!(world.resource::().0, 2); /// # assert_eq!(world.resource::().0, 3); /// ``` + /* #[inline] pub fn iter_resources_mut(&mut self) -> impl Iterator)> { self.storages @@ -3403,6 +3320,7 @@ impl World { Some((component_info, mut_untyped)) }) } + */ /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. /// The returned pointer must not be used to modify the resource, and must not be @@ -3450,14 +3368,10 @@ impl World { /// use this in cases where the actual types are not known at compile time.** pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { if let Some(entity) = self.components.resource_entities.remove(&component_id) { - self.despawn(entity); + self.despawn(entity).then_some::<()>(()) + } else { + None } - - self.storages - .resources - .get_mut(component_id)? - .remove_and_drop(); - Some(()) } /// Removes the resource of a given type, if it exists. Otherwise returns `None`. @@ -3658,7 +3572,7 @@ impl fmt::Debug for World { .field("entity_count", &self.entities.len()) .field("archetype_count", &self.archetypes.len()) .field("component_count", &self.components.len()) - .field("resource_count", &self.storages.resources.len()) + .field("resource_count", &self.components.resource_entities.len()) .finish() } } @@ -3937,6 +3851,7 @@ mod tests { assert!(iter.next().is_none()); } + /* #[test] fn iter_resources_mut() { let mut world = World::new(); @@ -3972,6 +3887,7 @@ mod tests { "Hello, world?".to_string() ); } + */ #[test] fn dynamic_resource() { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 2934c233f158b..1f09bfac14e36 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -17,7 +17,7 @@ use crate::{ world::RawCommandQueue, }; use bevy_platform::sync::atomic::Ordering; -use bevy_ptr::{Ptr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, panic::Location, ptr}; use thiserror::Error; @@ -455,12 +455,11 @@ impl<'w> UnsafeWorldCell<'w> { /// - no mutable reference to the resource exists at the same time #[inline] pub unsafe fn get_resource_by_id(self, component_id: ComponentId) -> Option> { + let entity = self.components().resource_entities.get(&component_id)?; + let entity_cell = self.get_entity(*entity).ok()?; // SAFETY: caller ensures that `self` has permission to access `R` // caller ensures that no mutable reference exists to `R` - unsafe { self.storages() } - .resources - .get(component_id)? - .get_data() + entity_cell.get_by_id(component_id) } /// Gets a reference to the non-send resource of the given type if it exists @@ -542,31 +541,11 @@ impl<'w> UnsafeWorldCell<'w> { component_id: ComponentId, ) -> Option> { self.assert_allows_mutable_access(); + let entity = self.components().resource_entities.get(&component_id)?; + let entity_cell = self.get_entity(*entity).ok()?; // SAFETY: we only access data that the caller has ensured is unaliased and `self` // has permission to access. - let (ptr, ticks, caller) = unsafe { self.storages() } - .resources - .get(component_id)? - .get_with_ticks()?; - - // SAFETY: - // - index is in-bounds because the column is initialized and non-empty - // - the caller promises that no other reference to the ticks of the same row can exist at the same time - let ticks = unsafe { - TicksMut::from_tick_cells(ticks, self.last_change_tick(), self.change_tick()) - }; - - Some(MutUntyped { - // SAFETY: - // - caller ensures that `self` has permission to access the resource - // - caller ensures that the resource is unaliased - value: unsafe { ptr.assert_unique() }, - ticks, - // SAFETY: - // - caller ensures that `self` has permission to access the resource - // - caller ensures that the resource is unaliased - changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, - }) + return entity_cell.get_mut_by_id(component_id).ok(); } /// Gets a mutable reference to the non-send resource of the given type if it exists @@ -646,14 +625,15 @@ impl<'w> UnsafeWorldCell<'w> { TickCells<'w>, MaybeLocation<&'w UnsafeCell<&'static Location<'static>>>, )> { + let entity = self.components().resource_entities.get(&component_id)?; + let storage_type = self.components().get_info(component_id)?.storage_type(); + let location = self.get_entity(*entity).ok()?.location(); // SAFETY: // - caller ensures there is no `&mut World` // - caller ensures there are no mutable borrows of this resource // - caller ensures that we have permission to access this resource - unsafe { self.storages() } - .resources - .get(component_id)? - .get_with_ticks() + // - storage_type and location are valid + get_component_and_ticks(self, component_id, storage_type, *entity, location) } // Shorthand helper function for getting the data and change ticks for a resource. From 36e819138f302aa093a91e1342ce12e475aa57a7 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 8 Sep 2025 23:31:59 +0200 Subject: [PATCH 24/69] fixed more tests --- crates/bevy_ecs/src/query/access.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 32 ++++++++++++++++++++++++----- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/query/access.rs b/crates/bevy_ecs/src/query/access.rs index c3eb63744397d..6a0e77a632c03 100644 --- a/crates/bevy_ecs/src/query/access.rs +++ b/crates/bevy_ecs/src/query/access.rs @@ -1383,7 +1383,7 @@ impl FilteredAccessSet { /// Adds a read access to a resource to the set. pub fn add_unfiltered_resource_read(&mut self, index: ComponentId) { let mut filter = FilteredAccess::default(); - filter.add_component_read(index); + filter.add_resource_read(index); self.add(filter); } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c19f255649ce0..c68204b7124ef 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -18,10 +18,11 @@ pub use crate::{ world::command_queue::CommandQueue, }; use crate::{ + entity_disabling::Internal, error::{DefaultErrorHandler, ErrorHandler}, event::BufferedEvent, lifecycle::{ComponentHooks, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, - prelude::{Add, Despawn, Insert, Remove, Replace}, + prelude::{Add, Despawn, Insert, Remove, Replace, Without}, resource::{ResourceEntity, TypeErasedResource}, }; pub use bevy_ecs_macros::FromWorld; @@ -1734,6 +1735,7 @@ impl World { caller: MaybeLocation, ) { let component_id = self.components_registrator().register_resource::(); + if !self .components .resource_entities @@ -1745,6 +1747,20 @@ impl World { self.components .resource_entities .insert(component_id, entity); + } else { + let entity = self + .components + .resource_entities + .get(&component_id) + .unwrap(); + if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { + entity_mut.insert_with_caller( + value, + InsertMode::Replace, + caller, + RelationshipHookMode::Run, + ); + } } } @@ -3004,10 +3020,16 @@ impl World { /// Despawns all entities in this [`World`]. pub fn clear_entities(&mut self) { - self.storages.tables.clear(); - self.storages.sparse_sets.clear_entities(); - self.archetypes.clear_entities(); - self.entities.clear(); + self.resource_scope::(|world: &mut World, _| { + let to_remove: Vec = world + .query_filtered::>() + .query(&world) + .into_iter() + .collect(); + for entity in to_remove { + world.despawn(entity); + } + }) } /// Clears all resources in this [`World`]. From 011b06842c68321007dc30680224e56f8e1ff29a Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 9 Sep 2025 01:09:47 +0200 Subject: [PATCH 25/69] cleanup --- crates/bevy_ecs/macros/src/component.rs | 2 +- crates/bevy_ecs/src/storage/resource.rs | 8 +++++ crates/bevy_ecs/src/world/mod.rs | 29 +++++++++++-------- .../bevy_ecs/src/world/unsafe_world_cell.rs | 4 +-- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index dd2beee7b92fa..8d34029331e6a 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -122,7 +122,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { resource_impl_token_stream .into_iter() - .chain(component_derive_token_stream.into_iter()) + .chain(component_derive_token_stream) .collect() } diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index 1a90cc511ccc0..0f19a566c13b6 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/resource.rs @@ -148,6 +148,10 @@ impl ResourceData { /// # Panics /// If `SEND` is false, this will panic if a value is present and is not accessed from the /// original thread it was inserted in. + #[expect( + dead_code, + reason = "To be removed later as part of resource-as-component efforts" + )] pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option> { let (ptr, ticks, caller) = self.get_with_ticks()?; Some(MutUntyped { @@ -210,6 +214,10 @@ impl ResourceData { /// # Safety /// - `value` must be valid for the underlying type for the resource. #[inline] + #[expect( + dead_code, + reason = "To be removed later as part of resource-as-component efforts" + )] pub(crate) unsafe fn insert_with_ticks( &mut self, value: OwningPtr<'_>, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c68204b7124ef..b17aaa40a4bde 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -70,7 +70,7 @@ use crate::{ }; use alloc::{boxed::Box, vec::Vec}; use bevy_platform::sync::atomic::{AtomicU32, Ordering}; -use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{OwningPtr, Ptr}; use core::{any::TypeId, fmt}; use log::warn; use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; @@ -1828,7 +1828,7 @@ impl World { let value = entity_ref.take::()?; entity_ref.despawn(); self.components.resource_entities.remove(&component_id); - return Some(value); + Some(value) } /// Removes a `!Send` resource from the world and returns it, if present. @@ -1870,7 +1870,7 @@ impl World { { return entity_ref.contains_id(component_id); } - return false; + false } /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. @@ -2097,7 +2097,7 @@ impl World { } } // SAFETY: If it didn't exist, we've just created the resource. - return unsafe { self.get_resource_mut::().debug_checked_unwrap() }; + unsafe { self.get_resource_mut::().debug_checked_unwrap() } } /// Gets a mutable reference to the resource of type `T` if it exists, @@ -2141,7 +2141,7 @@ impl World { self.insert_resource_with_caller(value, caller); } // SAFETY: The resouce either exists or we've just created it. - return unsafe { self.get_resource_mut::().debug_checked_unwrap() }; + unsafe { self.get_resource_mut::().debug_checked_unwrap() } } /// Gets an immutable reference to the non-send resource of the given type, if it exists. @@ -2596,8 +2596,11 @@ impl World { changed_by: caller.as_mut(), }; - let result = f(unsafe { entity_mut.world_mut() }, value_mut); - assert!(!unsafe { entity_mut.world_mut() }.contains_resource::(), + // SAFETY: We 'assume' that the function doesn't move the resource entity around. + let world = unsafe { entity_mut.world_mut() }; + + let result = f(world, value_mut); + assert!(!world.contains_resource::(), "Resource `{}` was inserted during a call to World::resource_scope.\n\ This is not allowed as the original resource is reinserted to the world after the closure is invoked.", DebugName::type_name::()); @@ -3014,8 +3017,10 @@ impl World { /// Runs both [`clear_entities`](Self::clear_entities) and [`clear_resources`](Self::clear_resources), /// invalidating all [`Entity`] and resource fetches such as [`Res`](crate::system::Res), [`ResMut`](crate::system::ResMut) pub fn clear_all(&mut self) { - self.clear_entities(); - self.clear_resources(); + self.storages.tables.clear(); + self.storages.sparse_sets.clear_entities(); + self.archetypes.clear_entities(); + self.entities.clear(); } /// Despawns all entities in this [`World`]. @@ -3023,13 +3028,13 @@ impl World { self.resource_scope::(|world: &mut World, _| { let to_remove: Vec = world .query_filtered::>() - .query(&world) + .query(world) .into_iter() .collect(); for entity in to_remove { world.despawn(entity); } - }) + }); } /// Clears all resources in this [`World`]. @@ -3236,6 +3241,7 @@ impl World { }) } + /* /// Mutably iterates over all resources in the world. /// /// The returned iterator provides lifetimed, but type-unsafe pointers. Actually reading from or writing @@ -3301,7 +3307,6 @@ impl World { /// # assert_eq!(world.resource::().0, 2); /// # assert_eq!(world.resource::().0, 3); /// ``` - /* #[inline] pub fn iter_resources_mut(&mut self) -> impl Iterator)> { self.storages diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 1f09bfac14e36..1b104920f15df 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -17,7 +17,7 @@ use crate::{ world::RawCommandQueue, }; use bevy_platform::sync::atomic::Ordering; -use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{Ptr, UnsafeCellDeref}; use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, panic::Location, ptr}; use thiserror::Error; @@ -545,7 +545,7 @@ impl<'w> UnsafeWorldCell<'w> { let entity_cell = self.get_entity(*entity).ok()?; // SAFETY: we only access data that the caller has ensured is unaliased and `self` // has permission to access. - return entity_cell.get_mut_by_id(component_id).ok(); + entity_cell.get_mut_by_id(component_id).ok() } /// Gets a mutable reference to the non-send resource of the given type if it exists From a12ec78453f5db9a4db040a5d33a1d35d755b941 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Fri, 12 Sep 2025 16:32:55 +0200 Subject: [PATCH 26/69] partly fixed bevy_scene --- crates/bevy_ecs/src/component/info.rs | 2 +- .../bevy_scene/src/dynamic_scene_builder.rs | 27 +++++++------- crates/bevy_scene/src/scene.rs | 36 +++++++++++++++---- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 465beedbd8b80..e83cb56e30094 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -351,7 +351,7 @@ pub struct Components { pub(super) indices: TypeIdMap, /// A lookup for the entities on which resources are stored. /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs - pub(crate) resource_entities: HashMap, + pub resource_entities: HashMap, // This is kept internal and local to verify that no deadlocks can occor. pub(super) queued: bevy_platform::sync::RwLock, } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index ee0b15847a315..d300d9dedd814 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -354,15 +354,15 @@ impl<'w> DynamicSceneBuilder<'w> { let type_registry = self.original_world.resource::().read(); - for (component_id, _) in self.original_world.storages().resources.iter() { - if Some(component_id) == original_world_dqf_id { + for (component_id, entity) in self.original_world.components().resource_entities.iter() { + if Some(*component_id) == original_world_dqf_id { continue; } let mut extract_and_push = || { let type_id = self .original_world .components() - .get_info(component_id)? + .get_info(*component_id)? .type_id()?; let is_denied = self.resource_filter.is_denied_by_id(type_id); @@ -373,16 +373,15 @@ impl<'w> DynamicSceneBuilder<'w> { } let type_registration = type_registry.get(type_id)?; + let entity = self.original_world.get_entity(*entity).ok()?; + let component = type_registration + .data::()? + .reflect(entity)?; - let resource = type_registration - .data::()? - .reflect(self.original_world) - .ok()?; + let component = + clone_reflect_value(component.as_partial_reflect(), type_registration); - let resource = - clone_reflect_value(resource.as_partial_reflect(), type_registration); - - self.extracted_resources.insert(component_id, resource); + self.extracted_resources.insert(*component_id, component); Some(()) }; extract_and_push(); @@ -416,11 +415,11 @@ mod tests { struct ComponentB; #[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)] - #[reflect(Resource)] + #[reflect(Resource, Component)] struct ResourceA; #[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)] - #[reflect(Resource)] + #[reflect(Resource, Component)] struct ResourceB; #[test] @@ -703,7 +702,7 @@ mod tests { #[test] fn should_use_from_reflect() { - #[derive(Resource, Component, Reflect)] + #[derive(Resource, Reflect)] #[reflect(Resource, Component)] struct SomeType(i32); diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 255f688673c86..2b154744e4178 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -71,18 +71,24 @@ impl Scene { .get_resource_id(TypeId::of::()); // Resources archetype - for (component_id, resource_data) in self.world.storages().resources.iter() { - if Some(component_id) == self_dqf_id { + for (component_id, entity) in self.world.components().resource_entities.iter() { + if Some(*component_id) == self_dqf_id { continue; } - if !resource_data.is_present() { + + let entity_ref = self + .world + .get_entity(*entity) + .expect("Resource entity should exist in the world."); + + if !entity_ref.contains_id(*component_id) { continue; } let component_info = self .world .components() - .get_info(component_id) + .get_info(*component_id) .expect("component_ids in archetypes should have ComponentInfo"); let type_id = component_info @@ -95,12 +101,30 @@ impl Scene { .ok_or_else(|| SceneSpawnError::UnregisteredType { std_type_name: component_info.name(), })?; - let reflect_resource = registration.data::().ok_or_else(|| { + let reflect_component = registration.data::().ok_or_else(|| { SceneSpawnError::UnregisteredResource { type_path: registration.type_info().type_path().to_string(), } })?; - reflect_resource.copy(&self.world, world, &type_registry); + + let Some(component) = reflect_component + .reflect(self.world.entity(*entity)) + .map(|component| clone_reflect_value(component.as_partial_reflect(), registration)) + else { + continue; + }; + + // If this component references entities in the scene, + // update them to the entities in the world. + SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { + reflect_component.apply_or_insert_mapped( + &mut world.entity_mut(*entity), + component.as_partial_reflect(), + &type_registry, + mapper, + RelationshipHookMode::Skip, + ); + }); } // Ensure that all scene entities have been allocated in the destination From 7f224be1b8b1284076c45f3964389b3033870278 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 14 Sep 2025 18:58:49 +0200 Subject: [PATCH 27/69] address comments --- crates/bevy_ecs/src/lib.rs | 8 ++++---- crates/bevy_ecs/src/resource.rs | 6 ++++-- crates/bevy_scene/src/serde.rs | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 700e0faf2b113..1db0582b40394 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1553,8 +1553,8 @@ mod tests { let mut world_a = World::new(); let world_b = World::new(); let mut query = world_a.query::<&A>(); - let _ = query.get(&world_a, Entity::from_raw_u32(0).unwrap()); - let _ = query.get(&world_b, Entity::from_raw_u32(0).unwrap()); + let _ = query.get(&world_a, Entity::from_raw_u32(10_000).unwrap()); + let _ = query.get(&world_b, Entity::from_raw_u32(10_000).unwrap()); } #[test] @@ -1794,7 +1794,7 @@ mod tests { fn try_insert_batch() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw_u32(2).unwrap(); + let e1 = Entity::from_raw_u32(10_000).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; @@ -1818,7 +1818,7 @@ mod tests { fn try_insert_batch_if_new() { let mut world = World::default(); let e0 = world.spawn(A(0)).id(); - let e1 = Entity::from_raw_u32(2).unwrap(); + let e1 = Entity::from_raw_u32(10_000).unwrap(); let values = vec![(e0, (A(1), B(0))), (e1, (A(0), B(1)))]; diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index a8e63565bfcdb..c8e9bb3b40757 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -3,6 +3,7 @@ use crate::entity_disabling::Internal; use crate::prelude::Component; use crate::prelude::ReflectComponent; +use crate::prelude::ReflectResource; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use core::marker::PhantomData; @@ -115,8 +116,9 @@ pub struct IsResource; /// Used in conjunction with [`ResourceEntity`], when no type information is available. /// This is used by [`World::insert_resource_by_id`](crate::world::World). -#[derive(Resource)] -pub(crate) struct TypeErasedResource; +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Debug))] +#[derive(Resource, Debug)] +pub struct TypeErasedResource; #[cfg(test)] mod tests { diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index cd16c4b824e8a..0b4c0b23f8e03 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -789,7 +789,7 @@ mod tests { let (scene, deserialized_scene) = roundtrip_ron(&world); - assert_eq!(3, deserialized_scene.entities.len()); + assert_eq!(3, deserialized_scene.entities.len()); // 1 entity and 2 resources assert_scene_eq(&scene, &deserialized_scene); let mut world = create_world(); From 6fdf25ef1bc96960092388f5d3637e5507d519b4 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 14 Sep 2025 23:43:16 +0200 Subject: [PATCH 28/69] fixed try_resource_scope in part --- crates/bevy_ecs/src/world/entity_ref.rs | 17 ++++++ crates/bevy_ecs/src/world/mod.rs | 16 ++++-- .../bevy_ecs/src/world/unsafe_world_cell.rs | 54 +++++++++++++++++++ 3 files changed, 83 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 7820a5018e4ec..d6f91aa22d11c 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -146,6 +146,13 @@ impl<'w> EntityRef<'w> { unsafe { self.cell.get_change_ticks::() } } + /// Retrieves the caller information for the given component. + #[inline] + pub fn get_caller(&self) -> Option { + // SAFETY: We have read-only access to all components of this entity. + unsafe { self.cell.get_caller::() } + } + /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change /// detection in custom runtimes. /// @@ -1742,6 +1749,16 @@ impl<'w> EntityWorldMut<'w> { self.as_readonly().get_change_ticks::() } + /// Retrieves the caller information for the given component. + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[inline] + pub fn get_caller(&self) -> Option { + self.as_readonly().get_caller::() + } + /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change /// detection in custom runtimes. /// diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index d87faac7aff18..ab92d42b06f1d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2593,12 +2593,13 @@ impl World { ) -> Option { let last_change_tick = self.last_change_tick(); let change_tick = self.change_tick(); - let mut caller = MaybeLocation::caller(); // TODO: Fix up caller location. let component_id = self.components.get_valid_resource_id(TypeId::of::())?; let entity = self.components.resource_entities.get(&component_id)?; let mut entity_mut = self.get_entity_mut(*entity).ok()?; - let mut ticks = entity_mut.get_change_ticks_by_id(component_id)?; + + let mut ticks = entity_mut.get_change_ticks::()?; + let mut caller = entity_mut.get_caller::()?; let mut value = entity_mut.take::()?; let value_mut = Mut { @@ -2621,8 +2622,15 @@ impl World { This is not allowed as the original resource is reinserted to the world after the closure is invoked.", DebugName::type_name::()); - // TODO: insert with ticks of value_mut and maybe also caller - entity_mut.insert(value); + move_as_ptr!(value); + + // TODO: insert with ticks, not only caller + entity_mut.insert_with_caller( + value, + InsertMode::Replace, + caller, + RelationshipHookMode::Skip, + ); Some(result) } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index cce700c2a28e6..9d7a7c464b503 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -880,6 +880,30 @@ impl<'w> UnsafeEntityCell<'w> { } } + /// Retrieves the caller information for the given component. + /// + /// # Safety + /// It is the caller's responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the component + /// - no other mutable references to the component exist at the same time + #[inline] + pub unsafe fn get_caller(self) -> Option { + let component_id = self.world.components().get_valid_id(TypeId::of::())?; + + // SAFETY: + // - entity location is valid + // - proper world access is promised by caller + unsafe { + get_caller( + self.world, + component_id, + T::STORAGE_TYPE, + self.entity, + self.location, + ) + } + } + /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change /// detection in custom runtimes. /// @@ -1292,6 +1316,36 @@ unsafe fn get_ticks( } } +/// Get the caller information for a [`Component`] on a particular [`Entity`] +/// +/// # Safety +/// - `location` must refer to an archetype that contains `entity` +/// the archetype +/// - `component_id` must be valid +/// - `storage_type` must accurately reflect where the components for `component_id` are stored. +/// - the caller must ensure that no aliasing rules are violated +#[inline] +unsafe fn get_caller( + world: UnsafeWorldCell<'_>, + component_id: ComponentId, + storage_type: StorageType, + entity: Entity, + location: EntityLocation, +) -> Option { + let caller = match storage_type { + StorageType::Table => world + .fetch_table(location)? + .get_changed_by(component_id, location.table_row), + StorageType::SparseSet => world.fetch_sparse_set(component_id)?.get_changed_by(entity), + }; + Some( + caller + .transpose()? + // SAFETY: This function is being called through an exclusive mutable reference to Self + .map(|changed_by| unsafe { *changed_by.deref() }), + ) +} + impl ContainsEntity for UnsafeEntityCell<'_> { fn entity(&self) -> Entity { self.id() From 2b1a5b51be7b316da3460e6a07ff8ca8240dba8d Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Wed, 17 Sep 2025 22:38:46 +0200 Subject: [PATCH 29/69] fixed require() statement for AmbientLight --- crates/bevy_ecs/macros/src/lib.rs | 7 ++-- crates/bevy_ecs/src/resource.rs | 3 +- crates/bevy_ecs/src/world/mod.rs | 45 ++++++++------------------ crates/bevy_light/src/ambient_light.rs | 1 - 4 files changed, 20 insertions(+), 36 deletions(-) diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index c465e052d284b..3c240e9ac7b94 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -613,7 +613,10 @@ pub fn derive_message(input: TokenStream) -> TokenStream { } /// Implement the `Resource` trait. -#[proc_macro_derive(Resource)] +#[proc_macro_derive( + Resource, + attributes(component, require, relationship, relationship_target, entities) +)] pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } @@ -690,7 +693,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { /// #[component(hook_name = function)] /// struct MyComponent; /// ``` -/// where `hook_name` is `on_add`, `on_insert`, `on_replace` or `on_remove`; +/// where `hook_name` is `on_add`, `on_insert`, `on_replace` or `on_remove`; /// `function` can be either a path, e.g. `some_function::`, /// or a function call that returns a function that can be turned into /// a `ComponentHook`, e.g. `get_closure("Hi!")`. diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index df1b8e6bd605d..89bdb6ca93eb7 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -1,5 +1,6 @@ //! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World). +use crate::component::Mutable; use crate::entity_disabling::Internal; use crate::prelude::Component; use crate::prelude::ReflectComponent; @@ -79,7 +80,7 @@ pub use bevy_ecs_macros::Resource; label = "invalid `Resource`", note = "consider annotating `{Self}` with `#[derive(Resource)]`" )] -pub trait Resource: Component {} +pub trait Resource: Component {} /// A marker component for the entity that stores the resource of type `T`. /// diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index ab92d42b06f1d..677697f06cfd3 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -47,7 +47,7 @@ use crate::{ lifecycle::{ComponentHooks, RemovedComponentMessages, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, message::{Message, MessageId, Messages, WriteBatchIds}, observer::Observers, - prelude::{Add, Despawn, Insert, Remove, Replace, Without}, + prelude::{Add, Despawn, DetectChangesMut, Insert, Remove, Replace, Without}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, resource::{Resource, ResourceEntity, TypeErasedResource}, @@ -2624,7 +2624,6 @@ impl World { move_as_ptr!(value); - // TODO: insert with ticks, not only caller entity_mut.insert_with_caller( value, InsertMode::Replace, @@ -2632,6 +2631,10 @@ impl World { RelationshipHookMode::Skip, ); + // Update ticks + entity_mut.get_mut::()?.set_last_added(ticks.added); + entity_mut.get_mut::()?.set_last_changed(ticks.changed); + Some(result) } @@ -3284,7 +3287,6 @@ impl World { }) } - /* /// Mutably iterates over all resources in the world. /// /// The returned iterator provides lifetimed, but type-unsafe pointers. Actually reading from or writing @@ -3350,47 +3352,28 @@ impl World { /// # assert_eq!(world.resource::().0, 2); /// # assert_eq!(world.resource::().0, 3); /// ``` - #[inline] pub fn iter_resources_mut(&mut self) -> impl Iterator)> { - self.storages - .resources + self.components() + .resource_entities .iter() - .filter_map(|(component_id, data)| { + .map(|(component_id, entity)| (*component_id, *entity)) + .filter_map(|(component_id, entity)| { // SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with its ID. let component_info = unsafe { self.components .get_info(component_id) .debug_checked_unwrap() }; - let (ptr, ticks, caller) = data.get_with_ticks()?; - // SAFETY: - // - We have exclusive access to the world, so no other code can be aliasing the `TickCells` - // - We only hold one `TicksMut` at a time, and we let go of it before getting the next one - let ticks = unsafe { - TicksMut::from_tick_cells( - ticks, - self.last_change_tick(), - self.read_change_tick(), - ) - }; + let world = self.as_unsafe_world_cell_readonly(); + let entity_cell = world.get_entity(entity).ok()?; - let mut_untyped = MutUntyped { - // SAFETY: - // - We have exclusive access to the world, so no other code can be aliasing the `Ptr` - // - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one - value: unsafe { ptr.assert_unique() }, - ticks, - // SAFETY: - // - We have exclusive access to the world, so no other code can be aliasing the `Ptr` - // - We iterate one resource at a time, and we let go of each `PtrMut` before getting the next one - changed_by: unsafe { caller.map(|caller| caller.deref_mut()) }, - }; + // SAFETY: We have exclusive world access and we don't iterate any entity twice + let mut_untyped = unsafe { entity_cell.get_mut_by_id(component_id).ok()? }; Some((component_info, mut_untyped)) }) } - */ /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. /// The returned pointer must not be used to modify the resource, and must not be @@ -3921,7 +3904,6 @@ mod tests { assert!(iter.next().is_none()); } - /* #[test] fn iter_resources_mut() { let mut world = World::new(); @@ -3957,7 +3939,6 @@ mod tests { "Hello, world?".to_string() ); } - */ #[test] fn dynamic_resource() { diff --git a/crates/bevy_light/src/ambient_light.rs b/crates/bevy_light/src/ambient_light.rs index 06422c8b63499..9d701cca0c64a 100644 --- a/crates/bevy_light/src/ambient_light.rs +++ b/crates/bevy_light/src/ambient_light.rs @@ -1,4 +1,3 @@ -use bevy_camera::Camera; use bevy_color::Color; use bevy_ecs::prelude::*; use bevy_reflect::prelude::*; From e8c67cc29a38c63363731f101958fcaaa017b0dc Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 18 Sep 2025 15:02:09 +0200 Subject: [PATCH 30/69] change location of resource_entities --- crates/bevy_ecs/src/component/info.rs | 6 +- crates/bevy_ecs/src/world/mod.rs | 74 +++++++------------ .../bevy_ecs/src/world/unsafe_world_cell.rs | 43 ++++++++++- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index e83cb56e30094..f318fa4606437 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -1,5 +1,5 @@ use alloc::{borrow::Cow, vec::Vec}; -use bevy_platform::{collections::HashMap, hash::FixedHasher, sync::PoisonError}; +use bevy_platform::{hash::FixedHasher, sync::PoisonError}; use bevy_ptr::OwningPtr; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -19,7 +19,6 @@ use crate::{ RequiredComponents, StorageType, }, lifecycle::ComponentHooks, - prelude::Entity, query::DebugCheckedUnwrap as _, resource::Resource, storage::SparseSetIndex, @@ -349,9 +348,6 @@ impl ComponentDescriptor { pub struct Components { pub(super) components: Vec>, pub(super) indices: TypeIdMap, - /// A lookup for the entities on which resources are stored. - /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs - pub resource_entities: HashMap, // This is kept internal and local to verify that no deadlocks can occor. pub(super) queued: bevy_platform::sync::RwLock, } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 677697f06cfd3..b389d31c13015 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -62,6 +62,7 @@ use crate::{ }, }; use alloc::{boxed::Box, vec::Vec}; +use bevy_platform::collections::HashMap; use bevy_platform::sync::atomic::{AtomicU32, Ordering}; use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr}; use bevy_utils::prelude::DebugName; @@ -92,6 +93,9 @@ pub struct World { pub(crate) entities: Entities, pub(crate) components: Components, pub(crate) component_ids: ComponentIds, + /// A lookup for the entities on which resources are stored. + /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs + pub resource_entities: HashMap, pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, @@ -110,6 +114,7 @@ impl Default for World { id: WorldId::new().expect("More `bevy` `World`s have been created than is supported"), entities: Entities::new(), components: Default::default(), + resource_entities: Default::default(), archetypes: Archetypes::new(), storages: Default::default(), bundles: Default::default(), @@ -1703,11 +1708,7 @@ impl World { let caller = MaybeLocation::caller(); let component_id = self.components_registrator().register_resource::(); - if !self - .components - .resource_entities - .contains_key(&component_id) - { + if !self.resource_entities.contains_key(&component_id) { let value = R::from_world(self); move_as_ptr!(value); @@ -1715,9 +1716,7 @@ impl World { .spawn_with_caller(value, caller) .insert(ResourceEntity::::default()) .id(); - self.components - .resource_entities - .insert(component_id, entity); + self.resource_entities.insert(component_id, entity); } component_id @@ -1744,26 +1743,16 @@ impl World { ) { let component_id = self.components_registrator().register_resource::(); - if !self - .components - .resource_entities - .contains_key(&component_id) - { + if !self.resource_entities.contains_key(&component_id) { move_as_ptr!(value); let entity = self .spawn_with_caller(value, caller) .insert(ResourceEntity::::default()) .id(); - self.components - .resource_entities - .insert(component_id, entity); + self.resource_entities.insert(component_id, entity); } else { - let entity = self - .components - .resource_entities - .get(&component_id) - .unwrap(); + let entity = self.resource_entities.get(&component_id).unwrap(); move_as_ptr!(value); if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { entity_mut.insert_with_caller( @@ -1835,11 +1824,11 @@ impl World { #[inline] pub fn remove_resource(&mut self) -> Option { let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let entity = self.components.resource_entities.remove(&component_id)?; + let entity = self.resource_entities.remove(&component_id)?; let mut entity_ref = self.get_entity_mut(entity).ok()?; let value = entity_ref.take::()?; entity_ref.despawn(); - self.components.resource_entities.remove(&component_id); + self.resource_entities.remove(&component_id); Some(value) } @@ -1877,7 +1866,7 @@ impl World { /// Returns `true` if a resource with provided `component_id` exists. Otherwise returns `false`. #[inline] pub fn contains_resource_by_id(&self, component_id: ComponentId) -> bool { - if let Some(entity) = self.components.resource_entities.get(&component_id) + if let Some(entity) = self.resource_entities.get(&component_id) && let Ok(entity_ref) = self.get_entity(*entity) { return entity_ref.contains_id(component_id); @@ -1967,7 +1956,7 @@ impl World { &self, component_id: ComponentId, ) -> Option { - let entity = self.components.resource_entities.get(&component_id)?; + let entity = self.resource_entities.get(&component_id)?; let entity_ref = self.get_entity(*entity).ok()?; entity_ref.get_change_ticks_by_id(component_id) } @@ -2097,11 +2086,11 @@ impl World { if !self.contains_resource_by_id(component_id) { let value = func(); - if let Some(entity) = self.components.resource_entities.get(&component_id) { + if let Some(entity) = self.resource_entities.get(&component_id) { if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { entity_mut.insert(value); } else { - self.components.resource_entities.remove(&component_id); + self.resource_entities.remove(&component_id); self.insert_resource_with_caller(value, caller); } } else { @@ -2595,7 +2584,7 @@ impl World { let change_tick = self.change_tick(); let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let entity = self.components.resource_entities.get(&component_id)?; + let entity = self.resource_entities.get(&component_id)?; let mut entity_mut = self.get_entity_mut(*entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; @@ -2717,11 +2706,7 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { - if !self - .components - .resource_entities - .contains_key(&component_id) - { + if !self.resource_entities.contains_key(&component_id) { let resource_entity = ResourceEntity::::default(); move_as_ptr!(resource_entity); @@ -2730,9 +2715,7 @@ impl World { .spawn_with_caller(resource_entity, caller) .insert_by_id(component_id, value) .id(); - self.components - .resource_entities - .insert(component_id, entity); + self.resource_entities.insert(component_id, entity); } } @@ -3075,16 +3058,11 @@ impl World { /// This can easily cause systems expecting certain resources to immediately start panicking. /// Use with caution. pub fn clear_resources(&mut self) { - let resource_entities: Vec = self - .components - .resource_entities - .values() - .copied() - .collect(); + let resource_entities: Vec = self.resource_entities.values().copied().collect(); for entity in resource_entities { self.despawn(entity); } - self.components.resource_entities.clear(); + self.resource_entities.clear(); self.storages.non_send_resources.clear(); } @@ -3278,8 +3256,7 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { - let component_ids: Vec = - self.components.resource_entities.keys().copied().collect(); + let component_ids: Vec = self.resource_entities.keys().copied().collect(); component_ids.into_iter().filter_map(|component_id| { let component_info = self.components().get_info(component_id)?; let resource = self.get_resource_by_id(component_id)?; @@ -3353,8 +3330,7 @@ impl World { /// # assert_eq!(world.resource::().0, 3); /// ``` pub fn iter_resources_mut(&mut self) -> impl Iterator)> { - self.components() - .resource_entities + self.resource_entities .iter() .map(|(component_id, entity)| (*component_id, *entity)) .filter_map(|(component_id, entity)| { @@ -3420,7 +3396,7 @@ impl World { /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { - if let Some(entity) = self.components.resource_entities.remove(&component_id) { + if let Some(entity) = self.resource_entities.remove(&component_id) { self.despawn(entity).then_some::<()>(()) } else { None @@ -3625,7 +3601,7 @@ impl fmt::Debug for World { .field("entity_count", &self.entities.len()) .field("archetype_count", &self.archetypes.len()) .field("component_count", &self.components.len()) - .field("resource_count", &self.components.resource_entities.len()) + .field("resource_count", &self.resource_entities.len()) .finish() } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 9d7a7c464b503..65f1d7caf26bf 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -16,6 +16,7 @@ use crate::{ storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, }; +use bevy_platform::collections::HashMap; use bevy_platform::sync::atomic::Ordering; use bevy_ptr::{Ptr, UnsafeCellDeref}; use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, panic::Location, ptr}; @@ -275,6 +276,14 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.components } + /// Retrieves this world's resource-entity map. + #[inline] + pub fn resource_entities(self) -> &'w HashMap { + // SAFETY: + // - we only access world metadata + &unsafe { self.world_metadata() }.resource_entities + } + /// Retrieves this world's collection of [removed components](RemovedComponentMessages). pub fn removed_components(self) -> &'w RemovedComponentMessages { // SAFETY: @@ -455,7 +464,7 @@ impl<'w> UnsafeWorldCell<'w> { /// - no mutable reference to the resource exists at the same time #[inline] pub unsafe fn get_resource_by_id(self, component_id: ComponentId) -> Option> { - let entity = self.components().resource_entities.get(&component_id)?; + let entity = self.resource_entities().get(&component_id)?; let entity_cell = self.get_entity(*entity).ok()?; // SAFETY: caller ensures that `self` has permission to access `R` // caller ensures that no mutable reference exists to `R` @@ -541,7 +550,7 @@ impl<'w> UnsafeWorldCell<'w> { component_id: ComponentId, ) -> Option> { self.assert_allows_mutable_access(); - let entity = self.components().resource_entities.get(&component_id)?; + let entity = self.resource_entities().get(&component_id)?; let entity_cell = self.get_entity(*entity).ok()?; // SAFETY: we only access data that the caller has ensured is unaliased and `self` // has permission to access. @@ -625,7 +634,7 @@ impl<'w> UnsafeWorldCell<'w> { TickCells<'w>, MaybeLocation<&'w UnsafeCell<&'static Location<'static>>>, )> { - let entity = self.components().resource_entities.get(&component_id)?; + let entity = self.resource_entities().get(&component_id)?; let storage_type = self.components().get_info(component_id)?.storage_type(); let location = self.get_entity(*entity).ok()?.location(); // SAFETY: @@ -936,6 +945,34 @@ impl<'w> UnsafeEntityCell<'w> { } } + /// Retrieves the caller information for the given [`ComponentId`]. + /// + /// **You should prefer to use the typed API [`UnsafeEntityCell::get_change_ticks`] where possible and only + /// use this in cases where the actual component types are not known at + /// compile time.** + /// + /// # Safety + /// It is the caller's responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the component + /// - no other mutable references to the component exist at the same time + #[inline] + pub unsafe fn get_caller_by_id(&self, component_id: ComponentId) -> Option { + let info = self.world.components().get_info(component_id)?; + // SAFETY: + // - entity location and entity is valid + // - world access is immutable, lifetime tied to `&self` + // - the storage type provided is correct for T + unsafe { + get_caller( + self.world, + component_id, + info.storage_type(), + self.entity, + self.location, + ) + } + } + /// # Safety /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component mutably From a01dc7dab04e391aac7ad75078ee6f0f503129f5 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Fri, 19 Sep 2025 00:13:31 +0200 Subject: [PATCH 31/69] changed scene serialization --- crates/bevy_scene/src/dynamic_scene.rs | 62 ++--- .../bevy_scene/src/dynamic_scene_builder.rs | 115 ++++++--- crates/bevy_scene/src/scene.rs | 4 +- crates/bevy_scene/src/serde.rs | 218 +++++++++++++++++- 4 files changed, 312 insertions(+), 87 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index f9ef1616ee4e6..c739c331b5c9b 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,6 +1,5 @@ use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; -use bevy_ecs::reflect::{ReflectMapEntities, ReflectResource}; use bevy_ecs::{ entity::{Entity, EntityHashMap, SceneEntityMapper}, reflect::{AppTypeRegistry, ReflectComponent}, @@ -8,8 +7,8 @@ use bevy_ecs::{ }; use bevy_reflect::{PartialReflect, TypePath}; -use crate::reflect_utils::clone_reflect_value; use bevy_ecs::component::ComponentCloneBehavior; +use bevy_ecs::component::ComponentId; use bevy_ecs::relationship::RelationshipHookMode; #[cfg(feature = "serialize")] @@ -29,7 +28,7 @@ use { #[derive(Asset, TypePath, Default)] pub struct DynamicScene { /// Resources stored in the dynamic scene. - pub resources: Vec>, + pub resources: Vec<(ComponentId, DynamicEntity)>, /// Entities contained in the dynamic scene. pub entities: Vec, } @@ -80,9 +79,20 @@ impl DynamicScene { ) -> Result<(), SceneSpawnError> { let type_registry = type_registry.read(); + let entities = self + .entities + .iter() + .map(|scene_entity| (None, scene_entity)) + .chain( + self.resources + .iter() + .map(|(resource_id, scene_entity)| (Some(resource_id), scene_entity)), + ) + .collect::, &DynamicEntity)>>(); + // First ensure that every entity in the scene has a corresponding world // entity in the entity map. - for scene_entity in &self.entities { + for (_resource_id, scene_entity) in &entities { // Fetch the entity with the given entity id from the `entity_map` // or spawn a new entity with a transiently unique id if there is // no corresponding entry. @@ -91,7 +101,7 @@ impl DynamicScene { .or_insert_with(|| world.spawn_empty().id()); } - for scene_entity in &self.entities { + for (resource_id, scene_entity) in &entities { // Fetch the entity with the given entity id from the `entity_map`. let entity = *entity_map .get(&scene_entity.entity) @@ -140,45 +150,11 @@ impl DynamicScene { ); }); } - } - // Insert resources after all entities have been added to the world. - // This ensures the entities are available for the resources to reference during mapping. - for resource in &self.resources { - let type_info = resource.get_represented_type_info().ok_or_else(|| { - SceneSpawnError::NoRepresentedType { - type_path: resource.reflect_type_path().to_string(), - } - })?; - let registration = type_registry.get(type_info.type_id()).ok_or_else(|| { - SceneSpawnError::UnregisteredButReflectedType { - type_path: type_info.type_path().to_string(), - } - })?; - let reflect_resource = registration.data::().ok_or_else(|| { - SceneSpawnError::UnregisteredResource { - type_path: type_info.type_path().to_string(), - } - })?; - - // If this component references entities in the scene, update - // them to the entities in the world. - let mut cloned_resource; - let partial_reflect_resource = if let Some(map_entities) = - registration.data::() - { - cloned_resource = clone_reflect_value(resource.as_partial_reflect(), registration); - SceneEntityMapper::world_scope(entity_map, world, |_, mapper| { - map_entities.map_entities(cloned_resource.as_partial_reflect_mut(), mapper); - }); - cloned_resource.as_partial_reflect() - } else { - resource.as_partial_reflect() - }; - - // If the world already contains an instance of the given resource - // just apply the (possibly) new value, otherwise insert the resource - reflect_resource.apply_or_insert(world, partial_reflect_resource, &type_registry); + // If this entity is also a resource, we have to also update the resource_entities + if let Some(resource_id) = resource_id { + world.resource_entities.insert(**resource_id, entity); + } } Ok(()) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 7da729e7aa78b..7cc725cfb8ead 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -7,11 +7,10 @@ use bevy_ecs::{ component::{Component, ComponentId}, entity_disabling::DefaultQueryFilters, prelude::Entity, - reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, + reflect::{AppTypeRegistry, ReflectComponent}, resource::Resource, world::World, }; -use bevy_reflect::PartialReflect; use bevy_utils::default; /// A [`DynamicScene`] builder, used to build a scene from a [`World`] by extracting some entities and resources. @@ -59,7 +58,7 @@ use bevy_utils::default; /// /// [`Reflect`]: bevy_reflect::Reflect pub struct DynamicSceneBuilder<'w> { - extracted_resources: BTreeMap>, + extracted_resources: BTreeMap, extracted_scene: BTreeMap, component_filter: SceneFilter, resource_filter: SceneFilter, @@ -211,7 +210,7 @@ impl<'w> DynamicSceneBuilder<'w> { #[must_use] pub fn build(self) -> DynamicScene { DynamicScene { - resources: self.extracted_resources.into_values().collect(), + resources: self.extracted_resources.into_iter().collect(), entities: self.extracted_scene.into_values().collect(), } } @@ -351,40 +350,62 @@ impl<'w> DynamicSceneBuilder<'w> { .original_world .components() .get_valid_resource_id(TypeId::of::()); + // Don't extract the AppTypeRegistry resource + let original_world_atr_id = self + .original_world + .components() + .get_valid_resource_id(TypeId::of::()); let type_registry = self.original_world.resource::().read(); - for (component_id, entity) in self.original_world.components().resource_entities.iter() { - if Some(*component_id) == original_world_dqf_id { + for (resource_id, entity) in self.original_world.resource_entities.iter() { + if (Some(*resource_id) == original_world_dqf_id) + || (Some(*resource_id) == original_world_atr_id) + { + continue; + } + + if self + .original_world + .components() + .get_info(*resource_id) + .and_then(|info| info.type_id()) + .is_some_and(|type_id| self.resource_filter.is_denied_by_id(type_id)) + { continue; } - let mut extract_and_push = || { - let type_id = self - .original_world - .components() - .get_info(*component_id)? - .type_id()?; - - let is_denied = self.resource_filter.is_denied_by_id(type_id); - - if is_denied { - // Resource is either in the denylist or _not_ in the allowlist - return None; - } - - let type_registration = type_registry.get(type_id)?; - let entity = self.original_world.get_entity(*entity).ok()?; - let component = type_registration - .data::()? - .reflect(entity)?; - - let component = - clone_reflect_value(component.as_partial_reflect(), type_registration); - - self.extracted_resources.insert(*component_id, component); - Some(()) + + let mut entry = DynamicEntity { + entity: *entity, + components: Vec::new(), }; - extract_and_push(); + + let original_entity = self.original_world.entity(*entity); + for component_id in original_entity.archetype().iter_components() { + let mut extract_and_push = || { + let type_id = self + .original_world + .components() + .get_info(component_id)? + .type_id()?; + + // The resource_id has been approved, so we don't do any other checks + let type_registration = type_registry.get(type_id)?; + + let component = type_registration + .data::()? + .reflect(original_entity)?; + + let component = + clone_reflect_value(component.as_partial_reflect(), type_registration); + + entry.components.push(component); + Some(()) + }; + extract_and_push(); + } + + self.extracted_resources.insert(*resource_id, entry); } drop(type_registry); @@ -576,7 +597,12 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert_eq!( + scene.resources[0].0, + world + .resource_id::() + .expect("ResourceA is registered") + ); } #[test] @@ -595,7 +621,12 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert_eq!( + scene.resources[0].0, + world + .resource_id::() + .expect("ResourceA is registered") + ); } #[test] @@ -673,7 +704,12 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert_eq!( + scene.resources[0].0, + world + .resource_id::() + .expect("ResourceA is registered") + ); } #[test] @@ -697,7 +733,12 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert_eq!( + scene.resources[0].0, + world + .resource_id::() + .expect("ResourceA is registered") + ); } #[test] @@ -728,10 +769,12 @@ mod tests { .expect("component should be concrete due to `FromReflect`") .is::()); + /* let resource = &scene.resources[0]; assert!(resource .try_as_reflect() .expect("resource should be concrete due to `FromReflect`") .is::()); + */ } } diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 71a60d85cbf77..2658d7b6180a4 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ component::ComponentCloneBehavior, entity::{Entity, EntityHashMap, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, - reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, + reflect::{AppTypeRegistry, ReflectComponent}, relationship::RelationshipHookMode, world::World, }; @@ -71,7 +71,7 @@ impl Scene { .get_resource_id(TypeId::of::()); // Resources archetype - for (component_id, entity) in self.world.components().resource_entities.iter() { + for (component_id, entity) in self.world.resource_entities.iter() { if Some(*component_id) == self_dqf_id { continue; } diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 0b4c0b23f8e03..ed90ff5d753d2 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -1,6 +1,7 @@ //! `serde` serialization and deserialization implementation for Bevy scenes. use crate::{DynamicEntity, DynamicScene}; +use bevy_ecs::component::ComponentId; use bevy_ecs::entity::Entity; use bevy_platform::collections::HashSet; use bevy_reflect::{ @@ -26,6 +27,10 @@ pub const SCENE_ENTITIES: &str = "entities"; /// Name of the serialized entity struct type. pub const ENTITY_STRUCT: &str = "Entity"; +/// Name of the serialized resource struct type. +pub const RESOURCE_STRUCT: &str = "Resource"; +/// Name of the serialized resource id. +pub const RESOURCE_ID: &str = "Resource ID"; /// Name of the serialized component field in an entity struct. pub const ENTITY_FIELD_COMPONENTS: &str = "components"; @@ -81,8 +86,8 @@ impl<'a> Serialize for SceneSerializer<'a> { let mut state = serializer.serialize_struct(SCENE_STRUCT, 2)?; state.serialize_field( SCENE_RESOURCES, - &SceneMapSerializer { - entries: &self.scene.resources, + &ResourcesSerializer { + resource_entities: &self.scene.resources, registry: self.registry, }, )?; @@ -124,6 +129,34 @@ impl<'a> Serialize for EntitiesSerializer<'a> { } } +/// Handles serialization of multiple resources. +pub struct ResourcesSerializer<'a> { + /// The resource entity to serialize. + pub resource_entities: &'a [(ComponentId, DynamicEntity)], + /// Type registry in which the component types used by the entity are registered. + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for ResourcesSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_map(Some(self.resource_entities.len()))?; + for (resource_id, entity) in self.resource_entities { + state.serialize_entry( + &entity.entity, + &ResourceSerializer { + resource_id, + entity, + registry: self.registry, + }, + )?; + } + state.end() + } +} + /// Handles entity serialization as a map of component type to component value. pub struct EntitySerializer<'a> { /// The entity to serialize. @@ -149,6 +182,34 @@ impl<'a> Serialize for EntitySerializer<'a> { } } +/// Handles entity serialization as a map of component type to component value. +pub struct ResourceSerializer<'a> { + /// The [`ComponentId`] of the resource + pub resource_id: &'a ComponentId, + /// The entity to serialize. + pub entity: &'a DynamicEntity, + /// Type registry in which the component types used by the entity are registered. + pub registry: &'a TypeRegistry, +} + +impl<'a> Serialize for ResourceSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut state = serializer.serialize_struct(RESOURCE_STRUCT, 2)?; + state.serialize_field(RESOURCE_ID, &self.resource_id.index())?; + state.serialize_field( + ENTITY_FIELD_COMPONENTS, + &EntitySerializer { + entity: self.entity, + registry: self.registry, + }, + )?; + state.end() + } +} + /// Handles serializing a list of values with a unique type as a map of type to value. /// /// Used to serialize scene resources in [`SceneSerializer`] and entity components in [`EntitySerializer`]. @@ -207,6 +268,13 @@ enum EntityField { Components, } +#[derive(Deserialize)] +#[serde(field_identifier, rename_all = "lowercase")] +enum ResourceField { + ResourceId, + Entity, +} + /// Handles scene deserialization. pub struct SceneDeserializer<'a> { /// Type registry in which the components and resources types used in the scene to deserialize are registered. @@ -246,8 +314,8 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> { A: SeqAccess<'de>, { let resources = seq - .next_element_seed(SceneMapDeserializer { - registry: self.type_registry, + .next_element_seed(SceneResourcesDeserializer { + type_registry: self.type_registry, })? .ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?; @@ -275,8 +343,8 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> { if resources.is_some() { return Err(Error::duplicate_field(SCENE_RESOURCES)); } - resources = Some(map.next_value_seed(SceneMapDeserializer { - registry: self.type_registry, + resources = Some(map.next_value_seed(SceneResourcesDeserializer { + type_registry: self.type_registry, })?); } SceneField::Entities => { @@ -347,6 +415,53 @@ impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> { } } +/// Handles deserialization for a collection of resources. +pub struct SceneResourcesDeserializer<'a> { + /// Type registry in which the component types used by the entities to deserialize are registered. + pub type_registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for SceneResourcesDeserializer<'a> { + type Value = Vec<(ComponentId, DynamicEntity)>; + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(SceneResourcesVisitor { + type_registry: self.type_registry, + }) + } +} + +struct SceneResourcesVisitor<'a> { + pub type_registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for SceneResourcesVisitor<'a> { + type Value = Vec<(ComponentId, DynamicEntity)>; + + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { + formatter.write_str("map of resources") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut resources = Vec::new(); + while let Some(entity) = map.next_key::()? { + let entity = map.next_value_seed(SceneResourceDeserializer { + entity, + type_registry: self.type_registry, + })?; + resources.push(entity); + } + + Ok(resources) + } +} + /// Handle deserialization of an entity and its components. pub struct SceneEntityDeserializer<'a> { /// Id of the deserialized entity. @@ -430,6 +545,97 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { } } +/// Handle deserialization of a resource. +pub struct SceneResourceDeserializer<'a> { + /// Id of the deserialized resource. + pub entity: Entity, + /// Type registry in which the component types used by the resource to deserialize are registered. + pub type_registry: &'a TypeRegistry, +} + +impl<'a, 'de> DeserializeSeed<'de> for SceneResourceDeserializer<'a> { + type Value = (ComponentId, DynamicEntity); + + fn deserialize(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_struct( + RESOURCE_STRUCT, + &[RESOURCE_ID, ENTITY_FIELD_COMPONENTS], + SceneResourceVisitor { + entity: self.entity, + registry: self.type_registry, + }, + ) + } +} + +struct SceneResourceVisitor<'a> { + pub entity: Entity, + pub registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for SceneResourceVisitor<'a> { + type Value = (ComponentId, DynamicEntity); + + fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { + formatter.write_str("resources") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let resource_id = ComponentId::new( + seq.next_element::()? + .ok_or_else(|| Error::missing_field(RESOURCE_ID))?, + ); + let dynamic_entity = seq + .next_element_seed(SceneEntityDeserializer { + entity: self.entity, + type_registry: self.registry, + })? + .ok_or_else(|| Error::missing_field(ENTITY_STRUCT))?; + + Ok((resource_id, dynamic_entity)) + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let mut resource_id = None; + let mut entity = None; + while let Some(key) = map.next_key()? { + match key { + ResourceField::ResourceId => { + if resource_id.is_some() { + return Err(Error::duplicate_field(RESOURCE_ID)); + } + + resource_id = Some(ComponentId::new(map.next_value()?)); + } + ResourceField::Entity => { + if entity.is_some() { + return Err(Error::duplicate_field(ENTITY_STRUCT)); + } + + entity = Some(map.next_value_seed(SceneEntityDeserializer { + entity: self.entity, + type_registry: self.registry, + })?); + } + } + } + + let resource_id = resource_id.ok_or_else(|| Error::missing_field(RESOURCE_ID))?; + let entity = entity.ok_or_else(|| Error::missing_field(ENTITY_STRUCT))?; + + Ok((resource_id, entity)) + } +} + /// Handles deserialization of a sequence of values with unique types. pub struct SceneMapDeserializer<'a> { /// Type registry in which the types of the values to deserialize are registered. From 9aecdf3e69190685e42740dbfcba10ab592df720 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 22 Sep 2025 14:54:32 +0200 Subject: [PATCH 32/69] removed ResourceEntity, redid bevy_scene, require ReflectComponent for reflect(Resource) --- crates/bevy_a11y/src/lib.rs | 3 +- crates/bevy_ecs/src/entity_disabling.rs | 9 +- crates/bevy_ecs/src/message/messages.rs | 2 +- crates/bevy_ecs/src/resource.rs | 32 +- crates/bevy_ecs/src/system/builder.rs | 2 +- crates/bevy_ecs/src/world/mod.rs | 12 +- crates/bevy_gizmos/src/config.rs | 5 +- crates/bevy_input/src/button_input.rs | 2 +- crates/bevy_input/src/mouse.rs | 2 +- crates/bevy_light/src/ambient_light.rs | 3 +- .../derive/src/container_attributes.rs | 11 + crates/bevy_scene/src/dynamic_scene.rs | 35 +-- .../bevy_scene/src/dynamic_scene_builder.rs | 46 +-- crates/bevy_scene/src/lib.rs | 3 +- crates/bevy_scene/src/scene_spawner.rs | 3 +- crates/bevy_scene/src/serde.rs | 278 ++---------------- crates/bevy_state/src/state/resources.rs | 2 +- crates/bevy_time/src/time.rs | 2 +- 18 files changed, 103 insertions(+), 349 deletions(-) diff --git a/crates/bevy_a11y/src/lib.rs b/crates/bevy_a11y/src/lib.rs index 1ec4cc392436a..f3d57eae8f5a3 100644 --- a/crates/bevy_a11y/src/lib.rs +++ b/crates/bevy_a11y/src/lib.rs @@ -73,7 +73,8 @@ use bevy_ecs::{component::Component, message::Message, resource::Resource, sched #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::ReflectResource, bevy_reflect::std_traits::ReflectDefault, + bevy_ecs::reflect::{ReflectComponent, ReflectResource}, + bevy_reflect::std_traits::ReflectDefault, bevy_reflect::Reflect, }; diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index c2fc5ee6c53b9..c25c1aed6077b 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -106,7 +106,8 @@ use smallvec::SmallVec; #[cfg(feature = "bevy_reflect")] use { - crate::reflect::ReflectComponent, bevy_reflect::std_traits::ReflectDefault, + crate::reflect::{ReflectComponent, ReflectResource}, + bevy_reflect::std_traits::ReflectDefault, bevy_reflect::Reflect, }; @@ -187,7 +188,11 @@ pub struct Internal; /// Think carefully about whether you need to use a new disabling component, /// and clearly communicate their presence in any libraries you publish. #[derive(Resource, Debug)] -#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] +#[cfg_attr( + feature = "bevy_reflect", + derive(bevy_reflect::Reflect), + reflect(Resource) +)] pub struct DefaultQueryFilters { // We only expect a few components per application to act as disabling components, so we use a SmallVec here // to avoid heap allocation in most cases. diff --git a/crates/bevy_ecs/src/message/messages.rs b/crates/bevy_ecs/src/message/messages.rs index 7e7e03f18e85e..9be5e8a1c2c4b 100644 --- a/crates/bevy_ecs/src/message/messages.rs +++ b/crates/bevy_ecs/src/message/messages.rs @@ -10,7 +10,7 @@ use core::{ }; #[cfg(feature = "bevy_reflect")] use { - crate::reflect::ReflectResource, + crate::reflect::{ReflectComponent, ReflectResource}, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 89bdb6ca93eb7..d5c618fcfc46c 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -4,10 +4,8 @@ use crate::component::Mutable; use crate::entity_disabling::Internal; use crate::prelude::Component; use crate::prelude::ReflectComponent; -use crate::prelude::ReflectResource; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; -use core::marker::PhantomData; // The derive macro for the `Resource` trait pub use bevy_ecs_macros::Resource; @@ -82,27 +80,6 @@ pub use bevy_ecs_macros::Resource; )] pub trait Resource: Component {} -/// 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(Internal, IsResource)] -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component, Default))] -pub struct ResourceEntity(#[reflect(ignore)] PhantomData); - -impl Default for ResourceEntity { - fn default() -> Self { - ResourceEntity(PhantomData) - } -} - /// A marker component for entities which store resources. /// /// By contrast, the [`ResourceEntity`] component is used to find the entity that stores a particular resource. @@ -112,15 +89,10 @@ impl Default for ResourceEntity { derive(Reflect), reflect(Component, Default, Debug) )] -#[derive(Component, Default, Debug)] +#[derive(Component, Debug, Default)] +#[require(Internal)] pub struct IsResource; -/// Used in conjunction with [`ResourceEntity`], when no type information is available. -/// This is used by [`World::insert_resource_by_id`](crate::world::World). -#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource, Debug))] -#[derive(Resource, Debug)] -pub struct TypeErasedResource; - #[cfg(test)] mod tests { use crate::change_detection::MaybeLocation; diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 937911ca834c1..309b3889afc75 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -622,7 +622,7 @@ mod tests { entity::Entities, error::Result, prelude::{Component, Query}, - reflect::ReflectResource, + reflect::{ReflectComponent, ReflectResource}, system::{Local, RunSystemOnce}, }; use alloc::vec; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index b389d31c13015..03d8d132b0f2e 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -50,7 +50,7 @@ use crate::{ prelude::{Add, Despawn, DetectChangesMut, Insert, Remove, Replace, Without}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, - resource::{Resource, ResourceEntity, TypeErasedResource}, + resource::{IsResource, Resource}, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, system::Commands, @@ -1714,7 +1714,7 @@ impl World { let entity = self .spawn_with_caller(value, caller) - .insert(ResourceEntity::::default()) + .insert(IsResource::default()) .id(); self.resource_entities.insert(component_id, entity); } @@ -1748,7 +1748,7 @@ impl World { let entity = self .spawn_with_caller(value, caller) - .insert(ResourceEntity::::default()) + .insert(IsResource::default()) .id(); self.resource_entities.insert(component_id, entity); } else { @@ -2707,12 +2707,12 @@ impl World { caller: MaybeLocation, ) { if !self.resource_entities.contains_key(&component_id) { - let resource_entity = ResourceEntity::::default(); - move_as_ptr!(resource_entity); + let is_resource = IsResource::default(); + move_as_ptr!(is_resource); // Since we don't know the type, we use a placeholder type. let entity = self - .spawn_with_caller(resource_entity, caller) + .spawn_with_caller(is_resource, caller) .insert_by_id(component_id, value) .id(); self.resource_entities.insert(component_id, entity); diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 3fcc45deb440c..6ce0eb6405829 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -8,7 +8,10 @@ pub use bevy_gizmos_macros::GizmoConfigGroup; ))] use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component}; -use bevy_ecs::{reflect::ReflectResource, resource::Resource}; +use bevy_ecs::{ + reflect::{ReflectComponent, ReflectResource}, + resource::Resource, +}; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; use bevy_utils::TypeIdMap; use core::{ diff --git a/crates/bevy_input/src/button_input.rs b/crates/bevy_input/src/button_input.rs index e4ff47f470907..10f0c632df751 100644 --- a/crates/bevy_input/src/button_input.rs +++ b/crates/bevy_input/src/button_input.rs @@ -5,7 +5,7 @@ use bevy_platform::collections::HashSet; use core::hash::Hash; #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::ReflectResource, + bevy_ecs::reflect::{ReflectComponent, ReflectResource}, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; diff --git a/crates/bevy_input/src/mouse.rs b/crates/bevy_input/src/mouse.rs index 2d8f0255e10eb..f6663e95f264e 100644 --- a/crates/bevy_input/src/mouse.rs +++ b/crates/bevy_input/src/mouse.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ use bevy_math::Vec2; #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::ReflectResource, + bevy_ecs::reflect::{ReflectComponent, ReflectResource}, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; diff --git a/crates/bevy_light/src/ambient_light.rs b/crates/bevy_light/src/ambient_light.rs index 9d701cca0c64a..201b831ea871f 100644 --- a/crates/bevy_light/src/ambient_light.rs +++ b/crates/bevy_light/src/ambient_light.rs @@ -1,3 +1,4 @@ +use bevy_camera::Camera; use bevy_color::Color; use bevy_ecs::prelude::*; use bevy_reflect::prelude::*; @@ -22,7 +23,7 @@ use bevy_reflect::prelude::*; /// /// [`LightPlugin`]: crate::LightPlugin #[derive(Resource, Clone, Debug, Reflect)] -#[reflect(Resource, Component, Debug, Default, Clone)] +#[reflect(Resource, Debug, Default, Clone)] #[require(Camera)] pub struct AmbientLight { pub color: Color, diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index 47272acd6547a..957a4df0e4dca 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -279,6 +279,17 @@ impl ContainerAttributes { add_unique_ident(&mut self.idents, reflect_ident)?; + // When reflecting resources we also need to reflect component, since + // #[derive(Resource)] is equivalent to #[derive(Resource, Component)], + // we need to ensure that #[reflect(Resource)] is also equivalent to + // #[reflect(Resource, Component)]. This is necessary for bevy_scene + // serialization to work. + if &ident_name == "Resource" { + let mut reflect_component_ident = crate::ident::get_reflect_ident("Component"); + reflect_component_ident.set_span(ident.span()); + add_unique_ident(&mut self.idents, reflect_component_ident)?; + } + Ok(()) } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index c739c331b5c9b..a153e4e6719e9 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -3,12 +3,12 @@ use bevy_asset::Asset; use bevy_ecs::{ entity::{Entity, EntityHashMap, SceneEntityMapper}, reflect::{AppTypeRegistry, ReflectComponent}, + resource::IsResource, world::World, }; use bevy_reflect::{PartialReflect, TypePath}; use bevy_ecs::component::ComponentCloneBehavior; -use bevy_ecs::component::ComponentId; use bevy_ecs::relationship::RelationshipHookMode; #[cfg(feature = "serialize")] @@ -28,7 +28,7 @@ use { #[derive(Asset, TypePath, Default)] pub struct DynamicScene { /// Resources stored in the dynamic scene. - pub resources: Vec<(ComponentId, DynamicEntity)>, + pub resources: Vec, /// Entities contained in the dynamic scene. pub entities: Vec, } @@ -60,7 +60,12 @@ impl DynamicScene { .archetypes() .iter() .flat_map(bevy_ecs::archetype::Archetype::entities) - .map(bevy_ecs::archetype::ArchetypeEntity::id), + .map(bevy_ecs::archetype::ArchetypeEntity::id) + .filter(|id| { + world + .get_entity(*id) + .is_ok_and(|entity| !entity.contains::()) + }), ) .extract_resources() .build() @@ -79,20 +84,9 @@ impl DynamicScene { ) -> Result<(), SceneSpawnError> { let type_registry = type_registry.read(); - let entities = self - .entities - .iter() - .map(|scene_entity| (None, scene_entity)) - .chain( - self.resources - .iter() - .map(|(resource_id, scene_entity)| (Some(resource_id), scene_entity)), - ) - .collect::, &DynamicEntity)>>(); - // First ensure that every entity in the scene has a corresponding world // entity in the entity map. - for (_resource_id, scene_entity) in &entities { + for scene_entity in self.entities.iter().chain(self.resources.iter()) { // Fetch the entity with the given entity id from the `entity_map` // or spawn a new entity with a transiently unique id if there is // no corresponding entry. @@ -101,7 +95,7 @@ impl DynamicScene { .or_insert_with(|| world.spawn_empty().id()); } - for (resource_id, scene_entity) in &entities { + for scene_entity in self.entities.iter().chain(self.resources.iter()) { // Fetch the entity with the given entity id from the `entity_map`. let entity = *entity_map .get(&scene_entity.entity) @@ -128,6 +122,10 @@ impl DynamicScene { { let component_id = reflect_component.register_component(world); + + // Really, really, really, hacky, but it does work, which is funny + world.resource_entities.insert(component_id, entity); + // SAFETY: we registered the component above. the info exists #[expect(unsafe_code, reason = "this is faster")] let component_info = @@ -150,11 +148,6 @@ impl DynamicScene { ); }); } - - // If this entity is also a resource, we have to also update the resource_entities - if let Some(resource_id) = resource_id { - world.resource_entities.insert(**resource_id, entity); - } } Ok(()) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 7cc725cfb8ead..c974ae4dcbf21 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -4,7 +4,7 @@ use crate::reflect_utils::clone_reflect_value; use crate::{DynamicEntity, DynamicScene, SceneFilter}; use alloc::collections::BTreeMap; use bevy_ecs::{ - component::{Component, ComponentId}, + component::Component, entity_disabling::DefaultQueryFilters, prelude::Entity, reflect::{AppTypeRegistry, ReflectComponent}, @@ -58,7 +58,7 @@ use bevy_utils::default; /// /// [`Reflect`]: bevy_reflect::Reflect pub struct DynamicSceneBuilder<'w> { - extracted_resources: BTreeMap, + extracted_resources: BTreeMap, extracted_scene: BTreeMap, component_filter: SceneFilter, resource_filter: SceneFilter, @@ -210,7 +210,7 @@ impl<'w> DynamicSceneBuilder<'w> { #[must_use] pub fn build(self) -> DynamicScene { DynamicScene { - resources: self.extracted_resources.into_iter().collect(), + resources: self.extracted_resources.into_values().collect(), entities: self.extracted_scene.into_values().collect(), } } @@ -405,7 +405,7 @@ impl<'w> DynamicSceneBuilder<'w> { extract_and_push(); } - self.extracted_resources.insert(*resource_id, entry); + self.extracted_resources.insert(*entity, entry); } drop(type_registry); @@ -417,9 +417,11 @@ impl<'w> DynamicSceneBuilder<'w> { mod tests { use bevy_ecs::{ component::Component, + entity_disabling::Internal, prelude::{Entity, Resource}, query::With, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, + resource::IsResource, world::World, }; @@ -436,11 +438,11 @@ mod tests { struct ComponentB; #[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)] - #[reflect(Resource, Component)] + #[reflect(Resource)] struct ResourceA; #[derive(Resource, Reflect, Default, Eq, PartialEq, Debug)] - #[reflect(Resource, Component)] + #[reflect(Resource)] struct ResourceB; #[test] @@ -588,6 +590,8 @@ mod tests { let atr = AppTypeRegistry::default(); atr.write().register::(); + atr.write().register::(); + atr.write().register::(); world.insert_resource(atr); world.insert_resource(ResourceA); @@ -597,12 +601,10 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert_eq!( - scene.resources[0].0, - world - .resource_id::() - .expect("ResourceA is registered") - ); + assert_eq!(scene.resources[0].components.len(), 3); + assert!(scene.resources[0].components[0].represents::()); + assert!(scene.resources[0].components[1].represents::()); + assert!(scene.resources[0].components[2].represents::()); } #[test] @@ -621,12 +623,6 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert_eq!( - scene.resources[0].0, - world - .resource_id::() - .expect("ResourceA is registered") - ); } #[test] @@ -704,12 +700,6 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert_eq!( - scene.resources[0].0, - world - .resource_id::() - .expect("ResourceA is registered") - ); } #[test] @@ -733,18 +723,12 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert_eq!( - scene.resources[0].0, - world - .resource_id::() - .expect("ResourceA is registered") - ); } #[test] fn should_use_from_reflect() { #[derive(Resource, Reflect)] - #[reflect(Resource, Component)] + #[reflect(Resource)] struct SomeType(i32); let mut world = World::default(); diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index af0037ff4c708..71439acffd51c 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -55,7 +55,6 @@ use { bevy_ecs::{ entity_disabling::{DefaultQueryFilters, Internal}, resource::IsResource, - resource::ResourceEntity, }, }; @@ -74,7 +73,7 @@ impl Plugin for ScenePlugin { .register_type::() .register_type::() .register_type::() - .register_type::>() + .register_type::() .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); // Register component hooks for DynamicSceneRoot diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 5bdfc3268ab3d..0196328af7eae 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -848,7 +848,8 @@ mod tests { #[reflect(Component)] struct ComponentF; - #[derive(Resource, Default)] + #[derive(Resource, Default, Reflect)] + #[reflect(Component)] struct TriggerCount(u32); fn setup() -> App { diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index ed90ff5d753d2..dc58d17d2beb8 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -1,7 +1,6 @@ //! `serde` serialization and deserialization implementation for Bevy scenes. use crate::{DynamicEntity, DynamicScene}; -use bevy_ecs::component::ComponentId; use bevy_ecs::entity::Entity; use bevy_platform::collections::HashSet; use bevy_reflect::{ @@ -30,7 +29,7 @@ pub const ENTITY_STRUCT: &str = "Entity"; /// Name of the serialized resource struct type. pub const RESOURCE_STRUCT: &str = "Resource"; /// Name of the serialized resource id. -pub const RESOURCE_ID: &str = "Resource ID"; +pub const RESOURCE_ID: &str = "resourceid"; /// Name of the serialized component field in an entity struct. pub const ENTITY_FIELD_COMPONENTS: &str = "components"; @@ -86,8 +85,8 @@ impl<'a> Serialize for SceneSerializer<'a> { let mut state = serializer.serialize_struct(SCENE_STRUCT, 2)?; state.serialize_field( SCENE_RESOURCES, - &ResourcesSerializer { - resource_entities: &self.scene.resources, + &EntitiesSerializer { + entities: &self.scene.resources, registry: self.registry, }, )?; @@ -129,34 +128,6 @@ impl<'a> Serialize for EntitiesSerializer<'a> { } } -/// Handles serialization of multiple resources. -pub struct ResourcesSerializer<'a> { - /// The resource entity to serialize. - pub resource_entities: &'a [(ComponentId, DynamicEntity)], - /// Type registry in which the component types used by the entity are registered. - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for ResourcesSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_map(Some(self.resource_entities.len()))?; - for (resource_id, entity) in self.resource_entities { - state.serialize_entry( - &entity.entity, - &ResourceSerializer { - resource_id, - entity, - registry: self.registry, - }, - )?; - } - state.end() - } -} - /// Handles entity serialization as a map of component type to component value. pub struct EntitySerializer<'a> { /// The entity to serialize. @@ -182,34 +153,6 @@ impl<'a> Serialize for EntitySerializer<'a> { } } -/// Handles entity serialization as a map of component type to component value. -pub struct ResourceSerializer<'a> { - /// The [`ComponentId`] of the resource - pub resource_id: &'a ComponentId, - /// The entity to serialize. - pub entity: &'a DynamicEntity, - /// Type registry in which the component types used by the entity are registered. - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for ResourceSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let mut state = serializer.serialize_struct(RESOURCE_STRUCT, 2)?; - state.serialize_field(RESOURCE_ID, &self.resource_id.index())?; - state.serialize_field( - ENTITY_FIELD_COMPONENTS, - &EntitySerializer { - entity: self.entity, - registry: self.registry, - }, - )?; - state.end() - } -} - /// Handles serializing a list of values with a unique type as a map of type to value. /// /// Used to serialize scene resources in [`SceneSerializer`] and entity components in [`EntitySerializer`]. @@ -314,7 +257,7 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> { A: SeqAccess<'de>, { let resources = seq - .next_element_seed(SceneResourcesDeserializer { + .next_element_seed(SceneEntitiesDeserializer { type_registry: self.type_registry, })? .ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?; @@ -343,7 +286,7 @@ impl<'a, 'de> Visitor<'de> for SceneVisitor<'a> { if resources.is_some() { return Err(Error::duplicate_field(SCENE_RESOURCES)); } - resources = Some(map.next_value_seed(SceneResourcesDeserializer { + resources = Some(map.next_value_seed(SceneEntitiesDeserializer { type_registry: self.type_registry, })?); } @@ -415,53 +358,6 @@ impl<'a, 'de> Visitor<'de> for SceneEntitiesVisitor<'a> { } } -/// Handles deserialization for a collection of resources. -pub struct SceneResourcesDeserializer<'a> { - /// Type registry in which the component types used by the entities to deserialize are registered. - pub type_registry: &'a TypeRegistry, -} - -impl<'a, 'de> DeserializeSeed<'de> for SceneResourcesDeserializer<'a> { - type Value = Vec<(ComponentId, DynamicEntity)>; - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(SceneResourcesVisitor { - type_registry: self.type_registry, - }) - } -} - -struct SceneResourcesVisitor<'a> { - pub type_registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for SceneResourcesVisitor<'a> { - type Value = Vec<(ComponentId, DynamicEntity)>; - - fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { - formatter.write_str("map of resources") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut resources = Vec::new(); - while let Some(entity) = map.next_key::()? { - let entity = map.next_value_seed(SceneResourceDeserializer { - entity, - type_registry: self.type_registry, - })?; - resources.push(entity); - } - - Ok(resources) - } -} - /// Handle deserialization of an entity and its components. pub struct SceneEntityDeserializer<'a> { /// Id of the deserialized entity. @@ -545,97 +441,6 @@ impl<'a, 'de> Visitor<'de> for SceneEntityVisitor<'a> { } } -/// Handle deserialization of a resource. -pub struct SceneResourceDeserializer<'a> { - /// Id of the deserialized resource. - pub entity: Entity, - /// Type registry in which the component types used by the resource to deserialize are registered. - pub type_registry: &'a TypeRegistry, -} - -impl<'a, 'de> DeserializeSeed<'de> for SceneResourceDeserializer<'a> { - type Value = (ComponentId, DynamicEntity); - - fn deserialize(self, deserializer: D) -> Result - where - D: Deserializer<'de>, - { - deserializer.deserialize_struct( - RESOURCE_STRUCT, - &[RESOURCE_ID, ENTITY_FIELD_COMPONENTS], - SceneResourceVisitor { - entity: self.entity, - registry: self.type_registry, - }, - ) - } -} - -struct SceneResourceVisitor<'a> { - pub entity: Entity, - pub registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for SceneResourceVisitor<'a> { - type Value = (ComponentId, DynamicEntity); - - fn expecting(&self, formatter: &mut Formatter) -> core::fmt::Result { - formatter.write_str("resources") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let resource_id = ComponentId::new( - seq.next_element::()? - .ok_or_else(|| Error::missing_field(RESOURCE_ID))?, - ); - let dynamic_entity = seq - .next_element_seed(SceneEntityDeserializer { - entity: self.entity, - type_registry: self.registry, - })? - .ok_or_else(|| Error::missing_field(ENTITY_STRUCT))?; - - Ok((resource_id, dynamic_entity)) - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut resource_id = None; - let mut entity = None; - while let Some(key) = map.next_key()? { - match key { - ResourceField::ResourceId => { - if resource_id.is_some() { - return Err(Error::duplicate_field(RESOURCE_ID)); - } - - resource_id = Some(ComponentId::new(map.next_value()?)); - } - ResourceField::Entity => { - if entity.is_some() { - return Err(Error::duplicate_field(ENTITY_STRUCT)); - } - - entity = Some(map.next_value_seed(SceneEntityDeserializer { - entity: self.entity, - type_registry: self.registry, - })?); - } - } - } - - let resource_id = resource_id.ok_or_else(|| Error::missing_field(RESOURCE_ID))?; - let entity = entity.ok_or_else(|| Error::missing_field(ENTITY_STRUCT))?; - - Ok((resource_id, entity)) - } -} - /// Handles deserialization of a sequence of values with unique types. pub struct SceneMapDeserializer<'a> { /// Type registry in which the types of the values to deserialize are registered. @@ -722,11 +527,10 @@ mod tests { }; use bevy_ecs::{ entity::{Entity, EntityHashMap}, - entity_disabling::DefaultQueryFilters, prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, query::{With, Without}, reflect::AppTypeRegistry, - resource::{IsResource, ResourceEntity}, + resource::IsResource, world::FromWorld, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; @@ -820,7 +624,6 @@ mod tests { registry.register::(); registry.register::(); registry.register::(); - registry.register::>(); } world.insert_resource(registry); world @@ -843,8 +646,13 @@ mod tests { let expected = r#"( resources: { - "bevy_scene::serde::tests::MyResource": ( - foo: 123, + 4294967290: ( + components: { + "bevy_ecs::resource::IsResource": (), + "bevy_scene::serde::tests::MyResource": ( + foo: 123, + ), + }, ), }, entities: { @@ -880,8 +688,13 @@ mod tests { let input = r#"( resources: { - "bevy_scene::serde::tests::MyResource": ( - foo: 123, + 8589934588: ( + components: { + "bevy_ecs::resource::IsResource": (), + "bevy_scene::serde::tests::MyResource": ( + foo: 123, + ), + }, ), }, entities: { @@ -967,7 +780,7 @@ mod tests { .write_to_world(&mut dst_world, &mut map) .unwrap(); - assert_eq!(4, deserialized_scene.entities.len()); + assert_eq!(2, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); let bar_to_foo = dst_world @@ -995,7 +808,7 @@ mod tests { let (scene, deserialized_scene) = roundtrip_ron(&world); - assert_eq!(3, deserialized_scene.entities.len()); // 1 entity and 2 resources + assert_eq!(1, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); let mut world = create_world(); @@ -1025,19 +838,10 @@ mod tests { assert_eq!( vec![ - 0, 3, 253, 255, 255, 255, 15, 1, 37, 98, 101, 118, 121, 95, 115, 99, 101, 110, 101, + 0, 1, 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, 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, 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 + 108, 64, 1, 12, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 ], serialized_scene ); @@ -1049,7 +853,7 @@ mod tests { .deserialize(&mut postcard::Deserializer::from_bytes(&serialized_scene)) .unwrap(); - assert_eq!(3, deserialized_scene.entities.len()); + assert_eq!(1, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); } @@ -1075,21 +879,11 @@ mod tests { assert_eq!( vec![ - 146, 128, 131, 206, 255, 255, 255, 253, 145, 129, 217, 37, 98, 101, 118, 121, 95, + 146, 128, 129, 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, 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, - 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 + 108, 101, 172, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33 ], buf ); @@ -1103,7 +897,7 @@ mod tests { .deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) .unwrap(); - assert_eq!(3, deserialized_scene.entities.len()); + assert_eq!(1, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); } @@ -1128,23 +922,13 @@ mod tests { assert_eq!( vec![ - 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, 0, 1, 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, 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, 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, - 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 + 100, 33 ], serialized_scene ); @@ -1157,7 +941,7 @@ mod tests { bincode::serde::seed_decode_from_slice(scene_deserializer, &serialized_scene, config) .unwrap(); - assert_eq!(3, deserialized_scene.entities.len()); + assert_eq!(1, deserialized_scene.entities.len()); assert_scene_eq(&scene, &deserialized_scene); } diff --git a/crates/bevy_state/src/state/resources.rs b/crates/bevy_state/src/state/resources.rs index 4bbe6d1b1f24e..bbd1ce36a21a9 100644 --- a/crates/bevy_state/src/state/resources.rs +++ b/crates/bevy_state/src/state/resources.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ use super::{freely_mutable_state::FreelyMutableState, states::States}; #[cfg(feature = "bevy_reflect")] -use bevy_ecs::prelude::ReflectResource; +use bevy_ecs::prelude::{ReflectComponent, ReflectResource}; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::ReflectDefault; diff --git a/crates/bevy_time/src/time.rs b/crates/bevy_time/src/time.rs index 1494afaadfddc..7977bca2e1a88 100644 --- a/crates/bevy_time/src/time.rs +++ b/crates/bevy_time/src/time.rs @@ -2,7 +2,7 @@ use bevy_ecs::resource::Resource; use core::time::Duration; #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::ReflectResource, + bevy_ecs::reflect::{ReflectComponent, ReflectResource}, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; From 3ddf8bfdc92890475afc35a7293dfb5bf725edb9 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 23 Sep 2025 14:52:28 +0200 Subject: [PATCH 33/69] fixed final tests --- crates/bevy_scene/src/dynamic_scene_builder.rs | 2 +- crates/bevy_scene/src/lib.rs | 4 ++++ crates/bevy_scene/src/scene_spawner.rs | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index c974ae4dcbf21..cf5ddd0d63bf7 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -327,7 +327,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// ``` /// # use bevy_scene::DynamicSceneBuilder; /// # use bevy_ecs::reflect::AppTypeRegistry; - /// # use bevy_ecs::prelude::{ReflectResource, Resource, World}; + /// # use bevy_ecs::prelude::{ReflectResource, ReflectComponent, Resource, World}; /// # use bevy_reflect::Reflect; /// #[derive(Resource, Default, Reflect)] /// #[reflect(Resource)] diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 71439acffd51c..52c7fe5262f4c 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -171,6 +171,8 @@ mod tests { let mut app = App::new(); app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -297,6 +299,8 @@ mod tests { let mut app = App::new(); app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 0196328af7eae..d1b9535b8970c 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -740,6 +740,7 @@ mod tests { app.add_plugins(ScheduleRunnerPlugin::default()) .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin); + app.register_type::(); app.update(); let mut scene_world = World::new(); @@ -773,7 +774,7 @@ mod tests { assert_eq!(scene_component_a.y, 4.0); assert_eq!( app.world().entity(entity).get::().unwrap().len(), - 3 // two resources-as-entities are also counted + 1 ); // let's try to delete the scene @@ -1063,6 +1064,8 @@ mod tests { .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::() + .register_type::() + .register_type::() .register_type::(); app.update(); From f0e973aea08ce5b052a0b24d6fe039984939a5ac Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 23 Sep 2025 15:51:09 +0200 Subject: [PATCH 34/69] cargo clippy fixes --- crates/bevy_ecs/src/world/mod.rs | 6 +++--- crates/bevy_scene/src/dynamic_scene_builder.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 03d8d132b0f2e..367e628793b49 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1714,7 +1714,7 @@ impl World { let entity = self .spawn_with_caller(value, caller) - .insert(IsResource::default()) + .insert(IsResource) .id(); self.resource_entities.insert(component_id, entity); } @@ -1748,7 +1748,7 @@ impl World { let entity = self .spawn_with_caller(value, caller) - .insert(IsResource::default()) + .insert(IsResource) .id(); self.resource_entities.insert(component_id, entity); } else { @@ -2707,7 +2707,7 @@ impl World { caller: MaybeLocation, ) { if !self.resource_entities.contains_key(&component_id) { - let is_resource = IsResource::default(); + let is_resource = IsResource; move_as_ptr!(is_resource); // Since we don't know the type, we use a placeholder type. diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index cf5ddd0d63bf7..fb5cfe1d76b35 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -369,7 +369,7 @@ impl<'w> DynamicSceneBuilder<'w> { .original_world .components() .get_info(*resource_id) - .and_then(|info| info.type_id()) + .and_then(bevy_ecs::component::ComponentInfo::type_id) .is_some_and(|type_id| self.resource_filter.is_denied_by_id(type_id)) { continue; From 8c1c8186d40a1db00775d7c3ad1befcfa70d56dc Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 23 Sep 2025 16:23:42 +0200 Subject: [PATCH 35/69] removed dead code --- crates/bevy_scene/src/serde.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index dc58d17d2beb8..c55d453d32ef1 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -26,10 +26,6 @@ pub const SCENE_ENTITIES: &str = "entities"; /// Name of the serialized entity struct type. pub const ENTITY_STRUCT: &str = "Entity"; -/// Name of the serialized resource struct type. -pub const RESOURCE_STRUCT: &str = "Resource"; -/// Name of the serialized resource id. -pub const RESOURCE_ID: &str = "resourceid"; /// Name of the serialized component field in an entity struct. pub const ENTITY_FIELD_COMPONENTS: &str = "components"; @@ -211,13 +207,6 @@ enum EntityField { Components, } -#[derive(Deserialize)] -#[serde(field_identifier, rename_all = "lowercase")] -enum ResourceField { - ResourceId, - Entity, -} - /// Handles scene deserialization. pub struct SceneDeserializer<'a> { /// Type registry in which the components and resources types used in the scene to deserialize are registered. From 1383efd08aa30324870f918e5b15baffa92608ba Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 23 Sep 2025 16:24:44 +0200 Subject: [PATCH 36/69] typo --- crates/bevy_ecs/src/world/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 367e628793b49..1463c63525b3d 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2141,7 +2141,7 @@ impl World { let value = R::from_world(self); self.insert_resource_with_caller(value, caller); } - // SAFETY: The resouce either exists or we've just created it. + // SAFETY: The resource either exists or we've just created it. unsafe { self.get_resource_mut::().debug_checked_unwrap() } } From 2f9990a1cdd21feb6a8faab308d729818366db23 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 23 Sep 2025 16:41:27 +0200 Subject: [PATCH 37/69] more clippy --- benches/benches/bevy_ecs/scheduling/run_condition.rs | 2 +- examples/games/game_menu.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/benches/bevy_ecs/scheduling/run_condition.rs b/benches/benches/bevy_ecs/scheduling/run_condition.rs index 9c40cf396e047..4f401f094db48 100644 --- a/benches/benches/bevy_ecs/scheduling/run_condition.rs +++ b/benches/benches/bevy_ecs/scheduling/run_condition.rs @@ -55,7 +55,7 @@ pub fn run_condition_no(criterion: &mut Criterion) { group.finish(); } -#[derive(Component, Resource)] +#[derive(Resource)] struct TestBool(pub bool); pub fn run_condition_yes_with_query(criterion: &mut Criterion) { diff --git a/examples/games/game_menu.rs b/examples/games/game_menu.rs index 59a09cf319dbb..3fa5073cfa376 100644 --- a/examples/games/game_menu.rs +++ b/examples/games/game_menu.rs @@ -15,7 +15,7 @@ enum GameState { } // One of the two settings that can be set through the menu. It will be a resource in the app -#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)] +#[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)] enum DisplayQuality { Low, Medium, @@ -23,7 +23,7 @@ enum DisplayQuality { } // One of the two settings that can be set through the menu. It will be a resource in the app -#[derive(Resource, Debug, Component, PartialEq, Eq, Clone, Copy)] +#[derive(Resource, Debug, PartialEq, Eq, Clone, Copy)] struct Volume(u32); fn main() { From 678da5395b1085527d9ea1791c2fd4cbcb9d59e5 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 15:26:20 +0200 Subject: [PATCH 38/69] fixed get_resources_mut and unsoundness Res and ResMut --- crates/bevy_ecs/src/change_detection.rs | 20 +++ crates/bevy_ecs/src/resource.rs | 3 - crates/bevy_ecs/src/system/system_param.rs | 161 ++++++++++++--------- crates/bevy_ecs/src/world/mod.rs | 27 ++-- 4 files changed, 127 insertions(+), 84 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 12d89b464cf5d..5d323c4684963 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -675,6 +675,16 @@ impl<'w, T: Resource> From> for Res<'w, T> { } } +impl<'w, T: Resource> From> for Res<'w, T> { + fn from(item: Ref<'w, T>) -> Res<'w, T> { + Res { + value: item.value, + ticks: item.ticks, + changed_by: item.changed_by, + } + } +} + impl<'w, T: Resource> From> for Ref<'w, T> { /// Convert a `Res` into a `Ref`. This allows keeping the change-detection feature of `Ref` /// while losing the specificity of `Res` for resources. @@ -747,6 +757,16 @@ change_detection_mut_impl!(ResMut<'w, T>, T, Resource); impl_methods!(ResMut<'w, T>, T, Resource); impl_debug!(ResMut<'w, T>, Resource); +impl<'w, T: Resource> From> for ResMut<'w, T> { + fn from(item: Mut<'w, T>) -> ResMut<'w, T> { + ResMut { + value: item.value, + ticks: item.ticks, + changed_by: item.changed_by, + } + } +} + impl<'w, T: Resource> From> for Mut<'w, T> { /// Convert this `ResMut` into a `Mut`. This allows keeping the change-detection feature of `Mut` /// while losing the specificity of `ResMut` for resources. diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index d5c618fcfc46c..102caf33fb0d5 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -81,9 +81,6 @@ pub use bevy_ecs_macros::Resource; pub trait Resource: Component {} /// A marker component for entities which store resources. -/// -/// By contrast, the [`ResourceEntity`] component is used to find the entity that stores a particular resource. -/// This component is required by the [`ResourceEntity`] component, and will automatically be added. #[cfg_attr( feature = "bevy_reflect", derive(Reflect), diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index f1418d70f8060..852c1db6ecf58 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -2,9 +2,11 @@ pub use crate::change_detection::{NonSendMut, Res, ResMut}; use crate::{ archetype::Archetypes, bundle::Bundles, - change_detection::{MaybeLocation, Ticks, TicksMut}, + change_detection::{MaybeLocation, TicksMut}, component::{ComponentId, ComponentTicks, Components, Tick}, entity::Entities, + entity_disabling::Internal, + prelude::{Mut, Ref, With}, query::{ Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, QueryState, ReadOnlyQueryData, @@ -752,22 +754,28 @@ all_tuples_enumerated!(impl_param_set, 1, 8, P, p); // SAFETY: Res only reads a single World resource unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} -// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. +// SAFETY: ResMut component access is tied to the singleton components under +// QueryState, With>, which guarentees safety. unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { - type State = ComponentId; + type State = (ComponentId, QueryState, With>); type Item<'w, 's> = Res<'w, T>; fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_resource::() + ( + world.components_registrator().register_resource::(), + QueryState::, With>::from_world(world), + ) } fn init_access( - &component_id: &Self::State, + state: &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, - _world: &mut World, + world: &mut World, ) { + Query::init_access(&state.1, system_meta, component_access_set, world); + let component_id = state.0; + let combined_access = component_access_set.combined_access(); assert!( !combined_access.has_resource_write(component_id), @@ -779,68 +787,77 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { component_access_set.add_unfiltered_resource_read(component_id); } + // TODO: Defer this to Single<_> in some way. #[inline] unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, + state: &mut Self::State, + system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. - if world.world().contains_resource_by_id(component_id) { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( + state.1.update_archetypes_unsafe_world_cell(world); + // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere + // and the query is read only. + // The caller ensures the world matches the one used in init_state. + let query = unsafe { + state.1.query_unchecked_manual_with_ticks( + world, + system_meta.last_run, + world.change_tick(), + ) + }; + match query.single_inner() { + Ok(_) => Ok(()), + Err(_) => Err(SystemParamValidationError::invalid::( "Resource does not exist", - )) + )), } } #[inline] unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, + state: &'s mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - let (ptr, ticks, caller) = - world - .get_resource_with_ticks(component_id) - .unwrap_or_else(|| { - panic!( - "Resource requested by {} does not exist: {}", - system_meta.name, - DebugName::type_name::() - ); - }); - Res { - value: ptr.deref(), - ticks: Ticks { - added: ticks.added.deref(), - changed: ticks.changed.deref(), - last_run: system_meta.last_run, - this_run: change_tick, - }, - changed_by: caller.map(|caller| caller.deref()), - } + state.1.update_archetypes_unsafe_world_cell(world); + // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. + // The caller ensures the world matches the one used in init_state. + let query = unsafe { + state + .1 + .query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + }; + return Res::from( + query + .single_inner() + .expect("The query was expected to contain exactly one matching entity."), + ); } } -// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. +// SAFETY: ResMut component access is tied to the singleton components under +// QueryState, With>, which guarentees safety. unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { - type State = ComponentId; + type State = (ComponentId, QueryState, With>); type Item<'w, 's> = ResMut<'w, T>; fn init_state(world: &mut World) -> Self::State { - world.components_registrator().register_resource::() + ( + world.components_registrator().register_resource::(), + QueryState::, With>::from_world(world), + ) } fn init_access( - &component_id: &Self::State, + state: &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, - _world: &mut World, + world: &mut World, ) { + Query::init_access(&state.1, system_meta, component_access_set, world); + let component_id = state.0; + let combined_access = component_access_set.combined_access(); if combined_access.has_resource_write(component_id) { panic!( @@ -854,48 +871,52 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { component_access_set.add_unfiltered_resource_write(component_id); } + // TODO: Defer this to Single<_> in some way. #[inline] unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, + state: &mut Self::State, + system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. - if world.world().contains_resource_by_id(component_id) { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( + state.1.update_archetypes_unsafe_world_cell(world); + // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere + // and the query is read only. + // The caller ensures the world matches the one used in init_state. + let query = unsafe { + state.1.query_unchecked_manual_with_ticks( + world, + system_meta.last_run, + world.change_tick(), + ) + }; + match query.single_inner() { + Ok(_) => Ok(()), + Err(_) => Err(SystemParamValidationError::invalid::( "Resource does not exist", - )) + )), } } #[inline] unsafe fn get_param<'w, 's>( - &mut component_id: &'s mut Self::State, + state: &'s mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - let value = world - .get_resource_mut_by_id(component_id) - .unwrap_or_else(|| { - panic!( - "Resource requested by {} does not exist: {}", - system_meta.name, - DebugName::type_name::() - ); - }); - ResMut { - value: value.value.deref_mut::(), - ticks: TicksMut { - added: value.ticks.added, - changed: value.ticks.changed, - last_run: system_meta.last_run, - this_run: change_tick, - }, - changed_by: value.changed_by, - } + state.1.update_archetypes_unsafe_world_cell(world); + // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. + // The caller ensures the world matches the one used in init_state. + let query = unsafe { + state + .1 + .query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + }; + return ResMut::from( + query + .single_inner() + .expect("The query was expected to contain exactly one matching entity."), + ); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 1463c63525b3d..da788e38d7970 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1748,7 +1748,7 @@ impl World { let entity = self .spawn_with_caller(value, caller) - .insert(IsResource) + .insert((IsResource, Internal)) .id(); self.resource_entities.insert(component_id, entity); } else { @@ -3330,21 +3330,26 @@ impl World { /// # assert_eq!(world.resource::().0, 3); /// ``` pub fn iter_resources_mut(&mut self) -> impl Iterator)> { - self.resource_entities + let unsafe_world = self.as_unsafe_world_cell(); + let resource_entities = unsafe_world.resource_entities(); + let components = unsafe_world.components(); + + resource_entities .iter() .map(|(component_id, entity)| (*component_id, *entity)) - .filter_map(|(component_id, entity)| { + .filter_map(move |(component_id, entity)| { // SAFETY: If a resource has been initialized, a corresponding ComponentInfo must exist with its ID. - let component_info = unsafe { - self.components - .get_info(component_id) - .debug_checked_unwrap() - }; + let component_info = + unsafe { components.get_info(component_id).debug_checked_unwrap() }; - let world = self.as_unsafe_world_cell_readonly(); - let entity_cell = world.get_entity(entity).ok()?; + let entity_cell = unsafe_world.get_entity(entity).ok()?; - // SAFETY: We have exclusive world access and we don't iterate any entity twice + // SAFETY: + // - We have exclusive world access + // - `UnsafeEntityCell::get_mut_by_id` doesn't access components + // or resource_entities mutably + // - `resource_entities` doesn't contain duplicate entities, so + // no duplicate references are created let mut_untyped = unsafe { entity_cell.get_mut_by_id(component_id).ok()? }; Some((component_info, mut_untyped)) From 67810f4f4f50a4ef731477d396b4df7e86e977d2 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 16:28:22 +0200 Subject: [PATCH 39/69] updated docs --- crates/bevy_ecs/src/world/mod.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index da788e38d7970..07c5f51eec5bc 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -95,6 +95,10 @@ pub struct World { pub(crate) component_ids: ComponentIds, /// A lookup for the entities on which resources are stored. /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs + /// A resource exists if all of the below is true: + /// 1. resource_entities has an entry for the [`ComponentId`] associated with the resource. + /// 2. The entity associated with the resource has three components: the resource (as a component), [`IsResource`], and [`Internal`]. + /// If the resource does not exists, none of the above must be true, any inbetween state is invalid. pub resource_entities: HashMap, pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, @@ -1748,7 +1752,7 @@ impl World { let entity = self .spawn_with_caller(value, caller) - .insert((IsResource, Internal)) + .insert(IsResource) .id(); self.resource_entities.insert(component_id, entity); } else { @@ -1869,7 +1873,9 @@ impl World { if let Some(entity) = self.resource_entities.get(&component_id) && let Ok(entity_ref) = self.get_entity(*entity) { - return entity_ref.contains_id(component_id); + return entity_ref.contains_id(component_id) + && entity_ref.contains::() + && entity_ref.contains::(); } false } @@ -2602,17 +2608,19 @@ impl World { changed_by: caller.as_mut(), }; - // SAFETY: We 'assume' that the function doesn't move the resource entity around. - let world = unsafe { entity_mut.world_mut() }; + // to fully remove the resource, we remove the entity and the resource_entities entry + entity_mut.despawn(); + self.resource_entities.remove(&component_id); - let result = f(world, value_mut); - assert!(!world.contains_resource::(), + let result = f(self, value_mut); + assert!(!self.contains_resource::(), "Resource `{}` was inserted during a call to World::resource_scope.\n\ This is not allowed as the original resource is reinserted to the world after the closure is invoked.", DebugName::type_name::()); move_as_ptr!(value); + let mut entity_mut = self.spawn(IsResource); entity_mut.insert_with_caller( value, InsertMode::Replace, @@ -2624,6 +2632,9 @@ impl World { entity_mut.get_mut::()?.set_last_added(ticks.added); entity_mut.get_mut::()?.set_last_changed(ticks.changed); + let entity = entity_mut.id(); + self.resource_entities.insert(component_id, entity); + Some(result) } From af03cec18d2e54f42f23a9866318f0e8db5356ce Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 16:46:26 +0200 Subject: [PATCH 40/69] improve docs and name --- crates/bevy_ecs/src/world/entity_ref.rs | 16 +++++--- crates/bevy_ecs/src/world/mod.rs | 2 +- .../bevy_ecs/src/world/unsafe_world_cell.rs | 41 ++++--------------- 3 files changed, 19 insertions(+), 40 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d6f91aa22d11c..043f6a17ec373 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -146,11 +146,13 @@ impl<'w> EntityRef<'w> { unsafe { self.cell.get_change_ticks::() } } - /// Retrieves the caller information for the given component. + /// Get the [`MaybeLocation`] for a [`Component`]. + /// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. + /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. #[inline] - pub fn get_caller(&self) -> Option { + pub fn get_changed_by(&self) -> Option { // SAFETY: We have read-only access to all components of this entity. - unsafe { self.cell.get_caller::() } + unsafe { self.cell.get_changed_by::() } } /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change @@ -1749,14 +1751,16 @@ impl<'w> EntityWorldMut<'w> { self.as_readonly().get_change_ticks::() } - /// Retrieves the caller information for the given component. + /// Get the [`MaybeLocation`] for a [`Component`]. + /// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. + /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. /// /// # Panics /// /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[inline] - pub fn get_caller(&self) -> Option { - self.as_readonly().get_caller::() + pub fn get_changed_by(&self) -> Option { + self.as_readonly().get_changed_by::() } /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 07c5f51eec5bc..8f7509b9576f2 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2594,7 +2594,7 @@ impl World { let mut entity_mut = self.get_entity_mut(*entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; - let mut caller = entity_mut.get_caller::()?; + let mut caller = entity_mut.get_changed_by::()?; let mut value = entity_mut.take::()?; let value_mut = Mut { diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 65f1d7caf26bf..138591a9c7223 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -889,21 +889,23 @@ impl<'w> UnsafeEntityCell<'w> { } } - /// Retrieves the caller information for the given component. + /// Get the [`MaybeLocation`] for a [`Component`]. + /// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. + /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. /// /// # Safety /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component /// - no other mutable references to the component exist at the same time #[inline] - pub unsafe fn get_caller(self) -> Option { + pub unsafe fn get_changed_by(self) -> Option { let component_id = self.world.components().get_valid_id(TypeId::of::())?; // SAFETY: // - entity location is valid // - proper world access is promised by caller unsafe { - get_caller( + get_changed_by( self.world, component_id, T::STORAGE_TYPE, @@ -945,34 +947,6 @@ impl<'w> UnsafeEntityCell<'w> { } } - /// Retrieves the caller information for the given [`ComponentId`]. - /// - /// **You should prefer to use the typed API [`UnsafeEntityCell::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// # Safety - /// It is the caller's responsibility to ensure that - /// - the [`UnsafeEntityCell`] has permission to access the component - /// - no other mutable references to the component exist at the same time - #[inline] - pub unsafe fn get_caller_by_id(&self, component_id: ComponentId) -> Option { - let info = self.world.components().get_info(component_id)?; - // SAFETY: - // - entity location and entity is valid - // - world access is immutable, lifetime tied to `&self` - // - the storage type provided is correct for T - unsafe { - get_caller( - self.world, - component_id, - info.storage_type(), - self.entity, - self.location, - ) - } - } - /// # Safety /// It is the caller's responsibility to ensure that /// - the [`UnsafeEntityCell`] has permission to access the component mutably @@ -1353,7 +1327,8 @@ unsafe fn get_ticks( } } -/// Get the caller information for a [`Component`] on a particular [`Entity`] +/// Get the [`MaybeLocation`] for a [`Component`] on a particular [`Entity`]. +/// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. /// /// # Safety /// - `location` must refer to an archetype that contains `entity` @@ -1362,7 +1337,7 @@ unsafe fn get_ticks( /// - `storage_type` must accurately reflect where the components for `component_id` are stored. /// - the caller must ensure that no aliasing rules are violated #[inline] -unsafe fn get_caller( +unsafe fn get_changed_by( world: UnsafeWorldCell<'_>, component_id: ComponentId, storage_type: StorageType, From 25b49d0a85f655a90da5148cb07b19fbf3a2afe3 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 16:52:27 +0200 Subject: [PATCH 41/69] cargo clippy --- crates/bevy_ecs/src/system/system_param.rs | 8 ++++---- crates/bevy_ecs/src/world/mod.rs | 9 ++++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 852c1db6ecf58..5d46066287318 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -828,11 +828,11 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { .1 .query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) }; - return Res::from( + Res::from( query .single_inner() .expect("The query was expected to contain exactly one matching entity."), - ); + ) } } @@ -912,11 +912,11 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { .1 .query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) }; - return ResMut::from( + ResMut::from( query .single_inner() .expect("The query was expected to contain exactly one matching entity."), - ); + ) } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 8f7509b9576f2..4f2836ab08bf8 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -85,8 +85,9 @@ use unsafe_world_cell::{UnsafeEntityCell, UnsafeWorldCell}; /// ## Resources /// /// Worlds can also store [`Resource`]s, -/// which are unique instances of a given type that don't belong to a specific Entity. +/// which are unique instances of a given type that belong to a specific unique Entity. /// There are also *non send resources*, which can only be accessed on the main thread. +/// These are stored outside of the ECS. /// See [`Resource`] for usage. pub struct World { id: WorldId, @@ -96,8 +97,10 @@ pub struct World { /// A lookup for the entities on which resources are stored. /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs /// A resource exists if all of the below is true: - /// 1. resource_entities has an entry for the [`ComponentId`] associated with the resource. - /// 2. The entity associated with the resource has three components: the resource (as a component), [`IsResource`], and [`Internal`]. + /// 1. `resource_entities` has an entry for the [`ComponentId`] associated with the resource. + /// 2. The entity associated with the resource has three components: + /// the resource (as a component), [`IsResource`], and [`Internal`]. + /// /// If the resource does not exists, none of the above must be true, any inbetween state is invalid. pub resource_entities: HashMap, pub(crate) archetypes: Archetypes, From 1117cfa894b4cab1c240e04919c2e5ddebebb7c1 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 16:56:54 +0200 Subject: [PATCH 42/69] typos --- crates/bevy_ecs/src/system/system_param.rs | 4 ++-- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- crates/bevy_ecs/src/world/unsafe_world_cell.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 5d46066287318..f7acccddaabce 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -755,7 +755,7 @@ all_tuples_enumerated!(impl_param_set, 1, 8, P, p); unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} // SAFETY: ResMut component access is tied to the singleton components under -// QueryState, With>, which guarentees safety. +// QueryState, With>, which guarantees safety. unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { type State = (ComponentId, QueryState, With>); type Item<'w, 's> = Res<'w, T>; @@ -837,7 +837,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { } // SAFETY: ResMut component access is tied to the singleton components under -// QueryState, With>, which guarentees safety. +// QueryState, With>, which guarantees safety. unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { type State = (ComponentId, QueryState, With>); type Item<'w, 's> = ResMut<'w, T>; diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 043f6a17ec373..839f466b748b9 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -147,7 +147,7 @@ impl<'w> EntityRef<'w> { } /// Get the [`MaybeLocation`] for a [`Component`]. - /// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. + /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. #[inline] pub fn get_changed_by(&self) -> Option { @@ -1752,7 +1752,7 @@ impl<'w> EntityWorldMut<'w> { } /// Get the [`MaybeLocation`] for a [`Component`]. - /// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. + /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. /// /// # Panics diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 138591a9c7223..c48e270325f47 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -890,7 +890,7 @@ impl<'w> UnsafeEntityCell<'w> { } /// Get the [`MaybeLocation`] for a [`Component`]. - /// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. + /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. /// /// # Safety @@ -1328,7 +1328,7 @@ unsafe fn get_ticks( } /// Get the [`MaybeLocation`] for a [`Component`] on a particular [`Entity`]. -/// This contains information regarding the last place (in code) that changed this component and can be usefull for debugging. +/// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// /// # Safety /// - `location` must refer to an archetype that contains `entity` From e3b12c282920754a34b1a6dee031761bbefc314d Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 17:01:04 +0200 Subject: [PATCH 43/69] fixed bevy_render bug --- crates/bevy_render/src/extract_param.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/src/extract_param.rs b/crates/bevy_render/src/extract_param.rs index e7701843fb2df..a237c36a4a374 100644 --- a/crates/bevy_render/src/extract_param.rs +++ b/crates/bevy_render/src/extract_param.rs @@ -101,7 +101,7 @@ where world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { // SAFETY: Read-only access to world data registered in `init_state`. - let result = unsafe { world.get_resource_by_id(state.main_world_state) }; + let result = unsafe { world.get_resource_by_id(state.main_world_state.0) }; let Some(main_world) = result else { return Err(SystemParamValidationError::invalid::( "`MainWorld` resource does not exist", From 33bd43d10f5b72ec846f04c1a71f98a1503a1ccb Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 17:17:44 +0200 Subject: [PATCH 44/69] fixed bevy_remove --- crates/bevy_remote/src/schemas/json_schema.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_remote/src/schemas/json_schema.rs b/crates/bevy_remote/src/schemas/json_schema.rs index 4e56625bc8eff..f4721d4d0255a 100644 --- a/crates/bevy_remote/src/schemas/json_schema.rs +++ b/crates/bevy_remote/src/schemas/json_schema.rs @@ -396,8 +396,8 @@ mod tests { let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default()); assert!( - !schema.reflect_types.contains(&"Component".to_owned()), - "Should not be a component" + schema.reflect_types.contains(&"Component".to_owned()), + "Should be a component" // Resources are Components ); assert!( schema.reflect_types.contains(&"Resource".to_owned()), @@ -599,6 +599,7 @@ mod tests { "reflectTypes": [ "Resource", "Default", + "Component", ], "kind": "Struct", "type": "object", From c75da2cf7131b48c341b37e8e9cd5709dda8687a Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 17:27:58 +0200 Subject: [PATCH 45/69] added ReflectComponent to debug_overlay --- crates/bevy_ui_render/src/debug_overlay.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ui_render/src/debug_overlay.rs b/crates/bevy_ui_render/src/debug_overlay.rs index b05cfbaa33774..e78f1da86da60 100644 --- a/crates/bevy_ui_render/src/debug_overlay.rs +++ b/crates/bevy_ui_render/src/debug_overlay.rs @@ -8,6 +8,7 @@ use bevy_asset::AssetId; use bevy_camera::visibility::InheritedVisibility; use bevy_color::Hsla; use bevy_ecs::entity::Entity; +use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::prelude::ReflectResource; use bevy_ecs::resource::Resource; use bevy_ecs::system::Commands; From 0e4d5c2b6cb9473fe0de93f1fdd69504158a069f Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Thu, 25 Sep 2025 17:34:17 +0200 Subject: [PATCH 46/69] fixed docs --- crates/bevy_scene/src/dynamic_scene_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index fb5cfe1d76b35..7fb77d92f5dd3 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -26,7 +26,7 @@ use bevy_utils::default; /// /// # Resource Extraction /// -/// By default, all resources registered with [`ReflectResource`] type data in a world's [`AppTypeRegistry`] will be extracted. +/// By default, all resources are extracted identically to entities. The resources must first be gegistered in a world's [`AppTypeRegistry`] will be extracted. /// (this type data is added automatically during registration if [`Reflect`] is derived with the `#[reflect(Resource)]` attribute). /// This can be changed by [specifying a filter](DynamicSceneBuilder::with_resource_filter) or by explicitly /// [allowing](DynamicSceneBuilder::allow_resource)/[denying](DynamicSceneBuilder::deny_resource) certain resources. From 0eef31b5240e36a632b66f88c6548432986fa47f Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 27 Sep 2025 00:29:01 +0200 Subject: [PATCH 47/69] addressed comments --- crates/bevy_ecs/src/change_detection.rs | 20 ---- crates/bevy_ecs/src/system/system_param.rs | 100 +++++++++--------- crates/bevy_ecs/src/world/entity_ref.rs | 4 +- crates/bevy_ecs/src/world/mod.rs | 27 +++-- .../bevy_ecs/src/world/unsafe_world_cell.rs | 2 +- 5 files changed, 70 insertions(+), 83 deletions(-) diff --git a/crates/bevy_ecs/src/change_detection.rs b/crates/bevy_ecs/src/change_detection.rs index 5d323c4684963..12d89b464cf5d 100644 --- a/crates/bevy_ecs/src/change_detection.rs +++ b/crates/bevy_ecs/src/change_detection.rs @@ -675,16 +675,6 @@ impl<'w, T: Resource> From> for Res<'w, T> { } } -impl<'w, T: Resource> From> for Res<'w, T> { - fn from(item: Ref<'w, T>) -> Res<'w, T> { - Res { - value: item.value, - ticks: item.ticks, - changed_by: item.changed_by, - } - } -} - impl<'w, T: Resource> From> for Ref<'w, T> { /// Convert a `Res` into a `Ref`. This allows keeping the change-detection feature of `Ref` /// while losing the specificity of `Res` for resources. @@ -757,16 +747,6 @@ change_detection_mut_impl!(ResMut<'w, T>, T, Resource); impl_methods!(ResMut<'w, T>, T, Resource); impl_debug!(ResMut<'w, T>, Resource); -impl<'w, T: Resource> From> for ResMut<'w, T> { - fn from(item: Mut<'w, T>) -> ResMut<'w, T> { - ResMut { - value: item.value, - ticks: item.ticks, - changed_by: item.changed_by, - } - } -} - impl<'w, T: Resource> From> for Mut<'w, T> { /// Convert this `ResMut` into a `Mut`. This allows keeping the change-detection feature of `Mut` /// while losing the specificity of `ResMut` for resources. diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 7da445d594017..ff0c178ce0ea4 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -754,8 +754,9 @@ all_tuples_enumerated!(impl_param_set, 1, 8, P, p); // SAFETY: Res only reads a single World resource unsafe impl<'a, T: Resource> ReadOnlySystemParam for Res<'a, T> {} -// SAFETY: ResMut component access is tied to the singleton components under -// QueryState, With>, which guarantees safety. +// SAFETY: +// - We register both the necessary component access and the resource access. +// - World accesses follow the registered accesses. unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { type State = (ComponentId, QueryState, With>); type Item<'w, 's> = Res<'w, T>; @@ -763,43 +764,42 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { fn init_state(world: &mut World) -> Self::State { ( world.components_registrator().register_resource::(), - QueryState::, With>::from_world(world), + Query::, With>::init_state(world), ) } fn init_access( - state: &Self::State, + (component_id, query_state): &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, world: &mut World, ) { - Query::init_access(&state.1, system_meta, component_access_set, world); - let component_id = state.0; + Query::init_access(&query_state, system_meta, component_access_set, world); let combined_access = component_access_set.combined_access(); assert!( - !combined_access.has_resource_write(component_id), + !combined_access.has_resource_write(*component_id), "error[B0002]: Res<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", DebugName::type_name::(), system_meta.name, ); - component_access_set.add_unfiltered_resource_read(component_id); + component_access_set.add_unfiltered_resource_read(*component_id); } // TODO: Defer this to Single<_> in some way. #[inline] unsafe fn validate_param( - state: &mut Self::State, + (_, query_state): &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - state.1.update_archetypes_unsafe_world_cell(world); - // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere - // and the query is read only. - // The caller ensures the world matches the one used in init_state. + query_state.update_archetypes_unsafe_world_cell(world); + // SAFETY: + // - This is not a mutable query. + // - This state is genererated by the same world, so the world ids are the same. let query = unsafe { - state.1.query_unchecked_manual_with_ticks( + query_state.query_unchecked_manual_with_ticks( world, system_meta.last_run, world.change_tick(), @@ -815,29 +815,31 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, + (_, query_state): &'s mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - state.1.update_archetypes_unsafe_world_cell(world); + query_state.update_archetypes_unsafe_world_cell(world); // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. // The caller ensures the world matches the one used in init_state. let query = unsafe { - state - .1 - .query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + query_state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) }; - Res::from( - query - .single_inner() - .expect("The query was expected to contain exactly one matching entity."), - ) + let result = query + .single_inner() + .expect("The query was expected to contain exactly one matching entity."); + Res { + value: result.value, + ticks: result.ticks, + changed_by: result.changed_by, + } } } -// SAFETY: ResMut component access is tied to the singleton components under -// QueryState, With>, which guarantees safety. +// SAFETY: +// - We register both the necessary component access and the resource access. +// - World accesses follow the registered accesses. unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { type State = (ComponentId, QueryState, With>); type Item<'w, 's> = ResMut<'w, T>; @@ -845,45 +847,44 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { fn init_state(world: &mut World) -> Self::State { ( world.components_registrator().register_resource::(), - QueryState::, With>::from_world(world), + Query::, With>::init_state(world), ) } fn init_access( - state: &Self::State, + (component_id, query_state): &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, world: &mut World, ) { - Query::init_access(&state.1, system_meta, component_access_set, world); - let component_id = state.0; + Query::init_access(&query_state, system_meta, component_access_set, world); let combined_access = component_access_set.combined_access(); - if combined_access.has_resource_write(component_id) { + if combined_access.has_resource_write(*component_id) { panic!( "error[B0002]: ResMut<{}> in system {} conflicts with a previous ResMut<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", DebugName::type_name::(), system_meta.name); - } else if combined_access.has_resource_read(component_id) { + } else if combined_access.has_resource_read(*component_id) { panic!( "error[B0002]: ResMut<{}> in system {} conflicts with a previous Res<{0}> access. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", DebugName::type_name::(), system_meta.name); } - component_access_set.add_unfiltered_resource_write(component_id); + component_access_set.add_unfiltered_resource_write(*component_id); } // TODO: Defer this to Single<_> in some way. #[inline] unsafe fn validate_param( - state: &mut Self::State, + (_, query_state): &mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - state.1.update_archetypes_unsafe_world_cell(world); - // SAFETY: State ensures that the components it accesses are not mutably accessible elsewhere - // and the query is read only. - // The caller ensures the world matches the one used in init_state. + query_state.update_archetypes_unsafe_world_cell(world); + // SAFETY: + // - We have unique mutable access to the resource. + // - This state is genererated by the same world, so the world ids are the same. let query = unsafe { - state.1.query_unchecked_manual_with_ticks( + query_state.query_unchecked_manual_with_ticks( world, system_meta.last_run, world.change_tick(), @@ -899,24 +900,25 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { #[inline] unsafe fn get_param<'w, 's>( - state: &'s mut Self::State, + (_, query_state): &'s mut Self::State, system_meta: &SystemMeta, world: UnsafeWorldCell<'w>, change_tick: Tick, ) -> Self::Item<'w, 's> { - state.1.update_archetypes_unsafe_world_cell(world); + query_state.update_archetypes_unsafe_world_cell(world); // SAFETY: State ensures that the components it accesses are not accessible somewhere elsewhere. // The caller ensures the world matches the one used in init_state. let query = unsafe { - state - .1 - .query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + query_state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) }; - ResMut::from( - query - .single_inner() - .expect("The query was expected to contain exactly one matching entity."), - ) + let result = query + .single_inner() + .expect("The query was expected to contain exactly one matching entity."); + ResMut { + value: result.value, + ticks: result.ticks, + changed_by: result.changed_by, + } } } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 839f466b748b9..cd53c47e8e7cf 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -146,7 +146,7 @@ impl<'w> EntityRef<'w> { unsafe { self.cell.get_change_ticks::() } } - /// Get the [`MaybeLocation`] for a [`Component`]. + /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. #[inline] @@ -1751,7 +1751,7 @@ impl<'w> EntityWorldMut<'w> { self.as_readonly().get_change_ticks::() } - /// Get the [`MaybeLocation`] for a [`Component`]. + /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. /// diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 4f2836ab08bf8..f300e43dfea62 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1751,12 +1751,11 @@ impl World { let component_id = self.components_registrator().register_resource::(); if !self.resource_entities.contains_key(&component_id) { - move_as_ptr!(value); + // the resource doesn't exist, so we make a new one. + let resource_bundle = (value, IsResource); + move_as_ptr!(resource_bundle); - let entity = self - .spawn_with_caller(value, caller) - .insert(IsResource) - .id(); + let entity = self.spawn_with_caller(resource_bundle, caller).id(); self.resource_entities.insert(component_id, entity); } else { let entity = self.resource_entities.get(&component_id).unwrap(); @@ -1768,6 +1767,11 @@ impl World { caller, RelationshipHookMode::Run, ); + } else { + panic!( + "Resource {} is registered in `resource_entities` but the associated entity does not exist.", + DebugName::type_name::() + ); } } } @@ -2597,7 +2601,7 @@ impl World { let mut entity_mut = self.get_entity_mut(*entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; - let mut caller = entity_mut.get_changed_by::()?; + let mut changed_by = entity_mut.get_changed_by::()?; let mut value = entity_mut.take::()?; let value_mut = Mut { @@ -2608,7 +2612,7 @@ impl World { last_run: last_change_tick, this_run: change_tick, }, - changed_by: caller.as_mut(), + changed_by: changed_by.as_mut(), }; // to fully remove the resource, we remove the entity and the resource_entities entry @@ -2621,13 +2625,14 @@ impl World { This is not allowed as the original resource is reinserted to the world after the closure is invoked.", DebugName::type_name::()); - move_as_ptr!(value); + let mut entity_mut = self.spawn_empty(); + let resource_bundle = (value, IsResource); + move_as_ptr!(resource_bundle); - let mut entity_mut = self.spawn(IsResource); entity_mut.insert_with_caller( - value, + resource_bundle, InsertMode::Replace, - caller, + changed_by, RelationshipHookMode::Skip, ); diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index c48e270325f47..ca4f5df5478b7 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -889,7 +889,7 @@ impl<'w> UnsafeEntityCell<'w> { } } - /// Get the [`MaybeLocation`] for a [`Component`]. + /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. /// From e68d5c2177b248cb0f73adbc4dc76b1cbeb0f6c0 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 27 Sep 2025 02:08:07 +0200 Subject: [PATCH 48/69] fixed a bug with try_resource_scope --- crates/bevy_ecs/src/world/mod.rs | 35 ++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index f300e43dfea62..0f039144bdedd 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2619,18 +2619,26 @@ impl World { entity_mut.despawn(); self.resource_entities.remove(&component_id); + // There may be commands that are registered by the closure, which interact with the resource. + // In make sure that the resource is available, we avoid flushing untill the resource is fully inserted back. + // Since spawn calls flush, we do it before calling the closure, and check afterwards that it was not removed. + // world.flush() is now called at the end of `insert_with_caller()`. + let entity = self.spawn(IsResource).id(); + let result = f(self, value_mut); assert!(!self.contains_resource::(), "Resource `{}` was inserted during a call to World::resource_scope.\n\ This is not allowed as the original resource is reinserted to the world after the closure is invoked.", DebugName::type_name::()); - let mut entity_mut = self.spawn_empty(); - let resource_bundle = (value, IsResource); - move_as_ptr!(resource_bundle); + let mut entity_mut = self + .get_entity_mut(entity) + .expect("Reserved resource entity was destroyed."); + + move_as_ptr!(value); entity_mut.insert_with_caller( - resource_bundle, + value, InsertMode::Replace, changed_by, RelationshipHookMode::Skip, @@ -3692,6 +3700,7 @@ mod tests { component::{ComponentCloneBehavior, ComponentDescriptor, ComponentInfo, StorageType}, entity::EntityHashSet, entity_disabling::{DefaultQueryFilters, Disabled, Internal}, + prelude::{Event, Mut, On, Res}, ptr::OwningPtr, resource::Resource, world::{error::EntityMutableFetchError, DeferredWorld}, @@ -4434,6 +4443,24 @@ mod tests { assert!(world.get_entity(eid).is_err()); } + #[test] + fn resource_query_after_resource_scope() { + #[derive(Event)] + struct EventA; + + #[derive(Resource)] + struct ResourceA; + + let mut world = World::default(); + + world.insert_resource(ResourceA); + world.add_observer(move |_event: On, _res: Res| {}); + world.resource_scope(|world, _res: Mut| { + // since we use commands, this should trigger outside of the resource_scope, so the observer should work. + world.commands().trigger(EventA); + }); + } + #[test] fn entities_and_commands_deferred() { #[derive(Component, PartialEq, Debug)] From 44c526bbd684a630c0752fcb41a54bef3a02693f Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 27 Sep 2025 02:11:58 +0200 Subject: [PATCH 49/69] typos --- crates/bevy_ecs/src/system/system_param.rs | 4 ++-- crates/bevy_ecs/src/world/mod.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index ff0c178ce0ea4..40f254334653e 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -797,7 +797,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { query_state.update_archetypes_unsafe_world_cell(world); // SAFETY: // - This is not a mutable query. - // - This state is genererated by the same world, so the world ids are the same. + // - This state is generated by the same world, so the world ids are the same. let query = unsafe { query_state.query_unchecked_manual_with_ticks( world, @@ -882,7 +882,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { query_state.update_archetypes_unsafe_world_cell(world); // SAFETY: // - We have unique mutable access to the resource. - // - This state is genererated by the same world, so the world ids are the same. + // - This state is generated by the same world, so the world ids are the same. let query = unsafe { query_state.query_unchecked_manual_with_ticks( world, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 0f039144bdedd..bddf4347a76ba 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2620,7 +2620,7 @@ impl World { self.resource_entities.remove(&component_id); // There may be commands that are registered by the closure, which interact with the resource. - // In make sure that the resource is available, we avoid flushing untill the resource is fully inserted back. + // In make sure that the resource is available, we avoid flushing until the resource is fully inserted back. // Since spawn calls flush, we do it before calling the closure, and check afterwards that it was not removed. // world.flush() is now called at the end of `insert_with_caller()`. let entity = self.spawn(IsResource).id(); From fb015225542c0af7e1efd0674e1af3ff09b894e9 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 27 Sep 2025 02:27:02 +0200 Subject: [PATCH 50/69] clippy --- crates/bevy_ecs/src/system/system_param.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 40f254334653e..9243bef2a6303 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -774,7 +774,7 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { component_access_set: &mut FilteredAccessSet, world: &mut World, ) { - Query::init_access(&query_state, system_meta, component_access_set, world); + Query::init_access(query_state, system_meta, component_access_set, world); let combined_access = component_access_set.combined_access(); assert!( @@ -857,7 +857,7 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { component_access_set: &mut FilteredAccessSet, world: &mut World, ) { - Query::init_access(&query_state, system_meta, component_access_set, world); + Query::init_access(query_state, system_meta, component_access_set, world); let combined_access = component_access_set.combined_access(); if combined_access.has_resource_write(*component_id) { From 44927a555ba431b0e9812ec018b7bbf9462a5e25 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sat, 25 Oct 2025 01:08:45 +0200 Subject: [PATCH 51/69] remove all unnecessary resourceComponent --- crates/bevy_a11y/src/lib.rs | 3 +- crates/bevy_ecs/src/message/messages.rs | 2 +- crates/bevy_ecs/src/reflect/mod.rs | 2 +- crates/bevy_ecs/src/reflect/resource.rs | 239 +----------------- crates/bevy_ecs/src/system/builder.rs | 2 +- crates/bevy_gizmos/src/config.rs | 5 +- crates/bevy_input/src/button_input.rs | 2 +- crates/bevy_input/src/mouse.rs | 2 +- .../derive/src/container_attributes.rs | 11 - crates/bevy_remote/src/builtin_methods.rs | 58 ++++- crates/bevy_state/src/state/resources.rs | 2 +- crates/bevy_time/src/time.rs | 2 +- 12 files changed, 65 insertions(+), 265 deletions(-) diff --git a/crates/bevy_a11y/src/lib.rs b/crates/bevy_a11y/src/lib.rs index c101003aa545b..f11e1fb51a8a1 100644 --- a/crates/bevy_a11y/src/lib.rs +++ b/crates/bevy_a11y/src/lib.rs @@ -73,8 +73,7 @@ use bevy_ecs::{component::Component, message::Message, resource::Resource, sched #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::{ReflectComponent, ReflectResource}, - bevy_reflect::std_traits::ReflectDefault, + bevy_ecs::reflect::ReflectResource, bevy_reflect::std_traits::ReflectDefault, bevy_reflect::Reflect, }; diff --git a/crates/bevy_ecs/src/message/messages.rs b/crates/bevy_ecs/src/message/messages.rs index 9be5e8a1c2c4b..7e7e03f18e85e 100644 --- a/crates/bevy_ecs/src/message/messages.rs +++ b/crates/bevy_ecs/src/message/messages.rs @@ -10,7 +10,7 @@ use core::{ }; #[cfg(feature = "bevy_reflect")] use { - crate::reflect::{ReflectComponent, ReflectResource}, + crate::reflect::ReflectResource, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; diff --git a/crates/bevy_ecs/src/reflect/mod.rs b/crates/bevy_ecs/src/reflect/mod.rs index c306723f3a707..18a09cd046bfa 100644 --- a/crates/bevy_ecs/src/reflect/mod.rs +++ b/crates/bevy_ecs/src/reflect/mod.rs @@ -24,7 +24,7 @@ pub use component::{ReflectComponent, ReflectComponentFns}; pub use entity_commands::ReflectCommandExt; pub use from_world::{ReflectFromWorld, ReflectFromWorldFns}; pub use map_entities::ReflectMapEntities; -pub use resource::{ReflectResource, ReflectResourceFns}; +pub use resource::ReflectResource; /// A [`Resource`] storing [`TypeRegistry`] for /// type registrations relevant to a whole app. diff --git a/crates/bevy_ecs/src/reflect/resource.rs b/crates/bevy_ecs/src/reflect/resource.rs index 16362b046f87f..5c334bb8ca879 100644 --- a/crates/bevy_ecs/src/reflect/resource.rs +++ b/crates/bevy_ecs/src/reflect/resource.rs @@ -5,252 +5,27 @@ //! See the module doc for [`reflect::component`](`crate::reflect::component`). use crate::{ - change_detection::Mut, - component::ComponentId, + reflect::{ReflectComponent, ReflectComponentFns}, resource::Resource, - world::{ - error::ResourceFetchError, unsafe_world_cell::UnsafeWorldCell, FilteredResources, - FilteredResourcesMut, World, - }, }; -use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry}; - -use super::from_reflect_with_fallback; +use bevy_reflect::{FromReflect, FromType, TypePath}; /// A struct used to operate on reflected [`Resource`] of a type. /// /// A [`ReflectResource`] for type `T` can be obtained via /// [`bevy_reflect::TypeRegistration::data`]. #[derive(Clone)] -pub struct ReflectResource(ReflectResourceFns); - -/// The raw function pointers needed to make up a [`ReflectResource`]. -/// -/// This is used when creating custom implementations of [`ReflectResource`] with -/// [`ReflectResource::new()`]. -/// -/// > **Note:** -/// > Creating custom implementations of [`ReflectResource`] is an advanced feature that most users -/// > will not need. -/// > Usually a [`ReflectResource`] is created for a type by deriving [`Reflect`] -/// > and adding the `#[reflect(Resource)]` attribute. -/// > After adding the component to the [`TypeRegistry`], -/// > its [`ReflectResource`] can then be retrieved when needed. -/// -/// Creating a custom [`ReflectResource`] may be useful if you need to create new resource types at -/// runtime, for example, for scripting implementations. -/// -/// By creating a custom [`ReflectResource`] and inserting it into a type's -/// [`TypeRegistration`][bevy_reflect::TypeRegistration], -/// you can modify the way that reflected resources of that type will be inserted into the bevy -/// world. -#[derive(Clone)] -pub struct ReflectResourceFns { - /// Function pointer implementing [`ReflectResource::insert()`]. - pub insert: fn(&mut World, &dyn PartialReflect, &TypeRegistry), - /// Function pointer implementing [`ReflectResource::apply()`]. - pub apply: fn(&mut World, &dyn PartialReflect), - /// Function pointer implementing [`ReflectResource::apply_or_insert()`]. - pub apply_or_insert: fn(&mut World, &dyn PartialReflect, &TypeRegistry), - /// Function pointer implementing [`ReflectResource::remove()`]. - pub remove: fn(&mut World), - /// Function pointer implementing [`ReflectResource::reflect()`]. - pub reflect: - for<'w> fn(FilteredResources<'w, '_>) -> Result<&'w dyn Reflect, ResourceFetchError>, - /// Function pointer implementing [`ReflectResource::reflect_mut()`]. - pub reflect_mut: for<'w> fn( - FilteredResourcesMut<'w, '_>, - ) -> Result, ResourceFetchError>, - /// Function pointer implementing [`ReflectResource::reflect_unchecked_mut()`]. - /// - /// # Safety - /// The function may only be called with an [`UnsafeWorldCell`] that can be used to mutably access the relevant resource. - pub reflect_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>) -> Option>, - /// Function pointer implementing [`ReflectResource::copy()`]. - pub copy: fn(&World, &mut World, &TypeRegistry), - /// Function pointer implementing [`ReflectResource::register_resource()`]. - pub register_resource: fn(&mut World) -> ComponentId, -} - -impl ReflectResourceFns { - /// Get the default set of [`ReflectResourceFns`] for a specific resource type using its - /// [`FromType`] implementation. - /// - /// This is useful if you want to start with the default implementation before overriding some - /// of the functions to create a custom implementation. - pub fn new() -> Self { - >::from_type().0 - } -} +pub struct ReflectResource(ReflectComponentFns); impl ReflectResource { - /// Insert a reflected [`Resource`] into the world like [`insert()`](World::insert_resource). - pub fn insert( - &self, - world: &mut World, - resource: &dyn PartialReflect, - registry: &TypeRegistry, - ) { - (self.0.insert)(world, resource, registry); - } - - /// Uses reflection to set the value of this [`Resource`] type in the world to the given value. - /// - /// # Panics - /// - /// Panics if there is no [`Resource`] of the given type. - pub fn apply(&self, world: &mut World, resource: &dyn PartialReflect) { - (self.0.apply)(world, resource); - } - - /// Uses reflection to set the value of this [`Resource`] type in the world to the given value or insert a new one if it does not exist. - pub fn apply_or_insert( - &self, - world: &mut World, - resource: &dyn PartialReflect, - registry: &TypeRegistry, - ) { - (self.0.apply_or_insert)(world, resource, registry); - } - - /// Removes this [`Resource`] type from the world. Does nothing if it doesn't exist. - pub fn remove(&self, world: &mut World) { - (self.0.remove)(world); - } - - /// Gets the value of this [`Resource`] type from the world as a reflected reference. - /// - /// Note that [`&World`](World) is a valid type for `resources`. - pub fn reflect<'w, 's>( - &self, - resources: impl Into>, - ) -> Result<&'w dyn Reflect, ResourceFetchError> { - (self.0.reflect)(resources.into()) - } - - /// Gets the value of this [`Resource`] type from the world as a mutable reflected reference. - /// - /// Note that [`&mut World`](World) is a valid type for `resources`. - pub fn reflect_mut<'w, 's>( - &self, - resources: impl Into>, - ) -> Result, ResourceFetchError> { - (self.0.reflect_mut)(resources.into()) - } - - /// # Safety - /// This method does not prevent you from having two mutable pointers to the same data, - /// violating Rust's aliasing rules. To avoid this: - /// * Only call this method with an [`UnsafeWorldCell`] which can be used to mutably access the resource. - /// * Don't call this method more than once in the same scope for a given [`Resource`]. - pub unsafe fn reflect_unchecked_mut<'w>( - &self, - world: UnsafeWorldCell<'w>, - ) -> Option> { - // SAFETY: caller promises to uphold uniqueness guarantees - unsafe { (self.0.reflect_unchecked_mut)(world) } - } - - /// Gets the value of this [`Resource`] type from `source_world` and [applies](Self::apply()) it to the value of this [`Resource`] type in `destination_world`. - /// - /// # Panics - /// - /// Panics if there is no [`Resource`] of the given type. - pub fn copy( - &self, - source_world: &World, - destination_world: &mut World, - registry: &TypeRegistry, - ) { - (self.0.copy)(source_world, destination_world, registry); - } - - /// Register the type of this [`Resource`] in [`World`], returning the [`ComponentId`] - pub fn register_resource(&self, world: &mut World) -> ComponentId { - (self.0.register_resource)(world) - } - - /// Create a custom implementation of [`ReflectResource`]. - /// - /// This is an advanced feature, - /// useful for scripting implementations, - /// that should not be used by most users - /// unless you know what you are doing. - /// - /// Usually you should derive [`Reflect`] and add the `#[reflect(Resource)]` component - /// to generate a [`ReflectResource`] implementation automatically. - /// - /// See [`ReflectResourceFns`] for more information. - pub fn new(&self, fns: ReflectResourceFns) -> Self { - Self(fns) - } - - /// The underlying function pointers implementing methods on `ReflectResource`. - /// - /// This is useful when you want to keep track locally of an individual - /// function pointer. - /// - /// Calling [`TypeRegistry::get`] followed by - /// [`TypeRegistration::data::`] can be costly if done several - /// times per frame. Consider cloning [`ReflectResource`] and keeping it - /// between frames, cloning a `ReflectResource` is very cheap. - /// - /// If you only need a subset of the methods on `ReflectResource`, - /// use `fn_pointers` to get the underlying [`ReflectResourceFns`] - /// and copy the subset of function pointers you care about. - /// - /// [`TypeRegistration::data::`]: bevy_reflect::TypeRegistration::data - /// [`TypeRegistry::get`]: bevy_reflect::TypeRegistry::get - pub fn fn_pointers(&self) -> &ReflectResourceFns { - &self.0 + /// Use as a [`ReflectComponent`]. + pub fn as_reflect_component(self) -> ReflectComponent { + ReflectComponent::new(self.0) } } impl FromType for ReflectResource { fn from_type() -> Self { - ReflectResource(ReflectResourceFns { - insert: |world, reflected_resource, registry| { - let resource = from_reflect_with_fallback::(reflected_resource, world, registry); - world.insert_resource(resource); - }, - apply: |world, reflected_resource| { - let mut resource = world.resource_mut::(); - resource.apply(reflected_resource); - }, - apply_or_insert: |world, reflected_resource, registry| { - if let Some(mut resource) = world.get_resource_mut::() { - resource.apply(reflected_resource); - } else { - let resource = - from_reflect_with_fallback::(reflected_resource, world, registry); - world.insert_resource(resource); - } - }, - remove: |world| { - world.remove_resource::(); - }, - reflect: |world| world.get::().map(|res| res.into_inner() as &dyn Reflect), - reflect_mut: |world| { - world - .into_mut::() - .map(|res| res.map_unchanged(|value| value as &mut dyn Reflect)) - }, - reflect_unchecked_mut: |world| { - // SAFETY: all usages of `reflect_unchecked_mut` guarantee that there is either a single mutable - // reference or multiple immutable ones alive at any given point - let res = unsafe { world.get_resource_mut::() }; - res.map(|res| res.map_unchanged(|value| value as &mut dyn Reflect)) - }, - copy: |source_world, destination_world, registry| { - let source_resource = source_world.resource::(); - let destination_resource = - from_reflect_with_fallback::(source_resource, destination_world, registry); - destination_world.insert_resource(destination_resource); - }, - - register_resource: |world: &mut World| -> ComponentId { - world.register_resource::() - }, - }) + ReflectResource(ReflectComponentFns::new::()) } } diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 309b3889afc75..937911ca834c1 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -622,7 +622,7 @@ mod tests { entity::Entities, error::Result, prelude::{Component, Query}, - reflect::{ReflectComponent, ReflectResource}, + reflect::ReflectResource, system::{Local, RunSystemOnce}, }; use alloc::vec; diff --git a/crates/bevy_gizmos/src/config.rs b/crates/bevy_gizmos/src/config.rs index 04197518a80a0..33344817a12fb 100644 --- a/crates/bevy_gizmos/src/config.rs +++ b/crates/bevy_gizmos/src/config.rs @@ -5,10 +5,7 @@ pub use bevy_gizmos_macros::GizmoConfigGroup; use {crate::GizmoAsset, bevy_asset::Handle, bevy_ecs::component::Component}; -use bevy_ecs::{ - reflect::{ReflectComponent, ReflectResource}, - resource::Resource, -}; +use bevy_ecs::{reflect::ReflectResource, resource::Resource}; use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath}; use bevy_utils::TypeIdMap; use core::{ diff --git a/crates/bevy_input/src/button_input.rs b/crates/bevy_input/src/button_input.rs index 10f0c632df751..e4ff47f470907 100644 --- a/crates/bevy_input/src/button_input.rs +++ b/crates/bevy_input/src/button_input.rs @@ -5,7 +5,7 @@ use bevy_platform::collections::HashSet; use core::hash::Hash; #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::{ReflectComponent, ReflectResource}, + bevy_ecs::reflect::ReflectResource, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; diff --git a/crates/bevy_input/src/mouse.rs b/crates/bevy_input/src/mouse.rs index 9cfb4c9367fc6..dcbbc5aaf16ce 100644 --- a/crates/bevy_input/src/mouse.rs +++ b/crates/bevy_input/src/mouse.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ use bevy_math::Vec2; #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::{ReflectComponent, ReflectResource}, + bevy_ecs::reflect::ReflectResource, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; diff --git a/crates/bevy_reflect/derive/src/container_attributes.rs b/crates/bevy_reflect/derive/src/container_attributes.rs index 957a4df0e4dca..47272acd6547a 100644 --- a/crates/bevy_reflect/derive/src/container_attributes.rs +++ b/crates/bevy_reflect/derive/src/container_attributes.rs @@ -279,17 +279,6 @@ impl ContainerAttributes { add_unique_ident(&mut self.idents, reflect_ident)?; - // When reflecting resources we also need to reflect component, since - // #[derive(Resource)] is equivalent to #[derive(Resource, Component)], - // we need to ensure that #[reflect(Resource)] is also equivalent to - // #[reflect(Resource, Component)]. This is necessary for bevy_scene - // serialization to work. - if &ident_name == "Resource" { - let mut reflect_component_ident = crate::ident::get_reflect_ident("Component"); - reflect_component_ident.set_span(ident.span()); - add_unique_ident(&mut self.idents, reflect_component_ident)?; - } - Ok(()) } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 6c7cdd02e2894..e2cc305e58911 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -516,8 +516,15 @@ pub fn process_remote_get_resources_request( let type_registry = app_type_registry.read(); let reflect_resource = get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; + let entity = get_resource_entity(&type_registry, &resource_path, world) + .map_err(BrpError::resource_error)?; + let entity_ref = world.get_entity(entity).map_err(BrpError::resource_error)?; - let Ok(reflected) = reflect_resource.reflect(world) else { + let Some(reflected) = reflect_resource + .clone() + .as_reflect_component() + .reflect(entity_ref) + else { return Err(BrpError::resource_not_present(&resource_path)); }; @@ -1031,9 +1038,20 @@ pub fn process_remote_insert_resources_request( let reflected_resource = deserialize_resource(&type_registry, &resource_path, value) .map_err(BrpError::resource_error)?; - let reflect_resource = - get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; - reflect_resource.insert(world, &*reflected_resource, &type_registry); + let resource_registration = get_resource_type_registration(&type_registry, &resource_path) + .map_err(BrpError::resource_error)?; + let type_id = resource_registration.type_id(); + let resource_id = world + .components() + .get_resource_id(type_id) + .ok_or(anyhow!("Resource is not registered: `{}`", resource_path)) + .map_err(BrpError::resource_error)?; + // get the entity if it already exists, otherwise spawn a new one. + if let Some(entity) = world.resource_entities.get(&resource_id) { + world.entity_mut(*entity).insert_reflect(reflected_resource); + } else { + world.spawn_empty().insert_reflect(reflected_resource); + } Ok(Value::Null) } @@ -1119,11 +1137,15 @@ pub fn process_remote_mutate_resources_request( // Get the `ReflectResource` for the given resource path. let reflect_resource = get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; + let entity = get_resource_entity(&type_registry, &resource_path, world) + .map_err(BrpError::resource_error)?; // Get the actual resource value from the world as a `dyn Reflect`. let mut reflected_resource = reflect_resource - .reflect_mut(world) - .map_err(|_| BrpError::resource_not_present(&resource_path))?; + .clone() + .as_reflect_component() + .reflect_mut(world.entity_mut(entity)) + .ok_or(BrpError::resource_not_present(&resource_path))?; // Get the type registration for the field with the given path. let value_registration = type_registry @@ -1194,9 +1216,9 @@ pub fn process_remote_remove_resources_request( let app_type_registry = world.resource::().clone(); let type_registry = app_type_registry.read(); - let reflect_resource = - get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; - reflect_resource.remove(world); + let entity = get_resource_entity(&type_registry, &resource_path, &world) + .map_err(BrpError::resource_error)?; + world.despawn(entity); Ok(Value::Null) } @@ -1605,6 +1627,24 @@ fn get_resource_type_registration<'r>( .ok_or_else(|| anyhow!("Unknown resource type: `{}`", resource_path)) } +fn get_resource_entity<'r>( + type_registry: &TypeRegistry, + resource_path: &str, + world: &World, +) -> AnyhowResult { + let resource_registration = get_resource_type_registration(type_registry, resource_path)?; + let type_id = resource_registration.type_id(); + let component_id = world + .components() + .get_resource_id(type_id) + .ok_or(anyhow!("Resource not registered: `{}`", resource_path))?; + let entity = world + .resource_entities + .get(&component_id) + .ok_or(anyhow!("Resource entity does not exist."))?; + Ok(*entity) +} + #[cfg(test)] mod tests { /// A generic function that tests serialization and deserialization of any type diff --git a/crates/bevy_state/src/state/resources.rs b/crates/bevy_state/src/state/resources.rs index bbd1ce36a21a9..4bbe6d1b1f24e 100644 --- a/crates/bevy_state/src/state/resources.rs +++ b/crates/bevy_state/src/state/resources.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ use super::{freely_mutable_state::FreelyMutableState, states::States}; #[cfg(feature = "bevy_reflect")] -use bevy_ecs::prelude::{ReflectComponent, ReflectResource}; +use bevy_ecs::prelude::ReflectResource; #[cfg(feature = "bevy_reflect")] use bevy_reflect::prelude::ReflectDefault; diff --git a/crates/bevy_time/src/time.rs b/crates/bevy_time/src/time.rs index 7977bca2e1a88..1494afaadfddc 100644 --- a/crates/bevy_time/src/time.rs +++ b/crates/bevy_time/src/time.rs @@ -2,7 +2,7 @@ use bevy_ecs::resource::Resource; use core::time::Duration; #[cfg(feature = "bevy_reflect")] use { - bevy_ecs::reflect::{ReflectComponent, ReflectResource}, + bevy_ecs::reflect::ReflectResource, bevy_reflect::{std_traits::ReflectDefault, Reflect}, }; From 4cbdaa5124a024384b053dbdb29e3ad647758afe Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 14:11:07 +0100 Subject: [PATCH 52/69] remove entity_ref --- crates/bevy_ecs/src/world/entity_ref.rs | 6831 ----------------------- 1 file changed, 6831 deletions(-) delete mode 100644 crates/bevy_ecs/src/world/entity_ref.rs diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs deleted file mode 100644 index 1d7b922967e8c..0000000000000 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ /dev/null @@ -1,6831 +0,0 @@ -use crate::{ - archetype::Archetype, - bundle::{ - Bundle, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle, InsertMode, - }, - change_detection::{MaybeLocation, MutUntyped}, - component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick}, - entity::{ - ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, - EntityIdLocation, EntityLocation, OptIn, OptOut, - }, - event::{EntityComponentsTrigger, EntityEvent}, - lifecycle::{Despawn, Remove, Replace, DESPAWN, REMOVE, REPLACE}, - observer::Observer, - query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData, ReleaseStateQueryData}, - relationship::RelationshipHookMode, - resource::Resource, - storage::{SparseSets, Table}, - system::IntoObserverSystem, - world::{error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World}, -}; -use alloc::vec::Vec; -use bevy_platform::collections::{HashMap, HashSet}; -use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr}; -use core::{ - any::TypeId, - cmp::Ordering, - hash::{Hash, Hasher}, - marker::PhantomData, - mem::MaybeUninit, -}; -use thiserror::Error; - -/// A read-only reference to a particular [`Entity`] and all of its components. -/// -/// # Examples -/// -/// Read-only access disjoint with mutable access. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Component)] pub struct A; -/// # #[derive(Component)] pub struct B; -/// fn disjoint_system( -/// query1: Query<&mut A>, -/// query2: Query>, -/// ) { -/// // ... -/// } -/// # bevy_ecs::system::assert_is_system(disjoint_system); -/// ``` -#[derive(Copy, Clone)] -pub struct EntityRef<'w> { - cell: UnsafeEntityCell<'w>, -} - -impl<'w> EntityRef<'w> { - /// # Safety - /// - `cell` must have permission to read every component of the entity. - /// - No mutable accesses to any of the entity's components may exist - /// at the same time as the returned [`EntityRef`]. - #[inline] - pub(crate) unsafe fn new(cell: UnsafeEntityCell<'w>) -> Self { - Self { cell } - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.cell.id() - } - - /// Gets metadata indicating the location where the current entity is stored. - #[inline] - pub fn location(&self) -> EntityLocation { - self.cell.location() - } - - /// Returns the archetype that the current entity belongs to. - #[inline] - pub fn archetype(&self) -> &Archetype { - self.cell.archetype() - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.cell.contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.cell.contains_type_id(type_id) - } - - /// Gets access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get(&self) -> Option<&'w T> { - // SAFETY: We have read-only access to all components of this entity. - unsafe { self.cell.get::() } - } - - /// Gets access to the component of type `T` for the current entity, - /// including change detection information as a [`Ref`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get_ref(&self) -> Option> { - // SAFETY: We have read-only access to all components of this entity. - unsafe { self.cell.get_ref::() } - } - - /// Retrieves the change ticks for the given component. This can be useful for implementing change - /// detection in custom runtimes. - #[inline] - pub fn get_change_ticks(&self) -> Option { - // SAFETY: We have read-only access to all components of this entity. - unsafe { self.cell.get_change_ticks::() } - } - - /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. - /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. - /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. - #[inline] - pub fn get_changed_by(&self) -> Option { - // SAFETY: We have read-only access to all components of this entity. - unsafe { self.cell.get_changed_by::() } - } - - /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// **You should prefer to use the typed API [`EntityRef::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - #[inline] - pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - // SAFETY: We have read-only access to all components of this entity. - unsafe { self.cell.get_change_ticks_by_id(component_id) } - } - - /// Returns [untyped read-only reference(s)](Ptr) to component(s) for the - /// current entity, based on the given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityRef::get`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityRef::get`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - /// # Examples - /// - /// ## Single [`ComponentId`] - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Foo(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn(Foo(42)).id(); - /// - /// // Grab the component ID for `Foo` in whatever way you like. - /// let component_id = world.register_component::(); - /// - /// // Then, get the component by ID. - /// let ptr = world.entity(entity).get_by_id(component_id); - /// # assert_eq!(unsafe { ptr.unwrap().deref::() }, &Foo(42)); - /// ``` - /// - /// ## Array of [`ComponentId`]s - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct X(i32); - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Y(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn((X(42), Y(10))).id(); - /// - /// // Grab the component IDs for `X` and `Y` in whatever way you like. - /// let x_id = world.register_component::(); - /// let y_id = world.register_component::(); - /// - /// // Then, get the components by ID. You'll receive a same-sized array. - /// let Ok([x_ptr, y_ptr]) = world.entity(entity).get_by_id([x_id, y_id]) else { - /// // Up to you to handle if a component is missing from the entity. - /// # unreachable!(); - /// }; - /// # assert_eq!((unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }), (&X(42), &Y(10))); - /// ``` - /// - /// ## Slice of [`ComponentId`]s - /// - /// ``` - /// # use bevy_ecs::{prelude::*, component::ComponentId}; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct X(i32); - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Y(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn((X(42), Y(10))).id(); - /// - /// // Grab the component IDs for `X` and `Y` in whatever way you like. - /// let x_id = world.register_component::(); - /// let y_id = world.register_component::(); - /// - /// // Then, get the components by ID. You'll receive a vec of ptrs. - /// let ptrs = world.entity(entity).get_by_id(&[x_id, y_id] as &[ComponentId]); - /// # let ptrs = ptrs.unwrap(); - /// # assert_eq!((unsafe { ptrs[0].deref::() }, unsafe { ptrs[1].deref::() }), (&X(42), &Y(10))); - /// ``` - /// - /// ## [`HashSet`] of [`ComponentId`]s - /// - /// ``` - /// # use bevy_platform::collections::HashSet; - /// # use bevy_ecs::{prelude::*, component::ComponentId}; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct X(i32); - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Y(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn((X(42), Y(10))).id(); - /// - /// // Grab the component IDs for `X` and `Y` in whatever way you like. - /// let x_id = world.register_component::(); - /// let y_id = world.register_component::(); - /// - /// // Then, get the components by ID. You'll receive a vec of ptrs. - /// let ptrs = world.entity(entity).get_by_id(&HashSet::from_iter([x_id, y_id])); - /// # let ptrs = ptrs.unwrap(); - /// # assert_eq!((unsafe { ptrs[&x_id].deref::() }, unsafe { ptrs[&y_id].deref::() }), (&X(42), &Y(10))); - /// ``` - #[inline] - pub fn get_by_id( - &self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: We have read-only access to all components of this entity. - unsafe { component_ids.fetch_ref(self.cell) } - } - - /// Returns read-only components for the current entity that match the query `Q`. - /// - /// # Panics - /// - /// If the entity does not have the components required by the query `Q`. - pub fn components(&self) -> Q::Item<'w, 'static> { - self.get_components::() - .expect("Query does not match the current entity") - } - - /// Returns read-only components for the current entity that match the query `Q`, - /// or `None` if the entity does not have the components required by the query `Q`. - pub fn get_components( - &self, - ) -> Option> { - // SAFETY: - // - We have read-only access to all components of this entity. - // - The query is read-only, and read-only references cannot have conflicts. - unsafe { self.cell.get_components::() } - } - - /// Returns the source code location from which this entity has been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.cell.spawned_by() - } - - /// Returns the [`Tick`] at which this entity has been spawned. - pub fn spawn_tick(&self) -> Tick { - self.cell.spawn_tick() - } -} - -impl<'w> From> for EntityRef<'w> { - fn from(entity: EntityWorldMut<'w>) -> EntityRef<'w> { - // SAFETY: - // - `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { EntityRef::new(entity.into_unsafe_entity_cell()) } - } -} - -impl<'a> From<&'a EntityWorldMut<'_>> for EntityRef<'a> { - fn from(entity: &'a EntityWorldMut<'_>) -> Self { - // SAFETY: - // - `EntityWorldMut` guarantees exclusive access to the entire world. - // - `&entity` ensures no mutable accesses are active. - unsafe { EntityRef::new(entity.as_unsafe_entity_cell_readonly()) } - } -} - -impl<'w> From> for EntityRef<'w> { - fn from(entity: EntityMut<'w>) -> Self { - // SAFETY: - // - `EntityMut` guarantees exclusive access to all of the entity's components. - unsafe { EntityRef::new(entity.cell) } - } -} - -impl<'a> From<&'a EntityMut<'_>> for EntityRef<'a> { - fn from(entity: &'a EntityMut<'_>) -> Self { - // SAFETY: - // - `EntityMut` guarantees exclusive access to all of the entity's components. - // - `&entity` ensures there are no mutable accesses. - unsafe { EntityRef::new(entity.cell) } - } -} - -impl<'a> TryFrom> for EntityRef<'a> { - type Error = TryFromFilteredError; - - fn try_from(entity: FilteredEntityRef<'a, '_>) -> Result { - if !entity.access.has_read_all() { - Err(TryFromFilteredError::MissingReadAllAccess) - } else { - // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(entity.entity) }) - } - } -} - -impl<'a> TryFrom<&'a FilteredEntityRef<'_, '_>> for EntityRef<'a> { - type Error = TryFromFilteredError; - - fn try_from(entity: &'a FilteredEntityRef<'_, '_>) -> Result { - if !entity.access.has_read_all() { - Err(TryFromFilteredError::MissingReadAllAccess) - } else { - // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(entity.entity) }) - } - } -} - -impl<'a> TryFrom> for EntityRef<'a> { - type Error = TryFromFilteredError; - - fn try_from(entity: FilteredEntityMut<'a, '_>) -> Result { - if !entity.access.has_read_all() { - Err(TryFromFilteredError::MissingReadAllAccess) - } else { - // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(entity.entity) }) - } - } -} - -impl<'a> TryFrom<&'a FilteredEntityMut<'_, '_>> for EntityRef<'a> { - type Error = TryFromFilteredError; - - fn try_from(entity: &'a FilteredEntityMut<'_, '_>) -> Result { - if !entity.access.has_read_all() { - Err(TryFromFilteredError::MissingReadAllAccess) - } else { - // SAFETY: check above guarantees read-only access to all components of the entity. - Ok(unsafe { EntityRef::new(entity.entity) }) - } - } -} - -impl PartialEq for EntityRef<'_> { - fn eq(&self, other: &Self) -> bool { - self.entity() == other.entity() - } -} - -impl Eq for EntityRef<'_> {} - -impl PartialOrd for EntityRef<'_> { - /// [`EntityRef`]'s comparison trait implementations match the underlying [`Entity`], - /// and cannot discern between different worlds. - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EntityRef<'_> { - fn cmp(&self, other: &Self) -> Ordering { - self.entity().cmp(&other.entity()) - } -} - -impl Hash for EntityRef<'_> { - fn hash(&self, state: &mut H) { - self.entity().hash(state); - } -} - -impl ContainsEntity for EntityRef<'_> { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for EntityRef<'_> {} - -/// Provides mutable access to a single entity and all of its components. -/// -/// Contrast with [`EntityWorldMut`], which allows adding and removing components, -/// despawning the entity, and provides mutable access to the entire world. -/// Because of this, `EntityWorldMut` cannot coexist with any other world accesses. -/// -/// # Examples -/// -/// Disjoint mutable access. -/// -/// ``` -/// # use bevy_ecs::prelude::*; -/// # #[derive(Component)] pub struct A; -/// fn disjoint_system( -/// query1: Query>, -/// query2: Query>, -/// ) { -/// // ... -/// } -/// # bevy_ecs::system::assert_is_system(disjoint_system); -/// ``` -pub struct EntityMut<'w> { - cell: UnsafeEntityCell<'w>, -} - -impl<'w> EntityMut<'w> { - /// # Safety - /// - `cell` must have permission to mutate every component of the entity. - /// - No accesses to any of the entity's components may exist - /// at the same time as the returned [`EntityMut`]. - #[inline] - pub(crate) unsafe fn new(cell: UnsafeEntityCell<'w>) -> Self { - Self { cell } - } - - /// Returns a new instance with a shorter lifetime. - /// This is useful if you have `&mut EntityMut`, but you need `EntityMut`. - pub fn reborrow(&mut self) -> EntityMut<'_> { - // SAFETY: We have exclusive access to the entire entity and its components. - unsafe { Self::new(self.cell) } - } - - /// Consumes `self` and returns read-only access to all of the entity's - /// components, with the world `'w` lifetime. - pub fn into_readonly(self) -> EntityRef<'w> { - EntityRef::from(self) - } - - /// Gets read-only access to all of the entity's components. - pub fn as_readonly(&self) -> EntityRef<'_> { - EntityRef::from(self) - } - - /// Get access to the underlying [`UnsafeEntityCell`] - pub fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> { - self.cell - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.cell.id() - } - - /// Gets metadata indicating the location where the current entity is stored. - #[inline] - pub fn location(&self) -> EntityLocation { - self.cell.location() - } - - /// Returns the archetype that the current entity belongs to. - #[inline] - pub fn archetype(&self) -> &Archetype { - self.cell.archetype() - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.cell.contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.cell.contains_type_id(type_id) - } - - /// Gets access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get(&self) -> Option<&'_ T> { - self.as_readonly().get() - } - - /// Returns read-only components for the current entity that match the query `Q`. - /// - /// # Panics - /// - /// If the entity does not have the components required by the query `Q`. - pub fn components(&self) -> Q::Item<'_, 'static> { - self.as_readonly().components::() - } - - /// Returns read-only components for the current entity that match the query `Q`, - /// or `None` if the entity does not have the components required by the query `Q`. - pub fn get_components( - &self, - ) -> Option> { - self.as_readonly().get_components::() - } - - /// Returns components for the current entity that match the query `Q`, - /// or `None` if the entity does not have the components required by the query `Q`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Component)] - /// struct X(usize); - /// #[derive(Component)] - /// struct Y(usize); - /// - /// # let mut world = World::default(); - /// let mut entity = world.spawn((X(0), Y(0))).into_mutable(); - /// // Get mutable access to two components at once - /// // SAFETY: X and Y are different components - /// let (mut x, mut y) = - /// unsafe { entity.get_components_mut_unchecked::<(&mut X, &mut Y)>() }.unwrap(); - /// *x = X(1); - /// *y = Y(1); - /// // This would trigger undefined behavior, as the `&mut X`s would alias: - /// // entity.get_components_mut_unchecked::<(&mut X, &mut X)>(); - /// ``` - /// - /// # Safety - /// It is the caller's responsibility to ensure that - /// the `QueryData` does not provide aliasing mutable references to the same component. - pub unsafe fn get_components_mut_unchecked( - &mut self, - ) -> Option> { - // SAFETY: Caller the `QueryData` does not provide aliasing mutable references to the same component - unsafe { self.reborrow().into_components_mut_unchecked::() } - } - - /// Consumes self and returns components for the current entity that match the query `Q` for the world lifetime `'w`, - /// or `None` if the entity does not have the components required by the query `Q`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Component)] - /// struct X(usize); - /// #[derive(Component)] - /// struct Y(usize); - /// - /// # let mut world = World::default(); - /// let mut entity = world.spawn((X(0), Y(0))).into_mutable(); - /// // Get mutable access to two components at once - /// // SAFETY: X and Y are different components - /// let (mut x, mut y) = - /// unsafe { entity.into_components_mut_unchecked::<(&mut X, &mut Y)>() }.unwrap(); - /// *x = X(1); - /// *y = Y(1); - /// // This would trigger undefined behavior, as the `&mut X`s would alias: - /// // entity.into_components_mut_unchecked::<(&mut X, &mut X)>(); - /// ``` - /// - /// # Safety - /// It is the caller's responsibility to ensure that - /// the `QueryData` does not provide aliasing mutable references to the same component. - pub unsafe fn into_components_mut_unchecked( - self, - ) -> Option> { - // SAFETY: - // - We have mutable access to all components of this entity. - // - Caller asserts the `QueryData` does not provide aliasing mutable references to the same component - unsafe { self.cell.get_components::() } - } - - /// Consumes `self` and gets access to the component of type `T` with the - /// world `'w` lifetime for the current entity. - /// - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn into_borrow(self) -> Option<&'w T> { - self.into_readonly().get() - } - - /// Gets access to the component of type `T` for the current entity, - /// including change detection information as a [`Ref`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get_ref(&self) -> Option> { - self.as_readonly().get_ref() - } - - /// Consumes `self` and gets access to the component of type `T` with world - /// `'w` lifetime for the current entity, including change detection information - /// as a [`Ref<'w>`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn into_ref(self) -> Option> { - self.into_readonly().get_ref() - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get_mut>(&mut self) -> Option> { - // SAFETY: &mut self implies exclusive access for duration of returned value - unsafe { self.cell.get_mut() } - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Safety - /// - /// - `T` must be a mutable component - #[inline] - pub unsafe fn get_mut_assume_mutable(&mut self) -> Option> { - // SAFETY: - // - &mut self implies exclusive access for duration of returned value - // - Caller ensures `T` is a mutable component - unsafe { self.cell.get_mut_assume_mutable() } - } - - /// Consumes self and gets mutable access to the component of type `T` - /// with the world `'w` lifetime for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn into_mut>(self) -> Option> { - // SAFETY: consuming `self` implies exclusive access - unsafe { self.cell.get_mut() } - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Safety - /// - /// - `T` must be a mutable component - #[inline] - pub unsafe fn into_mut_assume_mutable(self) -> Option> { - // SAFETY: - // - Consuming `self` implies exclusive access - // - Caller ensures `T` is a mutable component - unsafe { self.cell.get_mut_assume_mutable() } - } - - /// Retrieves the change ticks for the given component. This can be useful for implementing change - /// detection in custom runtimes. - #[inline] - pub fn get_change_ticks(&self) -> Option { - self.as_readonly().get_change_ticks::() - } - - /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - #[inline] - pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - self.as_readonly().get_change_ticks_by_id(component_id) - } - - /// Returns [untyped read-only reference(s)](Ptr) to component(s) for the - /// current entity, based on the given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityMut::get`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityMut::get`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityRef::get_by_id`]. - #[inline] - pub fn get_by_id( - &self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.as_readonly().get_by_id(component_ids) - } - - /// Consumes `self` and returns [untyped read-only reference(s)](Ptr) to - /// component(s) with lifetime `'w` for the current entity, based on the - /// given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityMut::into_borrow`] - /// where possible and only use this in cases where the actual component - /// types are not known at compile time.** - /// - /// Unlike [`EntityMut::into_borrow`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityRef::get_by_id`]. - #[inline] - pub fn into_borrow_by_id( - self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.into_readonly().get_by_id(component_ids) - } - - /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for - /// the current entity, based on the given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityMut::get_mut`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityMut::get_mut`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Examples - /// - /// ## Single [`ComponentId`] - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Foo(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn(Foo(42)).id(); - /// - /// // Grab the component ID for `Foo` in whatever way you like. - /// let component_id = world.register_component::(); - /// - /// // Then, get the component by ID. - /// let mut entity_mut = world.entity_mut(entity); - /// let mut ptr = entity_mut.get_mut_by_id(component_id) - /// # .unwrap(); - /// # assert_eq!(unsafe { ptr.as_mut().deref_mut::() }, &mut Foo(42)); - /// ``` - /// - /// ## Array of [`ComponentId`]s - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct X(i32); - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Y(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn((X(42), Y(10))).id(); - /// - /// // Grab the component IDs for `X` and `Y` in whatever way you like. - /// let x_id = world.register_component::(); - /// let y_id = world.register_component::(); - /// - /// // Then, get the components by ID. You'll receive a same-sized array. - /// let mut entity_mut = world.entity_mut(entity); - /// let Ok([mut x_ptr, mut y_ptr]) = entity_mut.get_mut_by_id([x_id, y_id]) else { - /// // Up to you to handle if a component is missing from the entity. - /// # unreachable!(); - /// }; - /// # assert_eq!((unsafe { x_ptr.as_mut().deref_mut::() }, unsafe { y_ptr.as_mut().deref_mut::() }), (&mut X(42), &mut Y(10))); - /// ``` - /// - /// ## Slice of [`ComponentId`]s - /// - /// ``` - /// # use bevy_ecs::{prelude::*, component::ComponentId, change_detection::MutUntyped}; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct X(i32); - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Y(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn((X(42), Y(10))).id(); - /// - /// // Grab the component IDs for `X` and `Y` in whatever way you like. - /// let x_id = world.register_component::(); - /// let y_id = world.register_component::(); - /// - /// // Then, get the components by ID. You'll receive a vec of ptrs. - /// let mut entity_mut = world.entity_mut(entity); - /// let ptrs = entity_mut.get_mut_by_id(&[x_id, y_id] as &[ComponentId]) - /// # .unwrap(); - /// # let [mut x_ptr, mut y_ptr]: [MutUntyped; 2] = ptrs.try_into().unwrap(); - /// # assert_eq!((unsafe { x_ptr.as_mut().deref_mut::() }, unsafe { y_ptr.as_mut().deref_mut::() }), (&mut X(42), &mut Y(10))); - /// ``` - /// - /// ## [`HashSet`] of [`ComponentId`]s - /// - /// ``` - /// # use bevy_platform::collections::HashSet; - /// # use bevy_ecs::{prelude::*, component::ComponentId}; - /// # - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct X(i32); - /// # #[derive(Component, PartialEq, Debug)] - /// # pub struct Y(i32); - /// # let mut world = World::new(); - /// let entity = world.spawn((X(42), Y(10))).id(); - /// - /// // Grab the component IDs for `X` and `Y` in whatever way you like. - /// let x_id = world.register_component::(); - /// let y_id = world.register_component::(); - /// - /// // Then, get the components by ID. You'll receive a `HashMap` of ptrs. - /// let mut entity_mut = world.entity_mut(entity); - /// let mut ptrs = entity_mut.get_mut_by_id(&HashSet::from_iter([x_id, y_id])) - /// # .unwrap(); - /// # let [Some(mut x_ptr), Some(mut y_ptr)] = ptrs.get_many_mut([&x_id, &y_id]) else { unreachable!() }; - /// # assert_eq!((unsafe { x_ptr.as_mut().deref_mut::() }, unsafe { y_ptr.as_mut().deref_mut::() }), (&mut X(42), &mut Y(10))); - /// ``` - #[inline] - pub fn get_mut_by_id( - &mut self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: - // - `&mut self` ensures that no references exist to this entity's components. - // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut(self.cell) } - } - - /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for - /// the current entity, based on the given [`ComponentId`]s. - /// Assumes the given [`ComponentId`]s refer to mutable components. - /// - /// **You should prefer to use the typed API [`EntityMut::get_mut_assume_mutable`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityMut::get_mut_assume_mutable`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the provided [`ComponentId`]s must refer to mutable components. - #[inline] - pub unsafe fn get_mut_assume_mutable_by_id( - &mut self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: - // - `&mut self` ensures that no references exist to this entity's components. - // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } - } - - /// Returns [untyped mutable reference](MutUntyped) to component for - /// the current entity, based on the given [`ComponentId`]. - /// - /// Unlike [`EntityMut::get_mut_by_id`], this method borrows &self instead of - /// &mut self, allowing the caller to access multiple components simultaneously. - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeEntityCell`] has permission to access the component mutably - /// - no other references to the component exist at the same time - #[inline] - pub unsafe fn get_mut_by_id_unchecked( - &self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: - // - The caller must ensure simultaneous access is limited - // - to components that are mutually independent. - unsafe { component_ids.fetch_mut(self.cell) } - } - - /// Returns [untyped mutable reference](MutUntyped) to component for - /// the current entity, based on the given [`ComponentId`]. - /// Assumes the given [`ComponentId`]s refer to mutable components. - /// - /// Unlike [`EntityMut::get_mut_assume_mutable_by_id`], this method borrows &self instead of - /// &mut self, allowing the caller to access multiple components simultaneously. - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the [`UnsafeEntityCell`] has permission to access the component mutably - /// - no other references to the component exist at the same time - /// - the provided [`ComponentId`]s must refer to mutable components. - #[inline] - pub unsafe fn get_mut_assume_mutable_by_id_unchecked( - &self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: - // - The caller must ensure simultaneous access is limited - // - to components that are mutually independent. - unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } - } - - /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) - /// to component(s) with lifetime `'w` for the current entity, based on the - /// given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityMut::into_mut`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityMut::into_mut`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityMut::get_mut_by_id`]. - #[inline] - pub fn into_mut_by_id( - self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: - // - consuming `self` ensures that no references exist to this entity's components. - // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut(self.cell) } - } - - /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) - /// to component(s) with lifetime `'w` for the current entity, based on the - /// given [`ComponentId`]s. - /// Assumes the given [`ComponentId`]s refer to mutable components. - /// - /// **You should prefer to use the typed API [`EntityMut::into_mut_assume_mutable`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityMut::into_mut_assume_mutable`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the provided [`ComponentId`]s must refer to mutable components. - #[inline] - pub unsafe fn into_mut_assume_mutable_by_id( - self, - component_ids: F, - ) -> Result, EntityComponentError> { - // SAFETY: - // - consuming `self` ensures that no references exist to this entity's components. - // - We have exclusive access to all components of this entity. - unsafe { component_ids.fetch_mut_assume_mutable(self.cell) } - } - - /// Returns the source code location from which this entity has been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.cell.spawned_by() - } - - /// Returns the [`Tick`] at which this entity has been spawned. - pub fn spawn_tick(&self) -> Tick { - self.cell.spawn_tick() - } -} - -impl<'w> From<&'w mut EntityMut<'_>> for EntityMut<'w> { - fn from(entity: &'w mut EntityMut<'_>) -> Self { - entity.reborrow() - } -} - -impl<'w> From> for EntityMut<'w> { - fn from(entity: EntityWorldMut<'w>) -> Self { - // SAFETY: `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { EntityMut::new(entity.into_unsafe_entity_cell()) } - } -} - -impl<'a> From<&'a mut EntityWorldMut<'_>> for EntityMut<'a> { - #[inline] - fn from(entity: &'a mut EntityWorldMut<'_>) -> Self { - // SAFETY: `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { EntityMut::new(entity.as_unsafe_entity_cell()) } - } -} - -impl<'a> TryFrom> for EntityMut<'a> { - type Error = TryFromFilteredError; - - fn try_from(entity: FilteredEntityMut<'a, '_>) -> Result { - if !entity.access.has_read_all() { - Err(TryFromFilteredError::MissingReadAllAccess) - } else if !entity.access.has_write_all() { - Err(TryFromFilteredError::MissingWriteAllAccess) - } else { - // SAFETY: check above guarantees exclusive access to all components of the entity. - Ok(unsafe { EntityMut::new(entity.entity) }) - } - } -} - -impl<'a> TryFrom<&'a mut FilteredEntityMut<'_, '_>> for EntityMut<'a> { - type Error = TryFromFilteredError; - - fn try_from(entity: &'a mut FilteredEntityMut<'_, '_>) -> Result { - if !entity.access.has_read_all() { - Err(TryFromFilteredError::MissingReadAllAccess) - } else if !entity.access.has_write_all() { - Err(TryFromFilteredError::MissingWriteAllAccess) - } else { - // SAFETY: check above guarantees exclusive access to all components of the entity. - Ok(unsafe { EntityMut::new(entity.entity) }) - } - } -} - -impl PartialEq for EntityMut<'_> { - fn eq(&self, other: &Self) -> bool { - self.entity() == other.entity() - } -} - -impl Eq for EntityMut<'_> {} - -impl PartialOrd for EntityMut<'_> { - /// [`EntityMut`]'s comparison trait implementations match the underlying [`Entity`], - /// and cannot discern between different worlds. - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EntityMut<'_> { - fn cmp(&self, other: &Self) -> Ordering { - self.entity().cmp(&other.entity()) - } -} - -impl Hash for EntityMut<'_> { - fn hash(&self, state: &mut H) { - self.entity().hash(state); - } -} - -impl ContainsEntity for EntityMut<'_> { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for EntityMut<'_> {} - -/// A mutable reference to a particular [`Entity`], and the entire world. -/// -/// This is essentially a performance-optimized `(Entity, &mut World)` tuple, -/// which caches the [`EntityLocation`] to reduce duplicate lookups. -/// -/// Since this type provides mutable access to the entire world, only one -/// [`EntityWorldMut`] can exist at a time for a given world. -/// -/// See also [`EntityMut`], which allows disjoint mutable access to multiple -/// entities at once. Unlike `EntityMut`, this type allows adding and -/// removing components, and despawning the entity. -pub struct EntityWorldMut<'w> { - world: &'w mut World, - entity: Entity, - location: EntityIdLocation, -} - -impl<'w> EntityWorldMut<'w> { - #[track_caller] - #[inline(never)] - #[cold] - fn panic_despawned(&self) -> ! { - panic!( - "Entity {} {}", - self.entity, - self.world - .entities() - .entity_does_not_exist_error_details(self.entity) - ); - } - - #[inline(always)] - #[track_caller] - pub(crate) fn assert_not_despawned(&self) { - if self.location.is_none() { - self.panic_despawned() - } - } - - #[inline(always)] - fn as_unsafe_entity_cell_readonly(&self) -> UnsafeEntityCell<'_> { - let location = self.location(); - let last_change_tick = self.world.last_change_tick; - let change_tick = self.world.read_change_tick(); - UnsafeEntityCell::new( - self.world.as_unsafe_world_cell_readonly(), - self.entity, - location, - last_change_tick, - change_tick, - ) - } - - #[inline(always)] - fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> { - let location = self.location(); - let last_change_tick = self.world.last_change_tick; - let change_tick = self.world.change_tick(); - UnsafeEntityCell::new( - self.world.as_unsafe_world_cell(), - self.entity, - location, - last_change_tick, - change_tick, - ) - } - - #[inline(always)] - fn into_unsafe_entity_cell(self) -> UnsafeEntityCell<'w> { - let location = self.location(); - let last_change_tick = self.world.last_change_tick; - let change_tick = self.world.change_tick(); - UnsafeEntityCell::new( - self.world.as_unsafe_world_cell(), - self.entity, - location, - last_change_tick, - change_tick, - ) - } - - /// # Safety - /// - /// - `entity` must be valid for `world`: the generation should match that of the entity at the same index. - /// - `location` must be sourced from `world`'s `Entities` and must exactly match the location for `entity` - /// - /// The above is trivially satisfied if `location` was sourced from `world.entities().get(entity)`. - #[inline] - pub(crate) unsafe fn new( - world: &'w mut World, - entity: Entity, - location: Option, - ) -> Self { - debug_assert!(world.entities().contains(entity)); - debug_assert_eq!(world.entities().get(entity), location); - - EntityWorldMut { - world, - entity, - location, - } - } - - /// Consumes `self` and returns read-only access to all of the entity's - /// components, with the world `'w` lifetime. - pub fn into_readonly(self) -> EntityRef<'w> { - EntityRef::from(self) - } - - /// Gets read-only access to all of the entity's components. - #[inline] - pub fn as_readonly(&self) -> EntityRef<'_> { - EntityRef::from(self) - } - - /// Consumes `self` and returns non-structural mutable access to all of the - /// entity's components, with the world `'w` lifetime. - pub fn into_mutable(self) -> EntityMut<'w> { - EntityMut::from(self) - } - - /// Gets non-structural mutable access to all of the entity's components. - #[inline] - pub fn as_mutable(&mut self) -> EntityMut<'_> { - EntityMut::from(self) - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.entity - } - - /// Gets metadata indicating the location where the current entity is stored. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn location(&self) -> EntityLocation { - match self.location { - Some(loc) => loc, - None => self.panic_despawned(), - } - } - - /// Returns the archetype that the current entity belongs to. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn archetype(&self) -> &Archetype { - let location = self.location(); - &self.world.archetypes[location.archetype_id] - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.as_unsafe_entity_cell_readonly() - .contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.as_unsafe_entity_cell_readonly() - .contains_type_id(type_id) - } - - /// Gets access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get(&self) -> Option<&'_ T> { - self.as_readonly().get() - } - - /// Returns read-only components for the current entity that match the query `Q`. - /// - /// # Panics - /// - /// If the entity does not have the components required by the query `Q` or if the entity - /// has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn components(&self) -> Q::Item<'_, 'static> { - self.as_readonly().components::() - } - - /// Returns read-only components for the current entity that match the query `Q`, - /// or `None` if the entity does not have the components required by the query `Q`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_components( - &self, - ) -> Option> { - self.as_readonly().get_components::() - } - - /// Returns components for the current entity that match the query `Q`, - /// or `None` if the entity does not have the components required by the query `Q`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Component)] - /// struct X(usize); - /// #[derive(Component)] - /// struct Y(usize); - /// - /// # let mut world = World::default(); - /// let mut entity = world.spawn((X(0), Y(0))); - /// // Get mutable access to two components at once - /// // SAFETY: X and Y are different components - /// let (mut x, mut y) = - /// unsafe { entity.get_components_mut_unchecked::<(&mut X, &mut Y)>() }.unwrap(); - /// *x = X(1); - /// *y = Y(1); - /// // This would trigger undefined behavior, as the `&mut X`s would alias: - /// // entity.get_components_mut_unchecked::<(&mut X, &mut X)>(); - /// ``` - /// - /// # Safety - /// It is the caller's responsibility to ensure that - /// the `QueryData` does not provide aliasing mutable references to the same component. - pub unsafe fn get_components_mut_unchecked( - &mut self, - ) -> Option> { - // SAFETY: Caller the `QueryData` does not provide aliasing mutable references to the same component - unsafe { self.as_mutable().into_components_mut_unchecked::() } - } - - /// Consumes self and returns components for the current entity that match the query `Q` for the world lifetime `'w`, - /// or `None` if the entity does not have the components required by the query `Q`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Component)] - /// struct X(usize); - /// #[derive(Component)] - /// struct Y(usize); - /// - /// # let mut world = World::default(); - /// let mut entity = world.spawn((X(0), Y(0))); - /// // Get mutable access to two components at once - /// // SAFETY: X and Y are different components - /// let (mut x, mut y) = - /// unsafe { entity.into_components_mut_unchecked::<(&mut X, &mut Y)>() }.unwrap(); - /// *x = X(1); - /// *y = Y(1); - /// // This would trigger undefined behavior, as the `&mut X`s would alias: - /// // entity.into_components_mut_unchecked::<(&mut X, &mut X)>(); - /// ``` - /// - /// # Safety - /// It is the caller's responsibility to ensure that - /// the `QueryData` does not provide aliasing mutable references to the same component. - pub unsafe fn into_components_mut_unchecked( - self, - ) -> Option> { - // SAFETY: Caller the `QueryData` does not provide aliasing mutable references to the same component - unsafe { self.into_mutable().into_components_mut_unchecked::() } - } - - /// Consumes `self` and gets access to the component of type `T` with - /// the world `'w` lifetime for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn into_borrow(self) -> Option<&'w T> { - self.into_readonly().get() - } - - /// Gets access to the component of type `T` for the current entity, - /// including change detection information as a [`Ref`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_ref(&self) -> Option> { - self.as_readonly().get_ref() - } - - /// Consumes `self` and gets access to the component of type `T` - /// with the world `'w` lifetime for the current entity, - /// including change detection information as a [`Ref`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn into_ref(self) -> Option> { - self.into_readonly().get_ref() - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_mut>(&mut self) -> Option> { - self.as_mutable().into_mut() - } - - /// Temporarily removes a [`Component`] `T` from this [`Entity`] and runs the - /// provided closure on it, returning the result if `T` was available. - /// This will trigger the `Remove` and `Replace` component hooks without - /// causing an archetype move. - /// - /// This is most useful with immutable components, where removal and reinsertion - /// is the only way to modify a value. - /// - /// If you do not need to ensure the above hooks are triggered, and your component - /// is mutable, prefer using [`get_mut`](EntityWorldMut::get_mut). - /// - /// # Examples - /// - /// ```rust - /// # use bevy_ecs::prelude::*; - /// # - /// #[derive(Component, PartialEq, Eq, Debug)] - /// #[component(immutable)] - /// struct Foo(bool); - /// - /// # let mut world = World::default(); - /// # world.register_component::(); - /// # - /// # let entity = world.spawn(Foo(false)).id(); - /// # - /// # let mut entity = world.entity_mut(entity); - /// # - /// # assert_eq!(entity.get::(), Some(&Foo(false))); - /// # - /// entity.modify_component(|foo: &mut Foo| { - /// foo.0 = true; - /// }); - /// # - /// # assert_eq!(entity.get::(), Some(&Foo(true))); - /// ``` - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn modify_component(&mut self, f: impl FnOnce(&mut T) -> R) -> Option { - self.assert_not_despawned(); - - let result = self - .world - .modify_component(self.entity, f) - .expect("entity access must be valid")?; - - self.update_location(); - - Some(result) - } - - /// Temporarily removes a [`Component`] `T` from this [`Entity`] and runs the - /// provided closure on it, returning the result if `T` was available. - /// This will trigger the `Remove` and `Replace` component hooks without - /// causing an archetype move. - /// - /// This is most useful with immutable components, where removal and reinsertion - /// is the only way to modify a value. - /// - /// If you do not need to ensure the above hooks are triggered, and your component - /// is mutable, prefer using [`get_mut`](EntityWorldMut::get_mut). - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn modify_component_by_id( - &mut self, - component_id: ComponentId, - f: impl for<'a> FnOnce(MutUntyped<'a>) -> R, - ) -> Option { - self.assert_not_despawned(); - - let result = self - .world - .modify_component_by_id(self.entity, component_id, f) - .expect("entity access must be valid")?; - - self.update_location(); - - Some(result) - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Safety - /// - /// - `T` must be a mutable component - #[inline] - pub unsafe fn get_mut_assume_mutable(&mut self) -> Option> { - self.as_mutable().into_mut_assume_mutable() - } - - /// Consumes `self` and gets mutable access to the component of type `T` - /// with the world `'w` lifetime for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn into_mut>(self) -> Option> { - // SAFETY: consuming `self` implies exclusive access - unsafe { self.into_unsafe_entity_cell().get_mut() } - } - - /// Consumes `self` and gets mutable access to the component of type `T` - /// with the world `'w` lifetime for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - /// - /// # Safety - /// - /// - `T` must be a mutable component - #[inline] - pub unsafe fn into_mut_assume_mutable(self) -> Option> { - // SAFETY: consuming `self` implies exclusive access - unsafe { self.into_unsafe_entity_cell().get_mut_assume_mutable() } - } - - /// Gets a reference to the resource of the given type - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`get_resource`](EntityWorldMut::get_resource) instead if you want to handle this case. - #[inline] - #[track_caller] - pub fn resource(&self) -> &R { - self.world.resource::() - } - - /// Gets a mutable reference to the resource of the given type - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`get_resource_mut`](World::get_resource_mut) instead if you want to handle this case. - /// - /// If you want to instead insert a value if the resource does not exist, - /// use [`get_resource_or_insert_with`](World::get_resource_or_insert_with). - #[inline] - #[track_caller] - pub fn resource_mut(&mut self) -> Mut<'_, R> { - self.world.resource_mut::() - } - - /// Gets a reference to the resource of the given type if it exists - #[inline] - pub fn get_resource(&self) -> Option<&R> { - self.world.get_resource() - } - - /// Gets a mutable reference to the resource of the given type if it exists - #[inline] - pub fn get_resource_mut(&mut self) -> Option> { - self.world.get_resource_mut() - } - - /// Temporarily removes the requested resource from the [`World`], runs custom user code, - /// then re-adds the resource before returning. - /// - /// # Panics - /// - /// Panics if the resource does not exist. - /// Use [`try_resource_scope`](Self::try_resource_scope) instead if you want to handle this case. - /// - /// See [`World::resource_scope`] for further details. - #[track_caller] - pub fn resource_scope( - &mut self, - f: impl FnOnce(&mut EntityWorldMut, Mut) -> U, - ) -> U { - let id = self.id(); - self.world_scope(|world| { - world.resource_scope(|world, res| { - // Acquiring a new EntityWorldMut here and using that instead of `self` is fine because - // the outer `world_scope` will handle updating our location if it gets changed by the user code - let mut this = world.entity_mut(id); - f(&mut this, res) - }) - }) - } - - /// Temporarily removes the requested resource from the [`World`] if it exists, runs custom user code, - /// then re-adds the resource before returning. Returns `None` if the resource does not exist in the [`World`]. - /// - /// See [`World::try_resource_scope`] for further details. - pub fn try_resource_scope( - &mut self, - f: impl FnOnce(&mut EntityWorldMut, Mut) -> U, - ) -> Option { - let id = self.id(); - self.world_scope(|world| { - world.try_resource_scope(|world, res| { - // Acquiring a new EntityWorldMut here and using that instead of `self` is fine because - // the outer `world_scope` will handle updating our location if it gets changed by the user code - let mut this = world.entity_mut(id); - f(&mut this, res) - }) - }) - } - - /// Retrieves the change ticks for the given component. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_change_ticks(&self) -> Option { - self.as_readonly().get_change_ticks::() - } - - /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. - /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. - /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_changed_by(&self) -> Option { - self.as_readonly().get_changed_by::() - } - - /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - self.as_readonly().get_change_ticks_by_id(component_id) - } - - /// Returns [untyped read-only reference(s)](Ptr) to component(s) for the - /// current entity, based on the given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::get`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityWorldMut::get`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityRef::get_by_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_by_id( - &self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.as_readonly().get_by_id(component_ids) - } - - /// Consumes `self` and returns [untyped read-only reference(s)](Ptr) to - /// component(s) with lifetime `'w` for the current entity, based on the - /// given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::into_borrow`] - /// where possible and only use this in cases where the actual component - /// types are not known at compile time.** - /// - /// Unlike [`EntityWorldMut::into_borrow`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityRef::get_by_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn into_borrow_by_id( - self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.into_readonly().get_by_id(component_ids) - } - - /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for - /// the current entity, based on the given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::get_mut`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityWorldMut::get_mut`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityMut::get_mut_by_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn get_mut_by_id( - &mut self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.as_mutable().into_mut_by_id(component_ids) - } - - /// Returns [untyped mutable reference(s)](MutUntyped) to component(s) for - /// the current entity, based on the given [`ComponentId`]s. - /// Assumes the given [`ComponentId`]s refer to mutable components. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::get_mut_assume_mutable`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityWorldMut::get_mut_assume_mutable`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the provided [`ComponentId`]s must refer to mutable components. - #[inline] - pub unsafe fn get_mut_assume_mutable_by_id( - &mut self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.as_mutable() - .into_mut_assume_mutable_by_id(component_ids) - } - - /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) - /// to component(s) with lifetime `'w` for the current entity, based on the - /// given [`ComponentId`]s. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::into_mut`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityWorldMut::into_mut`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Examples - /// - /// For examples on how to use this method, see [`EntityMut::get_mut_by_id`]. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[inline] - pub fn into_mut_by_id( - self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.into_mutable().into_mut_by_id(component_ids) - } - - /// Consumes `self` and returns [untyped mutable reference(s)](MutUntyped) - /// to component(s) with lifetime `'w` for the current entity, based on the - /// given [`ComponentId`]s. - /// Assumes the given [`ComponentId`]s refer to mutable components. - /// - /// **You should prefer to use the typed API [`EntityWorldMut::into_mut_assume_mutable`] where - /// possible and only use this in cases where the actual component types - /// are not known at compile time.** - /// - /// Unlike [`EntityWorldMut::into_mut_assume_mutable`], this returns untyped reference(s) to - /// component(s), and it's the job of the caller to ensure the correct - /// type(s) are dereferenced (if necessary). - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if the entity does - /// not have a component. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component - /// is requested multiple times. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - /// - /// # Safety - /// It is the callers responsibility to ensure that - /// - the provided [`ComponentId`]s must refer to mutable components. - #[inline] - pub unsafe fn into_mut_assume_mutable_by_id( - self, - component_ids: F, - ) -> Result, EntityComponentError> { - self.into_mutable() - .into_mut_assume_mutable_by_id(component_ids) - } - - /// Adds a [`Bundle`] of components to the entity. - /// - /// This will overwrite any previous value(s) of the same component type. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn insert(&mut self, bundle: T) -> &mut Self { - move_as_ptr!(bundle); - self.insert_with_caller( - bundle, - InsertMode::Replace, - MaybeLocation::caller(), - RelationshipHookMode::Run, - ) - } - - /// Adds a [`Bundle`] of components to the entity. - /// [`Relationship`](crate::relationship::Relationship) components in the bundle will follow the configuration - /// in `relationship_hook_mode`. - /// - /// This will overwrite any previous value(s) of the same component type. - /// - /// # Warning - /// - /// This can easily break the integrity of relationships. This is intended to be used for cloning and spawning code internals, - /// not most user-facing scenarios. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn insert_with_relationship_hook_mode( - &mut self, - bundle: T, - relationship_hook_mode: RelationshipHookMode, - ) -> &mut Self { - move_as_ptr!(bundle); - self.insert_with_caller( - bundle, - InsertMode::Replace, - MaybeLocation::caller(), - relationship_hook_mode, - ) - } - - /// Adds a [`Bundle`] of components to the entity without overwriting. - /// - /// This will leave any previous value(s) of the same component type - /// unchanged. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn insert_if_new(&mut self, bundle: T) -> &mut Self { - move_as_ptr!(bundle); - self.insert_with_caller( - bundle, - InsertMode::Keep, - MaybeLocation::caller(), - RelationshipHookMode::Run, - ) - } - - /// Adds a [`Bundle`] of components to the entity. - #[inline] - pub(crate) fn insert_with_caller( - &mut self, - bundle: MovingPtr<'_, T>, - mode: InsertMode, - caller: MaybeLocation, - relationship_hook_mode: RelationshipHookMode, - ) -> &mut Self { - let location = self.location(); - let change_tick = self.world.change_tick(); - let mut bundle_inserter = - BundleInserter::new::(self.world, location.archetype_id, change_tick); - // SAFETY: - // - `location` matches current entity and thus must currently exist in the source - // archetype for this inserter and its location within the archetype. - // - `T` matches the type used to create the `BundleInserter`. - // - `apply_effect` is called exactly once after this function. - // - The value pointed at by `bundle` is not accessed for anything other than `apply_effect` - // and the caller ensures that the value is not accessed or dropped after this function - // returns. - let (bundle, location) = bundle.partial_move(|bundle| unsafe { - bundle_inserter.insert( - self.entity, - location, - bundle, - mode, - caller, - relationship_hook_mode, - ) - }); - self.location = Some(location); - self.world.flush(); - self.update_location(); - // SAFETY: - // - This is called exactly once after the `BundleInsert::insert` call before returning to safe code. - // - `bundle` points to the same `B` that `BundleInsert::insert` was called on. - unsafe { T::apply_effect(bundle, self) }; - self - } - - /// Inserts a dynamic [`Component`] into the entity. - /// - /// This will overwrite any previous value(s) of the same component type. - /// - /// You should prefer to use the typed API [`EntityWorldMut::insert`] where possible. - /// - /// # Safety - /// - /// - [`ComponentId`] must be from the same world as [`EntityWorldMut`] - /// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`] - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub unsafe fn insert_by_id( - &mut self, - component_id: ComponentId, - component: OwningPtr<'_>, - ) -> &mut Self { - self.insert_by_id_with_caller( - component_id, - component, - InsertMode::Replace, - MaybeLocation::caller(), - RelationshipHookMode::Run, - ) - } - - /// # Safety - /// - /// - [`ComponentId`] must be from the same world as [`EntityWorldMut`] - /// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`] - #[inline] - pub(crate) unsafe fn insert_by_id_with_caller( - &mut self, - component_id: ComponentId, - component: OwningPtr<'_>, - mode: InsertMode, - caller: MaybeLocation, - relationship_hook_insert_mode: RelationshipHookMode, - ) -> &mut Self { - let location = self.location(); - let change_tick = self.world.change_tick(); - let bundle_id = self.world.bundles.init_component_info( - &mut self.world.storages, - &self.world.components, - component_id, - ); - let storage_type = self.world.bundles.get_storage_unchecked(bundle_id); - - let bundle_inserter = - BundleInserter::new_with_id(self.world, location.archetype_id, bundle_id, change_tick); - - self.location = Some(insert_dynamic_bundle( - bundle_inserter, - self.entity, - location, - Some(component).into_iter(), - Some(storage_type).iter().cloned(), - mode, - caller, - relationship_hook_insert_mode, - )); - self.world.flush(); - self.update_location(); - self - } - - /// Inserts a dynamic [`Bundle`] into the entity. - /// - /// This will overwrite any previous value(s) of the same component type. - /// - /// You should prefer to use the typed API [`EntityWorldMut::insert`] where possible. - /// If your [`Bundle`] only has one component, use the cached API [`EntityWorldMut::insert_by_id`]. - /// - /// If possible, pass a sorted slice of `ComponentId` to maximize caching potential. - /// - /// # Safety - /// - Each [`ComponentId`] must be from the same world as [`EntityWorldMut`] - /// - Each [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`] - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub unsafe fn insert_by_ids<'a, I: Iterator>>( - &mut self, - component_ids: &[ComponentId], - iter_components: I, - ) -> &mut Self { - self.insert_by_ids_internal(component_ids, iter_components, RelationshipHookMode::Run) - } - - #[track_caller] - pub(crate) unsafe fn insert_by_ids_internal<'a, I: Iterator>>( - &mut self, - component_ids: &[ComponentId], - iter_components: I, - relationship_hook_insert_mode: RelationshipHookMode, - ) -> &mut Self { - let location = self.location(); - let change_tick = self.world.change_tick(); - let bundle_id = self.world.bundles.init_dynamic_info( - &mut self.world.storages, - &self.world.components, - component_ids, - ); - let mut storage_types = - core::mem::take(self.world.bundles.get_storages_unchecked(bundle_id)); - let bundle_inserter = - BundleInserter::new_with_id(self.world, location.archetype_id, bundle_id, change_tick); - - self.location = Some(insert_dynamic_bundle( - bundle_inserter, - self.entity, - location, - iter_components, - (*storage_types).iter().cloned(), - InsertMode::Replace, - MaybeLocation::caller(), - relationship_hook_insert_mode, - )); - *self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types); - self.world.flush(); - self.update_location(); - self - } - - /// Removes all components in the [`Bundle`] from the entity and returns their previous values. - /// - /// **Note:** If the entity does not have every component in the bundle, this method will not - /// remove any of them. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[must_use] - #[track_caller] - pub fn take(&mut self) -> Option { - let location = self.location(); - let entity = self.entity; - - let mut remover = - // SAFETY: The archetype id must be valid since this entity is in it. - unsafe { BundleRemover::new::(self.world, location.archetype_id, true) }?; - // SAFETY: The passed location has the sane archetype as the remover, since they came from the same location. - let (new_location, result) = unsafe { - remover.remove( - entity, - location, - MaybeLocation::caller(), - |sets, table, components, bundle_components| { - let mut bundle_components = bundle_components.iter().copied(); - ( - false, - T::from_components(&mut (sets, table), &mut |(sets, table)| { - let component_id = bundle_components.next().unwrap(); - // SAFETY: the component existed to be removed, so its id must be valid. - let component_info = components.get_info_unchecked(component_id); - match component_info.storage_type() { - StorageType::Table => { - table - .as_mut() - // SAFETY: The table must be valid if the component is in it. - .debug_checked_unwrap() - // SAFETY: The remover is cleaning this up. - .take_component(component_id, location.table_row) - } - StorageType::SparseSet => sets - .get_mut(component_id) - .unwrap() - .remove_and_forget(entity) - .unwrap(), - } - }), - ) - }, - ) - }; - self.location = Some(new_location); - - self.world.flush(); - self.update_location(); - Some(result) - } - - /// Removes any components in the [`Bundle`] from the entity. - /// - /// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn remove(&mut self) -> &mut Self { - self.remove_with_caller::(MaybeLocation::caller()) - } - - #[inline] - pub(crate) fn remove_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { - let location = self.location(); - - let Some(mut remover) = - // SAFETY: The archetype id must be valid since this entity is in it. - (unsafe { BundleRemover::new::(self.world, location.archetype_id, false) }) - else { - return self; - }; - // SAFETY: The remover archetype came from the passed location and the removal can not fail. - let new_location = unsafe { - remover.remove( - self.entity, - location, - caller, - BundleRemover::empty_pre_remove, - ) - } - .0; - - self.location = Some(new_location); - self.world.flush(); - self.update_location(); - self - } - - /// Removes all components in the [`Bundle`] and remove all required components for each component in the bundle - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn remove_with_requires(&mut self) -> &mut Self { - self.remove_with_requires_with_caller::(MaybeLocation::caller()) - } - - pub(crate) fn remove_with_requires_with_caller( - &mut self, - caller: MaybeLocation, - ) -> &mut Self { - let location = self.location(); - let bundle_id = self.world.register_contributed_bundle_info::(); - - // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. - let Some(mut remover) = (unsafe { - BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false) - }) else { - return self; - }; - // SAFETY: The remover archetype came from the passed location and the removal can not fail. - let new_location = unsafe { - remover.remove( - self.entity, - location, - caller, - BundleRemover::empty_pre_remove, - ) - } - .0; - - self.location = Some(new_location); - self.world.flush(); - self.update_location(); - self - } - - /// Removes any components except those in the [`Bundle`] (and its Required Components) from the entity. - /// - /// See [`EntityCommands::retain`](crate::system::EntityCommands::retain) for more details. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn retain(&mut self) -> &mut Self { - self.retain_with_caller::(MaybeLocation::caller()) - } - - #[inline] - pub(crate) fn retain_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { - let old_location = self.location(); - let retained_bundle = self.world.register_bundle_info::(); - let archetypes = &mut self.world.archetypes; - - // SAFETY: `retained_bundle` exists as we just registered it. - let retained_bundle_info = unsafe { self.world.bundles.get_unchecked(retained_bundle) }; - let old_archetype = &mut archetypes[old_location.archetype_id]; - - // PERF: this could be stored in an Archetype Edge - let to_remove = &old_archetype - .iter_components() - .filter(|c| !retained_bundle_info.contributed_components().contains(c)) - .collect::>(); - let remove_bundle = self.world.bundles.init_dynamic_info( - &mut self.world.storages, - &self.world.components, - to_remove, - ); - - // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. - let Some(mut remover) = (unsafe { - BundleRemover::new_with_id(self.world, old_location.archetype_id, remove_bundle, false) - }) else { - return self; - }; - // SAFETY: The remover archetype came from the passed location and the removal can not fail. - let new_location = unsafe { - remover.remove( - self.entity, - old_location, - caller, - BundleRemover::empty_pre_remove, - ) - } - .0; - - self.location = Some(new_location); - self.world.flush(); - self.update_location(); - self - } - - /// Removes a dynamic [`Component`] from the entity if it exists. - /// - /// You should prefer to use the typed API [`EntityWorldMut::remove`] where possible. - /// - /// # Panics - /// - /// Panics if the provided [`ComponentId`] does not exist in the [`World`] or if the - /// entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn remove_by_id(&mut self, component_id: ComponentId) -> &mut Self { - self.remove_by_id_with_caller(component_id, MaybeLocation::caller()) - } - - #[inline] - pub(crate) fn remove_by_id_with_caller( - &mut self, - component_id: ComponentId, - caller: MaybeLocation, - ) -> &mut Self { - let location = self.location(); - let components = &mut self.world.components; - - let bundle_id = self.world.bundles.init_component_info( - &mut self.world.storages, - components, - component_id, - ); - - // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. - let Some(mut remover) = (unsafe { - BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false) - }) else { - return self; - }; - // SAFETY: The remover archetype came from the passed location and the removal can not fail. - let new_location = unsafe { - remover.remove( - self.entity, - location, - caller, - BundleRemover::empty_pre_remove, - ) - } - .0; - - self.location = Some(new_location); - self.world.flush(); - self.update_location(); - self - } - - /// Removes a dynamic bundle from the entity if it exists. - /// - /// You should prefer to use the typed API [`EntityWorldMut::remove`] where possible. - /// - /// # Panics - /// - /// Panics if any of the provided [`ComponentId`]s do not exist in the [`World`] or if the - /// entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn remove_by_ids(&mut self, component_ids: &[ComponentId]) -> &mut Self { - self.remove_by_ids_with_caller( - component_ids, - MaybeLocation::caller(), - RelationshipHookMode::Run, - BundleRemover::empty_pre_remove, - ) - } - - #[inline] - pub(crate) fn remove_by_ids_with_caller( - &mut self, - component_ids: &[ComponentId], - caller: MaybeLocation, - relationship_hook_mode: RelationshipHookMode, - pre_remove: impl FnOnce( - &mut SparseSets, - Option<&mut Table>, - &Components, - &[ComponentId], - ) -> (bool, T), - ) -> &mut Self { - let location = self.location(); - let components = &mut self.world.components; - - let bundle_id = self.world.bundles.init_dynamic_info( - &mut self.world.storages, - components, - component_ids, - ); - - // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. - let Some(mut remover) = (unsafe { - BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false) - }) else { - return self; - }; - remover.relationship_hook_mode = relationship_hook_mode; - // SAFETY: The remover archetype came from the passed location and the removal can not fail. - let new_location = unsafe { remover.remove(self.entity, location, caller, pre_remove) }.0; - - self.location = Some(new_location); - self.world.flush(); - self.update_location(); - self - } - - /// Removes all components associated with the entity. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn clear(&mut self) -> &mut Self { - self.clear_with_caller(MaybeLocation::caller()) - } - - #[inline] - pub(crate) fn clear_with_caller(&mut self, caller: MaybeLocation) -> &mut Self { - let location = self.location(); - // PERF: this should not be necessary - let component_ids: Vec = self.archetype().components().to_vec(); - let components = &mut self.world.components; - - let bundle_id = self.world.bundles.init_dynamic_info( - &mut self.world.storages, - components, - component_ids.as_slice(), - ); - - // SAFETY: We just created the bundle, and the archetype is valid, since we are in it. - let Some(mut remover) = (unsafe { - BundleRemover::new_with_id(self.world, location.archetype_id, bundle_id, false) - }) else { - return self; - }; - // SAFETY: The remover archetype came from the passed location and the removal can not fail. - let new_location = unsafe { - remover.remove( - self.entity, - location, - caller, - BundleRemover::empty_pre_remove, - ) - } - .0; - - self.location = Some(new_location); - self.world.flush(); - self.update_location(); - self - } - - /// Despawns the current entity. - /// - /// See [`World::despawn`] for more details. - /// - /// # Note - /// - /// This will also despawn any [`Children`](crate::hierarchy::Children) entities, and any other [`RelationshipTarget`](crate::relationship::RelationshipTarget) that is configured - /// to despawn descendants. This results in "recursive despawn" behavior. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - #[track_caller] - pub fn despawn(self) { - self.despawn_with_caller(MaybeLocation::caller()); - } - - pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) { - let location = self.location(); - let world = self.world; - let archetype = &world.archetypes[location.archetype_id]; - - // SAFETY: Archetype cannot be mutably aliased by DeferredWorld - let (archetype, mut deferred_world) = unsafe { - let archetype: *const Archetype = archetype; - let world = world.as_unsafe_world_cell(); - (&*archetype, world.into_deferred()) - }; - - // SAFETY: All components in the archetype exist in world - unsafe { - if archetype.has_despawn_observer() { - // SAFETY: the DESPAWN event_key corresponds to the Despawn event's type - deferred_world.trigger_raw( - DESPAWN, - &mut Despawn { - entity: self.entity, - }, - &mut EntityComponentsTrigger { - components: archetype.components(), - }, - caller, - ); - } - deferred_world.trigger_on_despawn( - archetype, - self.entity, - archetype.iter_components(), - caller, - ); - if archetype.has_replace_observer() { - // SAFETY: the REPLACE event_key corresponds to the Replace event's type - deferred_world.trigger_raw( - REPLACE, - &mut Replace { - entity: self.entity, - }, - &mut EntityComponentsTrigger { - components: archetype.components(), - }, - caller, - ); - } - deferred_world.trigger_on_replace( - archetype, - self.entity, - archetype.iter_components(), - caller, - RelationshipHookMode::Run, - ); - if archetype.has_remove_observer() { - // SAFETY: the REMOVE event_key corresponds to the Remove event's type - deferred_world.trigger_raw( - REMOVE, - &mut Remove { - entity: self.entity, - }, - &mut EntityComponentsTrigger { - components: archetype.components(), - }, - caller, - ); - } - deferred_world.trigger_on_remove( - archetype, - self.entity, - archetype.iter_components(), - caller, - ); - } - - for component_id in archetype.iter_components() { - world.removed_components.write(component_id, self.entity); - } - - // Observers and on_remove hooks may reserve new entities, which - // requires a flush before Entities::free may be called. - world.flush_entities(); - - let location = world - .entities - .free(self.entity) - .flatten() - .expect("entity should exist at this point."); - let table_row; - let moved_entity; - let change_tick = world.change_tick(); - - { - let archetype = &mut world.archetypes[location.archetype_id]; - let remove_result = archetype.swap_remove(location.archetype_row); - if let Some(swapped_entity) = remove_result.swapped_entity { - let swapped_location = world.entities.get(swapped_entity).unwrap(); - // SAFETY: swapped_entity is valid and the swapped entity's components are - // moved to the new location immediately after. - unsafe { - world.entities.set( - swapped_entity.index(), - Some(EntityLocation { - archetype_id: swapped_location.archetype_id, - archetype_row: location.archetype_row, - table_id: swapped_location.table_id, - table_row: swapped_location.table_row, - }), - ); - } - } - table_row = remove_result.table_row; - - for component_id in archetype.sparse_set_components() { - // set must have existed for the component to be added. - let sparse_set = world.storages.sparse_sets.get_mut(component_id).unwrap(); - sparse_set.remove(self.entity); - } - // SAFETY: table rows stored in archetypes always exist - moved_entity = unsafe { - world.storages.tables[archetype.table_id()].swap_remove_unchecked(table_row) - }; - }; - - if let Some(moved_entity) = moved_entity { - let moved_location = world.entities.get(moved_entity).unwrap(); - // SAFETY: `moved_entity` is valid and the provided `EntityLocation` accurately reflects - // the current location of the entity and its component data. - unsafe { - world.entities.set( - moved_entity.index(), - Some(EntityLocation { - archetype_id: moved_location.archetype_id, - archetype_row: moved_location.archetype_row, - table_id: moved_location.table_id, - table_row, - }), - ); - } - world.archetypes[moved_location.archetype_id] - .set_entity_table_row(moved_location.archetype_row, table_row); - } - - // SAFETY: `self.entity` is a valid entity index - unsafe { - world - .entities - .mark_spawn_despawn(self.entity.index(), caller, change_tick); - } - - world.flush(); - } - - /// Ensures any commands triggered by the actions of Self are applied, equivalent to [`World::flush`] - pub fn flush(self) -> Entity { - self.world.flush(); - self.entity - } - - /// Gets read-only access to the world that the current entity belongs to. - #[inline] - pub fn world(&self) -> &World { - self.world - } - - /// Returns this entity's world. - /// - /// See [`EntityWorldMut::world_scope`] or [`EntityWorldMut::into_world_mut`] for a safe alternative. - /// - /// # Safety - /// Caller must not modify the world in a way that changes the current entity's location - /// If the caller _does_ do something that could change the location, `self.update_location()` - /// must be called before using any other methods on this [`EntityWorldMut`]. - #[inline] - pub unsafe fn world_mut(&mut self) -> &mut World { - self.world - } - - /// Returns this entity's [`World`], consuming itself. - #[inline] - pub fn into_world_mut(self) -> &'w mut World { - self.world - } - - /// Gives mutable access to this entity's [`World`] in a temporary scope. - /// This is a safe alternative to using [`EntityWorldMut::world_mut`]. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Resource, Default, Clone, Copy)] - /// struct R(u32); - /// - /// # let mut world = World::new(); - /// # world.init_resource::(); - /// # let mut entity = world.spawn_empty(); - /// // This closure gives us temporary access to the world. - /// let new_r = entity.world_scope(|world: &mut World| { - /// // Mutate the world while we have access to it. - /// let mut r = world.resource_mut::(); - /// r.0 += 1; - /// - /// // Return a value from the world before giving it back to the `EntityWorldMut`. - /// *r - /// }); - /// # assert_eq!(new_r.0, 1); - /// ``` - pub fn world_scope(&mut self, f: impl FnOnce(&mut World) -> U) -> U { - struct Guard<'w, 'a> { - entity_mut: &'a mut EntityWorldMut<'w>, - } - - impl Drop for Guard<'_, '_> { - #[inline] - fn drop(&mut self) { - self.entity_mut.update_location(); - } - } - - // When `guard` is dropped at the end of this scope, - // it will update the cached `EntityLocation` for this instance. - // This will run even in case the closure `f` unwinds. - let guard = Guard { entity_mut: self }; - f(guard.entity_mut.world) - } - - /// Updates the internal entity location to match the current location in the internal - /// [`World`]. - /// - /// This is *only* required when using the unsafe function [`EntityWorldMut::world_mut`], - /// which enables the location to change. - pub fn update_location(&mut self) { - self.location = self.world.entities().get(self.entity); - } - - /// Returns if the entity has been despawned. - /// - /// Normally it shouldn't be needed to explicitly check if the entity has been despawned - /// between commands as this shouldn't happen. However, for some special cases where it - /// is known that a hook or an observer might despawn the entity while a [`EntityWorldMut`] - /// reference is still held, this method can be used to check if the entity is still alive - /// to avoid panicking when calling further methods. - #[inline] - pub fn is_despawned(&self) -> bool { - self.location.is_none() - } - - /// Gets an Entry into the world for this entity and component for in-place manipulation. - /// - /// The type parameter specifies which component to get. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn_empty(); - /// entity.entry().or_insert_with(|| Comp(4)); - /// # let entity_id = entity.id(); - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 4); - /// - /// # let mut entity = world.get_entity_mut(entity_id).unwrap(); - /// entity.entry::().and_modify(|mut c| c.0 += 1); - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 5); - /// ``` - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - pub fn entry<'a, T: Component>(&'a mut self) -> ComponentEntry<'w, 'a, T> { - if self.contains::() { - ComponentEntry::Occupied(OccupiedComponentEntry { - entity_world: self, - _marker: PhantomData, - }) - } else { - ComponentEntry::Vacant(VacantComponentEntry { - entity_world: self, - _marker: PhantomData, - }) - } - } - - /// Creates an [`Observer`] watching for an [`EntityEvent`] of type `E` whose [`EntityEvent::event_target`] - /// targets this entity. - /// - /// # Panics - /// - /// If the entity has been despawned while this `EntityWorldMut` is still alive. - /// - /// Panics if the given system is an exclusive system. - #[track_caller] - pub fn observe( - &mut self, - observer: impl IntoObserverSystem, - ) -> &mut Self { - self.observe_with_caller(observer, MaybeLocation::caller()) - } - - pub(crate) fn observe_with_caller( - &mut self, - observer: impl IntoObserverSystem, - caller: MaybeLocation, - ) -> &mut Self { - self.assert_not_despawned(); - let bundle = Observer::new(observer).with_entity(self.entity); - move_as_ptr!(bundle); - self.world.spawn_with_caller(bundle, caller); - self.world.flush(); - self.update_location(); - self - } - - /// Clones parts of an entity (components, observers, etc.) onto another entity, - /// configured through [`EntityClonerBuilder`]. - /// - /// The other entity will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) except those that are - /// [denied](EntityClonerBuilder::deny) in the `config`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentA; - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentB; - /// # let mut world = World::new(); - /// # let entity = world.spawn((ComponentA, ComponentB)).id(); - /// # let target = world.spawn_empty().id(); - /// // Clone all components except ComponentA onto the target. - /// world.entity_mut(entity).clone_with_opt_out(target, |builder| { - /// builder.deny::(); - /// }); - /// # assert_eq!(world.get::(target), None); - /// # assert_eq!(world.get::(target), Some(&ComponentB)); - /// ``` - /// - /// See [`EntityClonerBuilder`] for more options. - /// - /// # Panics - /// - /// - If this entity has been despawned while this `EntityWorldMut` is still alive. - /// - If the target entity does not exist. - pub fn clone_with_opt_out( - &mut self, - target: Entity, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, - ) -> &mut Self { - self.assert_not_despawned(); - - let mut builder = EntityCloner::build_opt_out(self.world); - config(&mut builder); - builder.clone_entity(self.entity, target); - - self.world.flush(); - self.update_location(); - self - } - - /// Clones parts of an entity (components, observers, etc.) onto another entity, - /// configured through [`EntityClonerBuilder`]. - /// - /// The other entity will receive only the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) and are - /// [allowed](EntityClonerBuilder::allow) in the `config`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentA; - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentB; - /// # let mut world = World::new(); - /// # let entity = world.spawn((ComponentA, ComponentB)).id(); - /// # let target = world.spawn_empty().id(); - /// // Clone only ComponentA onto the target. - /// world.entity_mut(entity).clone_with_opt_in(target, |builder| { - /// builder.allow::(); - /// }); - /// # assert_eq!(world.get::(target), Some(&ComponentA)); - /// # assert_eq!(world.get::(target), None); - /// ``` - /// - /// See [`EntityClonerBuilder`] for more options. - /// - /// # Panics - /// - /// - If this entity has been despawned while this `EntityWorldMut` is still alive. - /// - If the target entity does not exist. - pub fn clone_with_opt_in( - &mut self, - target: Entity, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, - ) -> &mut Self { - self.assert_not_despawned(); - - let mut builder = EntityCloner::build_opt_in(self.world); - config(&mut builder); - builder.clone_entity(self.entity, target); - - self.world.flush(); - self.update_location(); - self - } - - /// Spawns a clone of this entity and returns the [`Entity`] of the clone. - /// - /// The clone will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). - /// - /// To configure cloning behavior (such as only cloning certain components), - /// use [`EntityWorldMut::clone_and_spawn_with_opt_out`]/ - /// [`opt_in`](`EntityWorldMut::clone_and_spawn_with_opt_in`). - /// - /// # Panics - /// - /// If this entity has been despawned while this `EntityWorldMut` is still alive. - pub fn clone_and_spawn(&mut self) -> Entity { - self.clone_and_spawn_with_opt_out(|_| {}) - } - - /// Spawns a clone of this entity and allows configuring cloning behavior - /// using [`EntityClonerBuilder`], returning the [`Entity`] of the clone. - /// - /// The clone will receive all the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) except those that are - /// [denied](EntityClonerBuilder::deny) in the `config`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut world = World::new(); - /// # let entity = world.spawn((ComponentA, ComponentB)).id(); - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentA; - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentB; - /// // Create a clone of an entity but without ComponentA. - /// let entity_clone = world.entity_mut(entity).clone_and_spawn_with_opt_out(|builder| { - /// builder.deny::(); - /// }); - /// # assert_eq!(world.get::(entity_clone), None); - /// # assert_eq!(world.get::(entity_clone), Some(&ComponentB)); - /// ``` - /// - /// See [`EntityClonerBuilder`] for more options. - /// - /// # Panics - /// - /// If this entity has been despawned while this `EntityWorldMut` is still alive. - pub fn clone_and_spawn_with_opt_out( - &mut self, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, - ) -> Entity { - self.assert_not_despawned(); - - let entity_clone = self.world.entities.reserve_entity(); - self.world.flush(); - - let mut builder = EntityCloner::build_opt_out(self.world); - config(&mut builder); - builder.clone_entity(self.entity, entity_clone); - - self.world.flush(); - self.update_location(); - entity_clone - } - - /// Spawns a clone of this entity and allows configuring cloning behavior - /// using [`EntityClonerBuilder`], returning the [`Entity`] of the clone. - /// - /// The clone will receive only the components of the original that implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect) and are - /// [allowed](EntityClonerBuilder::allow) in the `config`. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// # let mut world = World::new(); - /// # let entity = world.spawn((ComponentA, ComponentB)).id(); - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentA; - /// # #[derive(Component, Clone, PartialEq, Debug)] - /// # struct ComponentB; - /// // Create a clone of an entity but only with ComponentA. - /// let entity_clone = world.entity_mut(entity).clone_and_spawn_with_opt_in(|builder| { - /// builder.allow::(); - /// }); - /// # assert_eq!(world.get::(entity_clone), Some(&ComponentA)); - /// # assert_eq!(world.get::(entity_clone), None); - /// ``` - /// - /// See [`EntityClonerBuilder`] for more options. - /// - /// # Panics - /// - /// If this entity has been despawned while this `EntityWorldMut` is still alive. - pub fn clone_and_spawn_with_opt_in( - &mut self, - config: impl FnOnce(&mut EntityClonerBuilder) + Send + Sync + 'static, - ) -> Entity { - self.assert_not_despawned(); - - let entity_clone = self.world.entities.reserve_entity(); - self.world.flush(); - - let mut builder = EntityCloner::build_opt_in(self.world); - config(&mut builder); - builder.clone_entity(self.entity, entity_clone); - - self.world.flush(); - self.update_location(); - entity_clone - } - - /// Clones the specified components of this entity and inserts them into another entity. - /// - /// Components can only be cloned if they implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). - /// - /// # Panics - /// - /// - If this entity has been despawned while this `EntityWorldMut` is still alive. - /// - If the target entity does not exist. - pub fn clone_components(&mut self, target: Entity) -> &mut Self { - self.assert_not_despawned(); - - EntityCloner::build_opt_in(self.world) - .allow::() - .clone_entity(self.entity, target); - - self.world.flush(); - self.update_location(); - self - } - - /// Clones the specified components of this entity and inserts them into another entity, - /// then removes the components from this entity. - /// - /// Components can only be cloned if they implement - /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). - /// - /// # Panics - /// - /// - If this entity has been despawned while this `EntityWorldMut` is still alive. - /// - If the target entity does not exist. - pub fn move_components(&mut self, target: Entity) -> &mut Self { - self.assert_not_despawned(); - - EntityCloner::build_opt_in(self.world) - .allow::() - .move_components(true) - .clone_entity(self.entity, target); - - self.world.flush(); - self.update_location(); - self - } - - /// Returns the source code location from which this entity has last been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.world() - .entities() - .entity_get_spawned_or_despawned_by(self.entity) - .map(|location| location.unwrap()) - } - - /// Returns the [`Tick`] at which this entity has last been spawned. - pub fn spawn_tick(&self) -> Tick { - self.assert_not_despawned(); - - // SAFETY: entity being alive was asserted - unsafe { - self.world() - .entities() - .entity_get_spawned_or_despawned_unchecked(self.entity) - .1 - } - } - - /// Reborrows this entity in a temporary scope. - /// This is useful for executing a function that requires a `EntityWorldMut` - /// but you do not want to move out the entity ownership. - pub fn reborrow_scope(&mut self, f: impl FnOnce(EntityWorldMut) -> U) -> U { - let Self { - entity, location, .. - } = *self; - self.world_scope(move |world| { - f(EntityWorldMut { - world, - entity, - location, - }) - }) - } - - /// Passes the current entity into the given function, and triggers the [`EntityEvent`] returned by that function. - /// See [`EntityCommands::trigger`] for usage examples - /// - /// [`EntityCommands::trigger`]: crate::system::EntityCommands::trigger - #[track_caller] - pub fn trigger<'t, E: EntityEvent: Default>>( - &mut self, - event_fn: impl FnOnce(Entity) -> E, - ) -> &mut Self { - let mut event = (event_fn)(self.entity); - let caller = MaybeLocation::caller(); - self.world_scope(|world| { - world.trigger_ref_with_caller( - &mut event, - &mut as Default>::default(), - caller, - ); - }); - self - } -} - -/// A view into a single entity and component in a world, which may either be vacant or occupied. -/// -/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`]. -/// -/// [`entry`]: EntityWorldMut::entry -pub enum ComponentEntry<'w, 'a, T: Component> { - /// An occupied entry. - Occupied(OccupiedComponentEntry<'w, 'a, T>), - /// A vacant entry. - Vacant(VacantComponentEntry<'w, 'a, T>), -} - -impl<'w, 'a, T: Component> ComponentEntry<'w, 'a, T> { - /// Provides in-place mutable access to an occupied entry. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn(Comp(0)); - /// - /// entity.entry::().and_modify(|mut c| c.0 += 1); - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 1); - /// ``` - #[inline] - pub fn and_modify)>(self, f: F) -> Self { - match self { - ComponentEntry::Occupied(mut entry) => { - f(entry.get_mut()); - ComponentEntry::Occupied(entry) - } - ComponentEntry::Vacant(entry) => ComponentEntry::Vacant(entry), - } - } -} - -impl<'w, 'a, T: Component> ComponentEntry<'w, 'a, T> { - /// Replaces the component of the entry, and returns an [`OccupiedComponentEntry`]. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn_empty(); - /// - /// let entry = entity.entry().insert_entry(Comp(4)); - /// assert_eq!(entry.get(), &Comp(4)); - /// - /// let entry = entity.entry().insert_entry(Comp(2)); - /// assert_eq!(entry.get(), &Comp(2)); - /// ``` - #[inline] - pub fn insert_entry(self, component: T) -> OccupiedComponentEntry<'w, 'a, T> { - match self { - ComponentEntry::Occupied(mut entry) => { - entry.insert(component); - entry - } - ComponentEntry::Vacant(entry) => entry.insert(component), - } - } - - /// Ensures the entry has this component by inserting the given default if empty, and - /// returns a mutable reference to this component in the entry. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn_empty(); - /// - /// entity.entry().or_insert(Comp(4)); - /// # let entity_id = entity.id(); - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 4); - /// - /// # let mut entity = world.get_entity_mut(entity_id).unwrap(); - /// entity.entry().or_insert(Comp(15)).into_mut().0 *= 2; - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 8); - /// ``` - #[inline] - pub fn or_insert(self, default: T) -> OccupiedComponentEntry<'w, 'a, T> { - match self { - ComponentEntry::Occupied(entry) => entry, - ComponentEntry::Vacant(entry) => entry.insert(default), - } - } - - /// Ensures the entry has this component by inserting the result of the default function if - /// empty, and returns a mutable reference to this component in the entry. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn_empty(); - /// - /// entity.entry().or_insert_with(|| Comp(4)); - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 4); - /// ``` - #[inline] - pub fn or_insert_with T>(self, default: F) -> OccupiedComponentEntry<'w, 'a, T> { - match self { - ComponentEntry::Occupied(entry) => entry, - ComponentEntry::Vacant(entry) => entry.insert(default()), - } - } -} - -impl<'w, 'a, T: Component + Default> ComponentEntry<'w, 'a, T> { - /// Ensures the entry has this component by inserting the default value if empty, and - /// returns a mutable reference to this component in the entry. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::prelude::*; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn_empty(); - /// - /// entity.entry::().or_default(); - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 0); - /// ``` - #[inline] - pub fn or_default(self) -> OccupiedComponentEntry<'w, 'a, T> { - match self { - ComponentEntry::Occupied(entry) => entry, - ComponentEntry::Vacant(entry) => entry.insert(Default::default()), - } - } -} - -/// A view into an occupied entry in a [`EntityWorldMut`]. It is part of the [`OccupiedComponentEntry`] enum. -/// -/// The contained entity must have the component type parameter if we have this struct. -pub struct OccupiedComponentEntry<'w, 'a, T: Component> { - entity_world: &'a mut EntityWorldMut<'w>, - _marker: PhantomData, -} - -impl<'w, 'a, T: Component> OccupiedComponentEntry<'w, 'a, T> { - /// Gets a reference to the component in the entry. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::ComponentEntry}; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn(Comp(5)); - /// - /// if let ComponentEntry::Occupied(o) = entity.entry::() { - /// assert_eq!(o.get().0, 5); - /// } - /// ``` - #[inline] - pub fn get(&self) -> &T { - // This shouldn't panic because if we have an OccupiedComponentEntry the component must exist. - self.entity_world.get::().unwrap() - } - - /// Replaces the component of the entry. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::ComponentEntry}; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn(Comp(5)); - /// - /// if let ComponentEntry::Occupied(mut o) = entity.entry::() { - /// o.insert(Comp(10)); - /// } - /// - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 10); - /// ``` - #[inline] - pub fn insert(&mut self, component: T) { - self.entity_world.insert(component); - } - - /// Removes the component from the entry and returns it. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::ComponentEntry}; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn(Comp(5)); - /// - /// if let ComponentEntry::Occupied(o) = entity.entry::() { - /// assert_eq!(o.take(), Comp(5)); - /// } - /// - /// assert_eq!(world.query::<&Comp>().iter(&world).len(), 0); - /// ``` - #[inline] - pub fn take(self) -> T { - // This shouldn't panic because if we have an OccupiedComponentEntry the component must exist. - self.entity_world.take().unwrap() - } -} - -impl<'w, 'a, T: Component> OccupiedComponentEntry<'w, 'a, T> { - /// Gets a mutable reference to the component in the entry. - /// - /// If you need a reference to the [`OccupiedComponentEntry`] which may outlive the destruction of - /// the [`OccupiedComponentEntry`] value, see [`into_mut`]. - /// - /// [`into_mut`]: Self::into_mut - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::ComponentEntry}; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn(Comp(5)); - /// - /// if let ComponentEntry::Occupied(mut o) = entity.entry::() { - /// o.get_mut().0 += 10; - /// assert_eq!(o.get().0, 15); - /// - /// // We can use the same Entry multiple times. - /// o.get_mut().0 += 2 - /// } - /// - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 17); - /// ``` - #[inline] - pub fn get_mut(&mut self) -> Mut<'_, T> { - // This shouldn't panic because if we have an OccupiedComponentEntry the component must exist. - self.entity_world.get_mut::().unwrap() - } - - /// Converts the [`OccupiedComponentEntry`] into a mutable reference to the value in the entry with - /// a lifetime bound to the `EntityWorldMut`. - /// - /// If you need multiple references to the [`OccupiedComponentEntry`], see [`get_mut`]. - /// - /// [`get_mut`]: Self::get_mut - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::ComponentEntry}; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn(Comp(5)); - /// - /// if let ComponentEntry::Occupied(o) = entity.entry::() { - /// o.into_mut().0 += 10; - /// } - /// - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 15); - /// ``` - #[inline] - pub fn into_mut(self) -> Mut<'a, T> { - // This shouldn't panic because if we have an OccupiedComponentEntry the component must exist. - self.entity_world.get_mut().unwrap() - } -} - -/// A view into a vacant entry in a [`EntityWorldMut`]. It is part of the [`ComponentEntry`] enum. -pub struct VacantComponentEntry<'w, 'a, T: Component> { - entity_world: &'a mut EntityWorldMut<'w>, - _marker: PhantomData, -} - -impl<'w, 'a, T: Component> VacantComponentEntry<'w, 'a, T> { - /// Inserts the component into the [`VacantComponentEntry`] and returns an [`OccupiedComponentEntry`]. - /// - /// # Examples - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::ComponentEntry}; - /// #[derive(Component, Default, Clone, Copy, Debug, PartialEq)] - /// struct Comp(u32); - /// - /// # let mut world = World::new(); - /// let mut entity = world.spawn_empty(); - /// - /// if let ComponentEntry::Vacant(v) = entity.entry::() { - /// v.insert(Comp(10)); - /// } - /// - /// assert_eq!(world.query::<&Comp>().single(&world).unwrap().0, 10); - /// ``` - #[inline] - pub fn insert(self, component: T) -> OccupiedComponentEntry<'w, 'a, T> { - self.entity_world.insert(component); - OccupiedComponentEntry { - entity_world: self.entity_world, - _marker: PhantomData, - } - } -} - -/// Provides read-only access to a single entity and some of its components defined by the contained [`Access`]. -/// -/// To define the access when used as a [`QueryData`](crate::query::QueryData), -/// use a [`QueryBuilder`](crate::query::QueryBuilder) or [`QueryParamBuilder`](crate::system::QueryParamBuilder). -/// The [`FilteredEntityRef`] must be the entire [`QueryData`](crate::query::QueryData), and not nested inside a tuple with other data. -/// -/// ``` -/// # use bevy_ecs::{prelude::*, world::FilteredEntityRef}; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # let mut world = World::new(); -/// # world.spawn(A); -/// # -/// // This gives the `FilteredEntityRef` access to `&A`. -/// let mut query = QueryBuilder::::new(&mut world) -/// .data::<&A>() -/// .build(); -/// -/// let filtered_entity: FilteredEntityRef = query.single(&mut world).unwrap(); -/// let component: &A = filtered_entity.get().unwrap(); -/// ``` -#[derive(Clone, Copy)] -pub struct FilteredEntityRef<'w, 's> { - entity: UnsafeEntityCell<'w>, - access: &'s Access, -} - -impl<'w, 's> FilteredEntityRef<'w, 's> { - /// # Safety - /// - No `&mut World` can exist from the underlying `UnsafeWorldCell` - /// - If `access` takes read access to a component no mutable reference to that - /// component can exist at the same time as the returned [`FilteredEntityMut`] - /// - If `access` takes any access for a component `entity` must have that component. - #[inline] - pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { - Self { entity, access } - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.entity.id() - } - - /// Gets metadata indicating the location where the current entity is stored. - #[inline] - pub fn location(&self) -> EntityLocation { - self.entity.location() - } - - /// Returns the archetype that the current entity belongs to. - #[inline] - pub fn archetype(&self) -> &Archetype { - self.entity.archetype() - } - - /// Returns a reference to the underlying [`Access`]. - #[inline] - pub fn access(&self) -> &Access { - self.access - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.entity.contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.entity.contains_type_id(type_id) - } - - /// Gets access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get(&self) -> Option<&'w T> { - let id = self - .entity - .world() - .components() - .get_valid_id(TypeId::of::())?; - self.access - .has_component_read(id) - // SAFETY: We have read access - .then(|| unsafe { self.entity.get() }) - .flatten() - } - - /// Gets access to the component of type `T` for the current entity, - /// including change detection information as a [`Ref`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get_ref(&self) -> Option> { - let id = self - .entity - .world() - .components() - .get_valid_id(TypeId::of::())?; - self.access - .has_component_read(id) - // SAFETY: We have read access - .then(|| unsafe { self.entity.get_ref() }) - .flatten() - } - - /// Retrieves the change ticks for the given component. This can be useful for implementing change - /// detection in custom runtimes. - #[inline] - pub fn get_change_ticks(&self) -> Option { - let id = self - .entity - .world() - .components() - .get_valid_id(TypeId::of::())?; - self.access - .has_component_read(id) - // SAFETY: We have read access - .then(|| unsafe { self.entity.get_change_ticks::() }) - .flatten() - } - - /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// **You should prefer to use the typed API [`Self::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - #[inline] - pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - self.access - .has_component_read(component_id) - // SAFETY: We have read access - .then(|| unsafe { self.entity.get_change_ticks_by_id(component_id) }) - .flatten() - } - - /// Gets the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`FilteredEntityRef::get`], this returns a raw pointer to the component, - /// which is only valid while the [`FilteredEntityRef`] is alive. - #[inline] - pub fn get_by_id(&self, component_id: ComponentId) -> Option> { - self.access - .has_component_read(component_id) - // SAFETY: We have read access - .then(|| unsafe { self.entity.get_by_id(component_id) }) - .flatten() - } - - /// Returns the source code location from which this entity has been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.entity.spawned_by() - } - - /// Returns the [`Tick`] at which this entity has been spawned. - pub fn spawn_tick(&self) -> Tick { - self.entity.spawn_tick() - } -} - -impl<'w, 's> From> for FilteredEntityRef<'w, 's> { - #[inline] - fn from(entity: FilteredEntityMut<'w, 's>) -> Self { - // SAFETY: - // - `FilteredEntityMut` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity.entity, entity.access) } - } -} - -impl<'w, 's> From<&'w FilteredEntityMut<'_, 's>> for FilteredEntityRef<'w, 's> { - #[inline] - fn from(entity: &'w FilteredEntityMut<'_, 's>) -> Self { - // SAFETY: - // - `FilteredEntityMut` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity.entity, entity.access) } - } -} - -impl<'a> From> for FilteredEntityRef<'a, 'static> { - fn from(entity: EntityRef<'a>) -> Self { - // SAFETY: - // - `EntityRef` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity.cell, const { &Access::new_read_all() }) } - } -} - -impl<'a> From<&'a EntityRef<'_>> for FilteredEntityRef<'a, 'static> { - fn from(entity: &'a EntityRef<'_>) -> Self { - // SAFETY: - // - `EntityRef` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity.cell, const { &Access::new_read_all() }) } - } -} - -impl<'a> From> for FilteredEntityRef<'a, 'static> { - fn from(entity: EntityMut<'a>) -> Self { - // SAFETY: - // - `EntityMut` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity.cell, const { &Access::new_read_all() }) } - } -} - -impl<'a> From<&'a EntityMut<'_>> for FilteredEntityRef<'a, 'static> { - fn from(entity: &'a EntityMut<'_>) -> Self { - // SAFETY: - // - `EntityMut` guarantees exclusive access to all components in the new `FilteredEntityRef`. - unsafe { FilteredEntityRef::new(entity.cell, const { &Access::new_read_all() }) } - } -} - -impl<'a> From> for FilteredEntityRef<'a, 'static> { - fn from(entity: EntityWorldMut<'a>) -> Self { - // SAFETY: - // - `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { - FilteredEntityRef::new( - entity.into_unsafe_entity_cell(), - const { &Access::new_read_all() }, - ) - } - } -} - -impl<'a> From<&'a EntityWorldMut<'_>> for FilteredEntityRef<'a, 'static> { - fn from(entity: &'a EntityWorldMut<'_>) -> Self { - // SAFETY: - // - `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { - FilteredEntityRef::new( - entity.as_unsafe_entity_cell_readonly(), - const { &Access::new_read_all() }, - ) - } - } -} - -impl<'w, 's, B: Bundle> From<&'w EntityRefExcept<'_, 's, B>> for FilteredEntityRef<'w, 's> { - fn from(value: &'w EntityRefExcept<'_, 's, B>) -> Self { - // SAFETY: - // - The FilteredEntityRef has the same component access as the given EntityRefExcept. - unsafe { FilteredEntityRef::new(value.entity, value.access) } - } -} - -impl PartialEq for FilteredEntityRef<'_, '_> { - fn eq(&self, other: &Self) -> bool { - self.entity() == other.entity() - } -} - -impl Eq for FilteredEntityRef<'_, '_> {} - -impl PartialOrd for FilteredEntityRef<'_, '_> { - /// [`FilteredEntityRef`]'s comparison trait implementations match the underlying [`Entity`], - /// and cannot discern between different worlds. - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for FilteredEntityRef<'_, '_> { - fn cmp(&self, other: &Self) -> Ordering { - self.entity().cmp(&other.entity()) - } -} - -impl Hash for FilteredEntityRef<'_, '_> { - fn hash(&self, state: &mut H) { - self.entity().hash(state); - } -} - -impl ContainsEntity for FilteredEntityRef<'_, '_> { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for FilteredEntityRef<'_, '_> {} - -/// Variant of [`FilteredEntityMut`] that can be used to create copies of a [`FilteredEntityMut`], as long -/// as the user ensures that these won't cause aliasing violations. -/// -/// This can be useful to mutably query multiple components from a single `FilteredEntityMut`. -/// -/// ### Example Usage -/// -/// ``` -/// # use bevy_ecs::{prelude::*, world::{FilteredEntityMut, UnsafeFilteredEntityMut}}; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # #[derive(Component)] -/// # struct B; -/// # -/// # let mut world = World::new(); -/// # world.spawn((A, B)); -/// # -/// // This gives the `FilteredEntityMut` access to `&mut A` and `&mut B`. -/// let mut query = QueryBuilder::::new(&mut world) -/// .data::<(&mut A, &mut B)>() -/// .build(); -/// -/// let mut filtered_entity: FilteredEntityMut = query.single_mut(&mut world).unwrap(); -/// let unsafe_filtered_entity = UnsafeFilteredEntityMut::new_readonly(&filtered_entity); -/// // SAFETY: the original FilteredEntityMut accesses `&mut A` and the clone accesses `&mut B`, so no aliasing violations occur. -/// let mut filtered_entity_clone: FilteredEntityMut = unsafe { unsafe_filtered_entity.into_mut() }; -/// let a: Mut = filtered_entity.get_mut().unwrap(); -/// let b: Mut = filtered_entity_clone.get_mut().unwrap(); -/// ``` -#[derive(Copy, Clone)] -pub struct UnsafeFilteredEntityMut<'w, 's> { - entity: UnsafeEntityCell<'w>, - access: &'s Access, -} - -impl<'w, 's> UnsafeFilteredEntityMut<'w, 's> { - /// Creates a [`UnsafeFilteredEntityMut`] that can be used to have multiple concurrent [`FilteredEntityMut`]s. - #[inline] - pub fn new_readonly(filtered_entity_mut: &FilteredEntityMut<'w, 's>) -> Self { - Self { - entity: filtered_entity_mut.entity, - access: filtered_entity_mut.access, - } - } - - /// Returns a new instance of [`FilteredEntityMut`]. - /// - /// # Safety - /// - The user must ensure that no aliasing violations occur when using the returned `FilteredEntityMut`. - #[inline] - pub unsafe fn into_mut(self) -> FilteredEntityMut<'w, 's> { - FilteredEntityMut::new(self.entity, self.access) - } -} - -/// Provides mutable access to a single entity and some of its components defined by the contained [`Access`]. -/// -/// To define the access when used as a [`QueryData`](crate::query::QueryData), -/// use a [`QueryBuilder`](crate::query::QueryBuilder) or [`QueryParamBuilder`](crate::system::QueryParamBuilder). -/// The `FilteredEntityMut` must be the entire `QueryData`, and not nested inside a tuple with other data. -/// -/// ``` -/// # use bevy_ecs::{prelude::*, world::FilteredEntityMut}; -/// # -/// # #[derive(Component)] -/// # struct A; -/// # -/// # let mut world = World::new(); -/// # world.spawn(A); -/// # -/// // This gives the `FilteredEntityMut` access to `&mut A`. -/// let mut query = QueryBuilder::::new(&mut world) -/// .data::<&mut A>() -/// .build(); -/// -/// let mut filtered_entity: FilteredEntityMut = query.single_mut(&mut world).unwrap(); -/// let component: Mut = filtered_entity.get_mut().unwrap(); -/// ``` -/// -/// Also see [`UnsafeFilteredEntityMut`] for a way to bypass borrow-checker restrictions. -pub struct FilteredEntityMut<'w, 's> { - entity: UnsafeEntityCell<'w>, - access: &'s Access, -} - -impl<'w, 's> FilteredEntityMut<'w, 's> { - /// # Safety - /// - No `&mut World` can exist from the underlying `UnsafeWorldCell` - /// - If `access` takes read access to a component no mutable reference to that - /// component can exist at the same time as the returned [`FilteredEntityMut`] - /// - If `access` takes write access to a component, no reference to that component - /// may exist at the same time as the returned [`FilteredEntityMut`] - /// - If `access` takes any access for a component `entity` must have that component. - #[inline] - pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { - Self { entity, access } - } - - /// Returns a new instance with a shorter lifetime. - /// This is useful if you have `&mut FilteredEntityMut`, but you need `FilteredEntityMut`. - pub fn reborrow(&mut self) -> FilteredEntityMut<'_, 's> { - // SAFETY: We have exclusive access to the entire entity and its components. - unsafe { Self::new(self.entity, self.access) } - } - - /// Gets read-only access to all of the entity's components. - #[inline] - pub fn as_readonly(&self) -> FilteredEntityRef<'_, 's> { - FilteredEntityRef::from(self) - } - - /// Get access to the underlying [`UnsafeEntityCell`] - pub fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> { - self.entity - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.entity.id() - } - - /// Gets metadata indicating the location where the current entity is stored. - #[inline] - pub fn location(&self) -> EntityLocation { - self.entity.location() - } - - /// Returns the archetype that the current entity belongs to. - #[inline] - pub fn archetype(&self) -> &Archetype { - self.entity.archetype() - } - - /// Returns a reference to the underlying [`Access`]. - #[inline] - pub fn access(&self) -> &Access { - self.access - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.entity.contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.entity.contains_type_id(type_id) - } - - /// Gets access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get(&self) -> Option<&'_ T> { - self.as_readonly().get() - } - - /// Gets access to the component of type `T` for the current entity, - /// including change detection information as a [`Ref`]. - /// - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn get_ref(&self) -> Option> { - self.as_readonly().get_ref() - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T` or if - /// the access does not include write access to `T`. - #[inline] - pub fn get_mut>(&mut self) -> Option> { - // SAFETY: we use a mutable reference to self, so we cannot use the `FilteredEntityMut` to access - // another component - unsafe { self.get_mut_unchecked() } - } - - /// Gets mutable access to the component of type `T` for the current entity. - /// Returns `None` if the entity does not have a component of type `T` or if - /// the access does not include write access to `T`. - /// - /// This only requires `&self`, and so may be used to get mutable access to multiple components. - /// - /// # Example - /// - /// ``` - /// # use bevy_ecs::{prelude::*, world::FilteredEntityMut}; - /// # - /// #[derive(Component)] - /// struct X(usize); - /// #[derive(Component)] - /// struct Y(usize); - /// - /// # let mut world = World::default(); - /// let mut entity = world.spawn((X(0), Y(0))).into_mutable(); - /// - /// // This gives the `FilteredEntityMut` access to `&mut X` and `&mut Y`. - /// let mut query = QueryBuilder::::new(&mut world) - /// .data::<(&mut X, &mut Y)>() - /// .build(); - /// - /// let mut filtered_entity: FilteredEntityMut = query.single_mut(&mut world).unwrap(); - /// - /// // Get mutable access to two components at once - /// // SAFETY: We don't take any other references to `X` from this entity - /// let mut x = unsafe { filtered_entity.get_mut_unchecked::() }.unwrap(); - /// // SAFETY: We don't take any other references to `Y` from this entity - /// let mut y = unsafe { filtered_entity.get_mut_unchecked::() }.unwrap(); - /// *x = X(1); - /// *y = Y(1); - /// ``` - /// - /// # Safety - /// - /// No other references to the same component may exist at the same time as the returned reference. - /// - /// # See also - /// - /// - [`get_mut`](Self::get_mut) for the safe version. - #[inline] - pub unsafe fn get_mut_unchecked>( - &self, - ) -> Option> { - let id = self - .entity - .world() - .components() - .get_valid_id(TypeId::of::())?; - self.access - .has_component_write(id) - // SAFETY: We have permission to access the component mutable - // and we promise to not create other references to the same component - .then(|| unsafe { self.entity.get_mut() }) - .flatten() - } - - /// Consumes self and gets mutable access to the component of type `T` - /// with the world `'w` lifetime for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - #[inline] - pub fn into_mut>(self) -> Option> { - // SAFETY: - // - We have write access - // - The bound `T: Component` ensures the component is mutable - unsafe { self.into_mut_assume_mutable() } - } - - /// Consumes self and gets mutable access to the component of type `T` - /// with the world `'w` lifetime for the current entity. - /// Returns `None` if the entity does not have a component of type `T`. - /// - /// # Safety - /// - /// - `T` must be a mutable component - #[inline] - pub unsafe fn into_mut_assume_mutable(self) -> Option> { - let id = self - .entity - .world() - .components() - .get_valid_id(TypeId::of::())?; - self.access - .has_component_write(id) - // SAFETY: - // - We have write access - // - Caller ensures `T` is a mutable component - .then(|| unsafe { self.entity.get_mut_assume_mutable() }) - .flatten() - } - - /// Retrieves the change ticks for the given component. This can be useful for implementing change - /// detection in custom runtimes. - #[inline] - pub fn get_change_ticks(&self) -> Option { - self.as_readonly().get_change_ticks::() - } - - /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// **You should prefer to use the typed API [`Self::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - #[inline] - pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - self.as_readonly().get_change_ticks_by_id(component_id) - } - - /// Gets the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`FilteredEntityMut::get`], this returns a raw pointer to the component, - /// which is only valid while the [`FilteredEntityMut`] is alive. - #[inline] - pub fn get_by_id(&self, component_id: ComponentId) -> Option> { - self.as_readonly().get_by_id(component_id) - } - - /// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get_mut`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`FilteredEntityMut::get_mut`], this returns a raw pointer to the component, - /// which is only valid while the [`FilteredEntityMut`] is alive. - #[inline] - pub fn get_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: we use a mutable reference to self, so we cannot use the `FilteredEntityMut` to access - // another component - unsafe { self.get_mut_by_id_unchecked(component_id) } - } - - /// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get_mut`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`FilteredEntityMut::get_mut`], this returns a raw pointer to the component, - /// which is only valid while the [`FilteredEntityMut`] is alive. - /// - /// This only requires `&self`, and so may be used to get mutable access to multiple components. - /// - /// # Safety - /// - /// No other references to the same component may exist at the same time as the returned reference. - /// - /// # See also - /// - /// - [`get_mut_by_id`](Self::get_mut_by_id) for the safe version. - #[inline] - pub unsafe fn get_mut_by_id_unchecked( - &self, - component_id: ComponentId, - ) -> Option> { - self.access - .has_component_write(component_id) - // SAFETY: We have permission to access the component mutable - // and we promise to not create other references to the same component - .then(|| unsafe { self.entity.get_mut_by_id(component_id).ok() }) - .flatten() - } - - /// Returns the source code location from which this entity has last been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.entity.spawned_by() - } - - /// Returns the [`Tick`] at which this entity has been spawned. - pub fn spawn_tick(&self) -> Tick { - self.entity.spawn_tick() - } -} - -impl<'a> From> for FilteredEntityMut<'a, 'static> { - fn from(entity: EntityMut<'a>) -> Self { - // SAFETY: - // - `EntityMut` guarantees exclusive access to all components in the new `FilteredEntityMut`. - unsafe { FilteredEntityMut::new(entity.cell, const { &Access::new_write_all() }) } - } -} - -impl<'a> From<&'a mut EntityMut<'_>> for FilteredEntityMut<'a, 'static> { - fn from(entity: &'a mut EntityMut<'_>) -> Self { - // SAFETY: - // - `EntityMut` guarantees exclusive access to all components in the new `FilteredEntityMut`. - unsafe { FilteredEntityMut::new(entity.cell, const { &Access::new_write_all() }) } - } -} - -impl<'a> From> for FilteredEntityMut<'a, 'static> { - fn from(entity: EntityWorldMut<'a>) -> Self { - // SAFETY: - // - `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { - FilteredEntityMut::new( - entity.into_unsafe_entity_cell(), - const { &Access::new_write_all() }, - ) - } - } -} - -impl<'a> From<&'a mut EntityWorldMut<'_>> for FilteredEntityMut<'a, 'static> { - fn from(entity: &'a mut EntityWorldMut<'_>) -> Self { - // SAFETY: - // - `EntityWorldMut` guarantees exclusive access to the entire world. - unsafe { - FilteredEntityMut::new( - entity.as_unsafe_entity_cell(), - const { &Access::new_write_all() }, - ) - } - } -} - -impl<'w, 's, B: Bundle> From<&'w EntityMutExcept<'_, 's, B>> for FilteredEntityMut<'w, 's> { - fn from(value: &'w EntityMutExcept<'_, 's, B>) -> Self { - // SAFETY: - // - The FilteredEntityMut has the same component access as the given EntityMutExcept. - unsafe { FilteredEntityMut::new(value.entity, value.access) } - } -} - -impl PartialEq for FilteredEntityMut<'_, '_> { - fn eq(&self, other: &Self) -> bool { - self.entity() == other.entity() - } -} - -impl Eq for FilteredEntityMut<'_, '_> {} - -impl PartialOrd for FilteredEntityMut<'_, '_> { - /// [`FilteredEntityMut`]'s comparison trait implementations match the underlying [`Entity`], - /// and cannot discern between different worlds. - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for FilteredEntityMut<'_, '_> { - fn cmp(&self, other: &Self) -> Ordering { - self.entity().cmp(&other.entity()) - } -} - -impl Hash for FilteredEntityMut<'_, '_> { - fn hash(&self, state: &mut H) { - self.entity().hash(state); - } -} - -impl ContainsEntity for FilteredEntityMut<'_, '_> { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for FilteredEntityMut<'_, '_> {} - -/// Error type returned by [`TryFrom`] conversions from filtered entity types -/// ([`FilteredEntityRef`]/[`FilteredEntityMut`]) to full-access entity types -/// ([`EntityRef`]/[`EntityMut`]). -#[derive(Error, Debug)] -pub enum TryFromFilteredError { - /// Error indicating that the filtered entity does not have read access to - /// all components. - #[error("Conversion failed, filtered entity ref does not have read access to all components")] - MissingReadAllAccess, - /// Error indicating that the filtered entity does not have write access to - /// all components. - #[error("Conversion failed, filtered entity ref does not have write access to all components")] - MissingWriteAllAccess, -} - -/// Provides read-only access to a single entity and all its components, save -/// for an explicitly-enumerated set. -pub struct EntityRefExcept<'w, 's, B> -where - B: Bundle, -{ - entity: UnsafeEntityCell<'w>, - access: &'s Access, - phantom: PhantomData, -} - -impl<'w, 's, B> EntityRefExcept<'w, 's, B> -where - B: Bundle, -{ - /// # Safety - /// Other users of `UnsafeEntityCell` must only have mutable access to the components in `B`. - pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { - Self { - entity, - access, - phantom: PhantomData, - } - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.entity.id() - } - - /// Gets access to the component of type `C` for the current entity. Returns - /// `None` if the component doesn't have a component of that type or if the - /// type is one of the excluded components. - #[inline] - pub fn get(&self) -> Option<&'w C> - where - C: Component, - { - let components = self.entity.world().components(); - let id = components.valid_component_id::()?; - if bundle_contains_component::(components, id) { - None - } else { - // SAFETY: We have read access for all components that weren't - // covered by the `contains` check above. - unsafe { self.entity.get() } - } - } - - /// Gets access to the component of type `C` for the current entity, - /// including change detection information. Returns `None` if the component - /// doesn't have a component of that type or if the type is one of the - /// excluded components. - #[inline] - pub fn get_ref(&self) -> Option> - where - C: Component, - { - let components = self.entity.world().components(); - let id = components.valid_component_id::()?; - if bundle_contains_component::(components, id) { - None - } else { - // SAFETY: We have read access for all components that weren't - // covered by the `contains` check above. - unsafe { self.entity.get_ref() } - } - } - - /// Returns the source code location from which this entity has been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.entity.spawned_by() - } - - /// Returns the [`Tick`] at which this entity has been spawned. - pub fn spawn_tick(&self) -> Tick { - self.entity.spawn_tick() - } - - /// Gets the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`EntityRefExcept::get`], this returns a raw pointer to the component, - /// which is only valid while the [`EntityRefExcept`] is alive. - #[inline] - pub fn get_by_id(&self, component_id: ComponentId) -> Option> { - let components = self.entity.world().components(); - (!bundle_contains_component::(components, component_id)) - .then(|| { - // SAFETY: We have read access for this component - unsafe { self.entity.get_by_id(component_id) } - }) - .flatten() - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.entity.contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.entity.contains_type_id(type_id) - } - - /// Retrieves the change ticks for the given component. This can be useful for implementing change - /// detection in custom runtimes. - #[inline] - pub fn get_change_ticks(&self) -> Option { - let component_id = self - .entity - .world() - .components() - .get_valid_id(TypeId::of::())?; - let components = self.entity.world().components(); - (!bundle_contains_component::(components, component_id)) - .then(|| { - // SAFETY: We have read access - unsafe { self.entity.get_change_ticks::() } - }) - .flatten() - } - - /// Retrieves the change ticks for the given [`ComponentId`]. This can be useful for implementing change - /// detection in custom runtimes. - /// - /// **You should prefer to use the typed API [`Self::get_change_ticks`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - #[inline] - pub fn get_change_ticks_by_id(&self, component_id: ComponentId) -> Option { - let components = self.entity.world().components(); - (!bundle_contains_component::(components, component_id)) - .then(|| { - // SAFETY: We have read access - unsafe { self.entity.get_change_ticks_by_id(component_id) } - }) - .flatten() - } -} - -impl<'w, 's, B> From<&'w EntityMutExcept<'_, 's, B>> for EntityRefExcept<'w, 's, B> -where - B: Bundle, -{ - fn from(entity: &'w EntityMutExcept<'_, 's, B>) -> Self { - // SAFETY: All accesses that `EntityRefExcept` provides are also - // accesses that `EntityMutExcept` provides. - unsafe { EntityRefExcept::new(entity.entity, entity.access) } - } -} - -impl Clone for EntityRefExcept<'_, '_, B> { - fn clone(&self) -> Self { - *self - } -} - -impl Copy for EntityRefExcept<'_, '_, B> {} - -impl PartialEq for EntityRefExcept<'_, '_, B> { - fn eq(&self, other: &Self) -> bool { - self.entity() == other.entity() - } -} - -impl Eq for EntityRefExcept<'_, '_, B> {} - -impl PartialOrd for EntityRefExcept<'_, '_, B> { - /// [`EntityRefExcept`]'s comparison trait implementations match the underlying [`Entity`], - /// and cannot discern between different worlds. - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EntityRefExcept<'_, '_, B> { - fn cmp(&self, other: &Self) -> Ordering { - self.entity().cmp(&other.entity()) - } -} - -impl Hash for EntityRefExcept<'_, '_, B> { - fn hash(&self, state: &mut H) { - self.entity().hash(state); - } -} - -impl ContainsEntity for EntityRefExcept<'_, '_, B> { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for EntityRefExcept<'_, '_, B> {} - -/// Provides mutable access to all components of an entity, with the exception -/// of an explicit set. -/// -/// This is a rather niche type that should only be used if you need access to -/// *all* components of an entity, while still allowing you to consult other -/// queries that might match entities that this query also matches. If you don't -/// need access to all components, prefer a standard query with a -/// [`Without`](`crate::query::Without`) filter. -pub struct EntityMutExcept<'w, 's, B> -where - B: Bundle, -{ - entity: UnsafeEntityCell<'w>, - access: &'s Access, - phantom: PhantomData, -} - -impl<'w, 's, B> EntityMutExcept<'w, 's, B> -where - B: Bundle, -{ - /// # Safety - /// Other users of `UnsafeEntityCell` must not have access to any components not in `B`. - pub(crate) unsafe fn new(entity: UnsafeEntityCell<'w>, access: &'s Access) -> Self { - Self { - entity, - access, - phantom: PhantomData, - } - } - - /// Returns the [ID](Entity) of the current entity. - #[inline] - #[must_use = "Omit the .id() call if you do not need to store the `Entity` identifier."] - pub fn id(&self) -> Entity { - self.entity.id() - } - - /// Returns a new instance with a shorter lifetime. - /// - /// This is useful if you have `&mut EntityMutExcept`, but you need - /// `EntityMutExcept`. - pub fn reborrow(&mut self) -> EntityMutExcept<'_, 's, B> { - // SAFETY: We have exclusive access to the entire entity and the - // applicable components. - unsafe { Self::new(self.entity, self.access) } - } - - /// Gets read-only access to all of the entity's components, except for the - /// ones in `CL`. - #[inline] - pub fn as_readonly(&self) -> EntityRefExcept<'_, 's, B> { - EntityRefExcept::from(self) - } - - /// Get access to the underlying [`UnsafeEntityCell`] - pub fn as_unsafe_entity_cell(&mut self) -> UnsafeEntityCell<'_> { - self.entity - } - - /// Gets access to the component of type `C` for the current entity. Returns - /// `None` if the component doesn't have a component of that type or if the - /// type is one of the excluded components. - #[inline] - pub fn get(&self) -> Option<&'_ C> - where - C: Component, - { - self.as_readonly().get() - } - - /// Gets access to the component of type `C` for the current entity, - /// including change detection information. Returns `None` if the component - /// doesn't have a component of that type or if the type is one of the - /// excluded components. - #[inline] - pub fn get_ref(&self) -> Option> - where - C: Component, - { - self.as_readonly().get_ref() - } - - /// Gets mutable access to the component of type `C` for the current entity. - /// Returns `None` if the component doesn't have a component of that type or - /// if the type is one of the excluded components. - #[inline] - pub fn get_mut(&mut self) -> Option> - where - C: Component, - { - let components = self.entity.world().components(); - let id = components.valid_component_id::()?; - if bundle_contains_component::(components, id) { - None - } else { - // SAFETY: We have write access for all components that weren't - // covered by the `contains` check above. - unsafe { self.entity.get_mut() } - } - } - - /// Returns the source code location from which this entity has been spawned. - pub fn spawned_by(&self) -> MaybeLocation { - self.entity.spawned_by() - } - - /// Returns the [`Tick`] at which this entity has been spawned. - pub fn spawn_tick(&self) -> Tick { - self.entity.spawn_tick() - } - - /// Returns `true` if the current entity has a component of type `T`. - /// Otherwise, this returns `false`. - /// - /// ## Notes - /// - /// If you do not know the concrete type of a component, consider using - /// [`Self::contains_id`] or [`Self::contains_type_id`]. - #[inline] - pub fn contains(&self) -> bool { - self.contains_type_id(TypeId::of::()) - } - - /// Returns `true` if the current entity has a component identified by `component_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you know the component's [`TypeId`] but not its [`ComponentId`], consider using - /// [`Self::contains_type_id`]. - #[inline] - pub fn contains_id(&self, component_id: ComponentId) -> bool { - self.entity.contains_id(component_id) - } - - /// Returns `true` if the current entity has a component with the type identified by `type_id`. - /// Otherwise, this returns false. - /// - /// ## Notes - /// - /// - If you know the concrete type of the component, you should prefer [`Self::contains`]. - /// - If you have a [`ComponentId`] instead of a [`TypeId`], consider using [`Self::contains_id`]. - #[inline] - pub fn contains_type_id(&self, type_id: TypeId) -> bool { - self.entity.contains_type_id(type_id) - } - - /// Gets the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`EntityMutExcept::get`], this returns a raw pointer to the component, - /// which is only valid while the [`EntityMutExcept`] is alive. - #[inline] - pub fn get_by_id(&'w self, component_id: ComponentId) -> Option> { - self.as_readonly().get_by_id(component_id) - } - - /// Gets a [`MutUntyped`] of the component of the given [`ComponentId`] from the entity. - /// - /// **You should prefer to use the typed API [`Self::get_mut`] where possible and only - /// use this in cases where the actual component types are not known at - /// compile time.** - /// - /// Unlike [`EntityMutExcept::get_mut`], this returns a raw pointer to the component, - /// which is only valid while the [`EntityMutExcept`] is alive. - #[inline] - pub fn get_mut_by_id( - &mut self, - component_id: ComponentId, - ) -> Option> { - let components = self.entity.world().components(); - (!bundle_contains_component::(components, component_id)) - .then(|| { - // SAFETY: We have write access - unsafe { self.entity.get_mut_by_id(component_id).ok() } - }) - .flatten() - } -} - -impl PartialEq for EntityMutExcept<'_, '_, B> { - fn eq(&self, other: &Self) -> bool { - self.entity() == other.entity() - } -} - -impl Eq for EntityMutExcept<'_, '_, B> {} - -impl PartialOrd for EntityMutExcept<'_, '_, B> { - /// [`EntityMutExcept`]'s comparison trait implementations match the underlying [`Entity`], - /// and cannot discern between different worlds. - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for EntityMutExcept<'_, '_, B> { - fn cmp(&self, other: &Self) -> Ordering { - self.entity().cmp(&other.entity()) - } -} - -impl Hash for EntityMutExcept<'_, '_, B> { - fn hash(&self, state: &mut H) { - self.entity().hash(state); - } -} - -impl ContainsEntity for EntityMutExcept<'_, '_, B> { - fn entity(&self) -> Entity { - self.id() - } -} - -// SAFETY: This type represents one Entity. We implement the comparison traits based on that Entity. -unsafe impl EntityEquivalent for EntityMutExcept<'_, '_, B> {} - -fn bundle_contains_component(components: &Components, query_id: ComponentId) -> bool -where - B: Bundle, -{ - let mut found = false; - B::get_component_ids(components, &mut |maybe_id| { - if let Some(id) = maybe_id { - found = found || id == query_id; - } - }); - found -} - -/// Inserts a dynamic [`Bundle`] into the entity. -/// -/// # Safety -/// -/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the -/// [`BundleInfo`](crate::bundle::BundleInfo) used to construct [`BundleInserter`] -/// - [`Entity`] must correspond to [`EntityLocation`] -unsafe fn insert_dynamic_bundle< - 'a, - I: Iterator>, - S: Iterator, ->( - mut bundle_inserter: BundleInserter<'_>, - entity: Entity, - location: EntityLocation, - components: I, - storage_types: S, - mode: InsertMode, - caller: MaybeLocation, - relationship_hook_insert_mode: RelationshipHookMode, -) -> EntityLocation { - struct DynamicInsertBundle<'a, I: Iterator)>> { - components: I, - } - - impl<'a, I: Iterator)>> DynamicBundle - for DynamicInsertBundle<'a, I> - { - type Effect = (); - unsafe fn get_components( - mut ptr: MovingPtr<'_, Self>, - func: &mut impl FnMut(StorageType, OwningPtr<'_>), - ) { - (&mut ptr.components).for_each(|(t, ptr)| func(t, ptr)); - } - - unsafe fn apply_effect( - _ptr: MovingPtr<'_, MaybeUninit>, - _entity: &mut EntityWorldMut, - ) { - } - } - - let bundle = DynamicInsertBundle { - components: storage_types.zip(components), - }; - - move_as_ptr!(bundle); - - // SAFETY: - // - `location` matches `entity`. and thus must currently exist in the source - // archetype for this inserter and its location within the archetype. - // - The caller must ensure that the iterators and storage types match up with the `BundleInserter` - // - `apply_effect` is never called on this bundle. - // - `bundle` is not used or dropped after this point. - unsafe { - bundle_inserter.insert( - entity, - location, - bundle, - mode, - caller, - relationship_hook_insert_mode, - ) - } -} - -/// Types that can be used to fetch components from an entity dynamically by -/// [`ComponentId`]s. -/// -/// Provided implementations are: -/// - [`ComponentId`]: Returns a single untyped reference. -/// - `[ComponentId; N]` and `&[ComponentId; N]`: Returns a same-sized array of untyped references. -/// - `&[ComponentId]`: Returns a [`Vec`] of untyped references. -/// - [`&HashSet`](HashSet): Returns a [`HashMap`] of IDs to untyped references. -/// -/// # Performance -/// -/// - The slice and array implementations perform an aliased mutability check in -/// [`DynamicComponentFetch::fetch_mut`] that is `O(N^2)`. -/// - The [`HashSet`] implementation performs no such check as the type itself -/// guarantees unique IDs. -/// - The single [`ComponentId`] implementation performs no such check as only -/// one reference is returned. -/// -/// # Safety -/// -/// Implementor must ensure that: -/// - No aliased mutability is caused by the returned references. -/// - [`DynamicComponentFetch::fetch_ref`] returns only read-only references. -pub unsafe trait DynamicComponentFetch { - /// The read-only reference type returned by [`DynamicComponentFetch::fetch_ref`]. - type Ref<'w>; - - /// The mutable reference type returned by [`DynamicComponentFetch::fetch_mut`]. - type Mut<'w>; - - /// Returns untyped read-only reference(s) to the component(s) with the - /// given [`ComponentId`]s, as determined by `self`. - /// - /// # Safety - /// - /// It is the caller's responsibility to ensure that: - /// - The given [`UnsafeEntityCell`] has read-only access to the fetched components. - /// - No other mutable references to the fetched components exist at the same time. - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if a component is missing from the entity. - unsafe fn fetch_ref( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError>; - - /// Returns untyped mutable reference(s) to the component(s) with the - /// given [`ComponentId`]s, as determined by `self`. - /// - /// # Safety - /// - /// It is the caller's responsibility to ensure that: - /// - The given [`UnsafeEntityCell`] has mutable access to the fetched components. - /// - No other references to the fetched components exist at the same time. - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if a component is missing from the entity. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component is requested multiple times. - unsafe fn fetch_mut( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError>; - - /// Returns untyped mutable reference(s) to the component(s) with the - /// given [`ComponentId`]s, as determined by `self`. - /// Assumes all [`ComponentId`]s refer to mutable components. - /// - /// # Safety - /// - /// It is the caller's responsibility to ensure that: - /// - The given [`UnsafeEntityCell`] has mutable access to the fetched components. - /// - No other references to the fetched components exist at the same time. - /// - The requested components are all mutable. - /// - /// # Errors - /// - /// - Returns [`EntityComponentError::MissingComponent`] if a component is missing from the entity. - /// - Returns [`EntityComponentError::AliasedMutability`] if a component is requested multiple times. - unsafe fn fetch_mut_assume_mutable( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError>; -} - -// SAFETY: -// - No aliased mutability is caused because a single reference is returned. -// - No mutable references are returned by `fetch_ref`. -unsafe impl DynamicComponentFetch for ComponentId { - type Ref<'w> = Ptr<'w>; - type Mut<'w> = MutUntyped<'w>; - - unsafe fn fetch_ref( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // SAFETY: caller ensures that the cell has read access to the component. - unsafe { cell.get_by_id(self) }.ok_or(EntityComponentError::MissingComponent(self)) - } - - unsafe fn fetch_mut( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_by_id(self) } - .map_err(|_| EntityComponentError::MissingComponent(self)) - } - - unsafe fn fetch_mut_assume_mutable( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_assume_mutable_by_id(self) } - .map_err(|_| EntityComponentError::MissingComponent(self)) - } -} - -// SAFETY: -// - No aliased mutability is caused because the array is checked for duplicates. -// - No mutable references are returned by `fetch_ref`. -unsafe impl DynamicComponentFetch for [ComponentId; N] { - type Ref<'w> = [Ptr<'w>; N]; - type Mut<'w> = [MutUntyped<'w>; N]; - - unsafe fn fetch_ref( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - <&Self>::fetch_ref(&self, cell) - } - - unsafe fn fetch_mut( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - <&Self>::fetch_mut(&self, cell) - } - - unsafe fn fetch_mut_assume_mutable( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - <&Self>::fetch_mut_assume_mutable(&self, cell) - } -} - -// SAFETY: -// - No aliased mutability is caused because the array is checked for duplicates. -// - No mutable references are returned by `fetch_ref`. -unsafe impl DynamicComponentFetch for &'_ [ComponentId; N] { - type Ref<'w> = [Ptr<'w>; N]; - type Mut<'w> = [MutUntyped<'w>; N]; - - unsafe fn fetch_ref( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - let mut ptrs = [const { MaybeUninit::uninit() }; N]; - for (ptr, &id) in core::iter::zip(&mut ptrs, self) { - *ptr = MaybeUninit::new( - // SAFETY: caller ensures that the cell has read access to the component. - unsafe { cell.get_by_id(id) }.ok_or(EntityComponentError::MissingComponent(id))?, - ); - } - - // SAFETY: Each ptr was initialized in the loop above. - let ptrs = ptrs.map(|ptr| unsafe { MaybeUninit::assume_init(ptr) }); - - Ok(ptrs) - } - - unsafe fn fetch_mut( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // Check for duplicate component IDs. - for i in 0..self.len() { - for j in 0..i { - if self[i] == self[j] { - return Err(EntityComponentError::AliasedMutability(self[i])); - } - } - } - - let mut ptrs = [const { MaybeUninit::uninit() }; N]; - for (ptr, &id) in core::iter::zip(&mut ptrs, self) { - *ptr = MaybeUninit::new( - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_by_id(id) } - .map_err(|_| EntityComponentError::MissingComponent(id))?, - ); - } - - // SAFETY: Each ptr was initialized in the loop above. - let ptrs = ptrs.map(|ptr| unsafe { MaybeUninit::assume_init(ptr) }); - - Ok(ptrs) - } - - unsafe fn fetch_mut_assume_mutable( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // Check for duplicate component IDs. - for i in 0..self.len() { - for j in 0..i { - if self[i] == self[j] { - return Err(EntityComponentError::AliasedMutability(self[i])); - } - } - } - - let mut ptrs = [const { MaybeUninit::uninit() }; N]; - for (ptr, &id) in core::iter::zip(&mut ptrs, self) { - *ptr = MaybeUninit::new( - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_assume_mutable_by_id(id) } - .map_err(|_| EntityComponentError::MissingComponent(id))?, - ); - } - - // SAFETY: Each ptr was initialized in the loop above. - let ptrs = ptrs.map(|ptr| unsafe { MaybeUninit::assume_init(ptr) }); - - Ok(ptrs) - } -} - -// SAFETY: -// - No aliased mutability is caused because the slice is checked for duplicates. -// - No mutable references are returned by `fetch_ref`. -unsafe impl DynamicComponentFetch for &'_ [ComponentId] { - type Ref<'w> = Vec>; - type Mut<'w> = Vec>; - - unsafe fn fetch_ref( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - let mut ptrs = Vec::with_capacity(self.len()); - for &id in self { - ptrs.push( - // SAFETY: caller ensures that the cell has read access to the component. - unsafe { cell.get_by_id(id) }.ok_or(EntityComponentError::MissingComponent(id))?, - ); - } - Ok(ptrs) - } - - unsafe fn fetch_mut( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // Check for duplicate component IDs. - for i in 0..self.len() { - for j in 0..i { - if self[i] == self[j] { - return Err(EntityComponentError::AliasedMutability(self[i])); - } - } - } - - let mut ptrs = Vec::with_capacity(self.len()); - for &id in self { - ptrs.push( - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_by_id(id) } - .map_err(|_| EntityComponentError::MissingComponent(id))?, - ); - } - Ok(ptrs) - } - - unsafe fn fetch_mut_assume_mutable( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - // Check for duplicate component IDs. - for i in 0..self.len() { - for j in 0..i { - if self[i] == self[j] { - return Err(EntityComponentError::AliasedMutability(self[i])); - } - } - } - - let mut ptrs = Vec::with_capacity(self.len()); - for &id in self { - ptrs.push( - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_assume_mutable_by_id(id) } - .map_err(|_| EntityComponentError::MissingComponent(id))?, - ); - } - Ok(ptrs) - } -} - -// SAFETY: -// - No aliased mutability is caused because `HashSet` guarantees unique elements. -// - No mutable references are returned by `fetch_ref`. -unsafe impl DynamicComponentFetch for &'_ HashSet { - type Ref<'w> = HashMap>; - type Mut<'w> = HashMap>; - - unsafe fn fetch_ref( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - let mut ptrs = HashMap::with_capacity_and_hasher(self.len(), Default::default()); - for &id in self { - ptrs.insert( - id, - // SAFETY: caller ensures that the cell has read access to the component. - unsafe { cell.get_by_id(id) }.ok_or(EntityComponentError::MissingComponent(id))?, - ); - } - Ok(ptrs) - } - - unsafe fn fetch_mut( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - let mut ptrs = HashMap::with_capacity_and_hasher(self.len(), Default::default()); - for &id in self { - ptrs.insert( - id, - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_by_id(id) } - .map_err(|_| EntityComponentError::MissingComponent(id))?, - ); - } - Ok(ptrs) - } - - unsafe fn fetch_mut_assume_mutable( - self, - cell: UnsafeEntityCell<'_>, - ) -> Result, EntityComponentError> { - let mut ptrs = HashMap::with_capacity_and_hasher(self.len(), Default::default()); - for &id in self { - ptrs.insert( - id, - // SAFETY: caller ensures that the cell has mutable access to the component. - unsafe { cell.get_mut_assume_mutable_by_id(id) } - .map_err(|_| EntityComponentError::MissingComponent(id))?, - ); - } - Ok(ptrs) - } -} - -#[cfg(test)] -mod tests { - use alloc::{vec, vec::Vec}; - use bevy_ptr::{OwningPtr, Ptr}; - use core::panic::AssertUnwindSafe; - use std::sync::OnceLock; - - use crate::component::Tick; - use crate::lifecycle::HookContext; - use crate::{ - change_detection::{MaybeLocation, MutUntyped}, - component::ComponentId, - entity_disabling::Internal, - prelude::*, - system::{assert_is_system, RunSystemOnce as _}, - world::{error::EntityComponentError, DeferredWorld, FilteredEntityMut, FilteredEntityRef}, - }; - - use super::{EntityMutExcept, EntityRefExcept}; - - #[derive(Component, Clone, Copy, Debug, PartialEq)] - struct TestComponent(u32); - - #[derive(Component, Clone, Copy, Debug, PartialEq)] - #[component(storage = "SparseSet")] - struct TestComponent2(u32); - - #[test] - fn entity_ref_get_by_id() { - let mut world = World::new(); - let entity = world.spawn(TestComponent(42)).id(); - let component_id = world - .components() - .get_valid_id(core::any::TypeId::of::()) - .unwrap(); - - let entity = world.entity(entity); - let test_component = entity.get_by_id(component_id).unwrap(); - // SAFETY: points to a valid `TestComponent` - let test_component = unsafe { test_component.deref::() }; - - assert_eq!(test_component.0, 42); - } - - #[test] - fn entity_mut_get_by_id() { - let mut world = World::new(); - let entity = world.spawn(TestComponent(42)).id(); - let component_id = world - .components() - .get_valid_id(core::any::TypeId::of::()) - .unwrap(); - - let mut entity_mut = world.entity_mut(entity); - let mut test_component = entity_mut.get_mut_by_id(component_id).unwrap(); - { - test_component.set_changed(); - let test_component = - // SAFETY: `test_component` has unique access of the `EntityWorldMut` and is not used afterwards - unsafe { test_component.into_inner().deref_mut::() }; - test_component.0 = 43; - } - - let entity = world.entity(entity); - let test_component = entity.get_by_id(component_id).unwrap(); - // SAFETY: `TestComponent` is the correct component type - let test_component = unsafe { test_component.deref::() }; - - assert_eq!(test_component.0, 43); - } - - #[test] - fn entity_ref_get_by_id_invalid_component_id() { - let invalid_component_id = ComponentId::new(usize::MAX); - - let mut world = World::new(); - let entity = world.spawn_empty().id(); - let entity = world.entity(entity); - assert!(entity.get_by_id(invalid_component_id).is_err()); - } - - #[test] - fn entity_mut_get_by_id_invalid_component_id() { - let invalid_component_id = ComponentId::new(usize::MAX); - - let mut world = World::new(); - let mut entity = world.spawn_empty(); - assert!(entity.get_by_id(invalid_component_id).is_err()); - assert!(entity.get_mut_by_id(invalid_component_id).is_err()); - } - - #[derive(Resource)] - struct R(usize); - - #[test] - fn entity_mut_resource_scope() { - // Keep in sync with the `resource_scope` test in lib.rs - let mut world = World::new(); - let mut entity = world.spawn_empty(); - - assert!(entity.try_resource_scope::(|_, _| {}).is_none()); - entity.world_scope(|world| world.insert_resource(R(0))); - entity.resource_scope(|entity: &mut EntityWorldMut, mut value: Mut| { - value.0 += 1; - assert!(!entity.world().contains_resource::()); - }); - assert_eq!(entity.resource::().0, 1); - } - - #[test] - fn entity_mut_resource_scope_panic() { - let mut world = World::new(); - world.insert_resource(R(0)); - - let mut entity = world.spawn_empty(); - let old_location = entity.location(); - let result = std::panic::catch_unwind(AssertUnwindSafe(|| { - entity.resource_scope(|entity: &mut EntityWorldMut, _: Mut| { - // Change the entity's `EntityLocation`. - entity.insert(TestComponent(0)); - - // Ensure that the entity location still gets updated even in case of a panic. - panic!("this should get caught by the outer scope") - }); - })); - assert!(result.is_err()); - - // Ensure that the location has been properly updated. - assert_ne!(entity.location(), old_location); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7387 - #[test] - fn entity_mut_world_scope_panic() { - let mut world = World::new(); - - let mut entity = world.spawn_empty(); - let old_location = entity.location(); - let id = entity.id(); - let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - entity.world_scope(|w| { - // Change the entity's `EntityLocation`, which invalidates the original `EntityWorldMut`. - // This will get updated at the end of the scope. - w.entity_mut(id).insert(TestComponent(0)); - - // Ensure that the entity location still gets updated even in case of a panic. - panic!("this should get caught by the outer scope") - }); - })); - assert!(res.is_err()); - - // Ensure that the location has been properly updated. - assert_ne!(entity.location(), old_location); - } - - #[test] - fn entity_mut_reborrow_scope_panic() { - let mut world = World::new(); - - let mut entity = world.spawn_empty(); - let old_location = entity.location(); - let res = std::panic::catch_unwind(AssertUnwindSafe(|| { - entity.reborrow_scope(|mut entity| { - // Change the entity's `EntityLocation`, which invalidates the original `EntityWorldMut`. - // This will get updated at the end of the scope. - entity.insert(TestComponent(0)); - - // Ensure that the entity location still gets updated even in case of a panic. - panic!("this should get caught by the outer scope") - }); - })); - assert!(res.is_err()); - - // Ensure that the location has been properly updated. - assert_ne!(entity.location(), old_location); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7805 - #[test] - fn removing_sparse_updates_archetype_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn((Dense(0), Sparse)).id(); - let e2 = world.spawn((Dense(1), Sparse)).id(); - - world.entity_mut(e1).remove::(); - assert_eq!(world.entity(e2).get::().unwrap(), &Dense(1)); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7805 - #[test] - fn removing_dense_updates_table_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn((Dense(0), Sparse)).id(); - let e2 = world.spawn((Dense(1), Sparse)).id(); - - world.entity_mut(e1).remove::(); - assert_eq!(world.entity(e2).get::().unwrap(), &Dense(1)); - } - - // Test that calling retain with `()` removes all components. - #[test] - fn retain_nothing() { - #[derive(Component)] - struct Marker; - - let mut world = World::new(); - let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id(); - - world.entity_mut(ent).retain::<()>(); - assert_eq!(world.entity(ent).archetype().components().len(), 0); - } - - // Test removing some components with `retain`, including components not on the entity. - #[test] - fn retain_some_components() { - #[derive(Component)] - struct Marker; - - let mut world = World::new(); - let ent = world.spawn((Marker::<1>, Marker::<2>, Marker::<3>)).id(); - - world.entity_mut(ent).retain::<(Marker<2>, Marker<4>)>(); - // Check that marker 2 was retained. - assert!(world.entity(ent).get::>().is_some()); - // Check that only marker 2 was retained. - assert_eq!(world.entity(ent).archetype().components().len(), 1); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7805 - #[test] - fn inserting_sparse_updates_archetype_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn(Dense(0)).id(); - let e2 = world.spawn(Dense(1)).id(); - - world.entity_mut(e1).insert(Sparse); - assert_eq!(world.entity(e2).get::().unwrap(), &Dense(1)); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7805 - #[test] - fn inserting_dense_updates_archetype_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - struct Dense2; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn(Dense(0)).id(); - let e2 = world.spawn(Dense(1)).id(); - - world.entity_mut(e1).insert(Sparse).remove::(); - - // archetype with [e2, e1] - // table with [e1, e2] - - world.entity_mut(e2).insert(Dense2); - - assert_eq!(world.entity(e1).get::().unwrap(), &Dense(0)); - } - - #[test] - fn inserting_dense_updates_table_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - struct Dense2; - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn(Dense(0)).id(); - let e2 = world.spawn(Dense(1)).id(); - - world.entity_mut(e1).insert(Sparse).remove::(); - - // archetype with [e2, e1] - // table with [e1, e2] - - world.entity_mut(e1).insert(Dense2); - - assert_eq!(world.entity(e2).get::().unwrap(), &Dense(1)); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7805 - #[test] - fn despawning_entity_updates_archetype_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn(Dense(0)).id(); - let e2 = world.spawn(Dense(1)).id(); - - world.entity_mut(e1).insert(Sparse).remove::(); - - // archetype with [e2, e1] - // table with [e1, e2] - - world.entity_mut(e2).despawn(); - - assert_eq!(world.entity(e1).get::().unwrap(), &Dense(0)); - } - - // regression test for https://github.com/bevyengine/bevy/pull/7805 - #[test] - fn despawning_entity_updates_table_row() { - #[derive(Component, PartialEq, Debug)] - struct Dense(u8); - - #[derive(Component)] - #[component(storage = "SparseSet")] - struct Sparse; - - let mut world = World::new(); - let e1 = world.spawn(Dense(0)).id(); - let e2 = world.spawn(Dense(1)).id(); - - world.entity_mut(e1).insert(Sparse).remove::(); - - // archetype with [e2, e1] - // table with [e1, e2] - - world.entity_mut(e1).despawn(); - - assert_eq!(world.entity(e2).get::().unwrap(), &Dense(1)); - } - - #[test] - fn entity_mut_insert_by_id() { - let mut world = World::new(); - let test_component_id = world.register_component::(); - - let mut entity = world.spawn_empty(); - OwningPtr::make(TestComponent(42), |ptr| { - // SAFETY: `ptr` matches the component id - unsafe { entity.insert_by_id(test_component_id, ptr) }; - }); - - let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect(); - - assert_eq!(components, vec![&TestComponent(42)]); - - // Compare with `insert_bundle_by_id` - - let mut entity = world.spawn_empty(); - OwningPtr::make(TestComponent(84), |ptr| { - // SAFETY: `ptr` matches the component id - unsafe { entity.insert_by_ids(&[test_component_id], vec![ptr].into_iter()) }; - }); - - let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect(); - - assert_eq!(components, vec![&TestComponent(42), &TestComponent(84)]); - } - - #[test] - fn entity_mut_insert_bundle_by_id() { - let mut world = World::new(); - let test_component_id = world.register_component::(); - let test_component_2_id = world.register_component::(); - - let component_ids = [test_component_id, test_component_2_id]; - let test_component_value = TestComponent(42); - let test_component_2_value = TestComponent2(84); - - let mut entity = world.spawn_empty(); - OwningPtr::make(test_component_value, |ptr1| { - OwningPtr::make(test_component_2_value, |ptr2| { - // SAFETY: `ptr1` and `ptr2` match the component ids - unsafe { entity.insert_by_ids(&component_ids, vec![ptr1, ptr2].into_iter()) }; - }); - }); - - let dynamic_components: Vec<_> = world - .query::<(&TestComponent, &TestComponent2)>() - .iter(&world) - .collect(); - - assert_eq!( - dynamic_components, - vec![(&TestComponent(42), &TestComponent2(84))] - ); - - // Compare with `World` generated using static type equivalents - let mut static_world = World::new(); - - static_world.spawn((test_component_value, test_component_2_value)); - let static_components: Vec<_> = static_world - .query::<(&TestComponent, &TestComponent2)>() - .iter(&static_world) - .collect(); - - assert_eq!(dynamic_components, static_components); - } - - #[test] - fn entity_mut_remove_by_id() { - let mut world = World::new(); - let test_component_id = world.register_component::(); - - let mut entity = world.spawn(TestComponent(42)); - entity.remove_by_id(test_component_id); - - let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect(); - - assert_eq!(components, vec![] as Vec<&TestComponent>); - - // remove non-existent component does not panic - world.spawn_empty().remove_by_id(test_component_id); - } - - /// Tests that components can be accessed through an `EntityRefExcept`. - #[test] - fn entity_ref_except() { - let mut world = World::new(); - world.register_component::(); - world.register_component::(); - - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - let mut query = world.query::>(); - - let mut found = false; - for entity_ref in query.iter_mut(&mut world) { - found = true; - assert!(entity_ref.get::().is_none()); - assert!(entity_ref.get_ref::().is_none()); - assert!(matches!( - entity_ref.get::(), - Some(TestComponent2(0)) - )); - } - - assert!(found); - } - - // Test that a single query can't both contain a mutable reference to a - // component C and an `EntityRefExcept` that doesn't include C among its - // exclusions. - #[test] - #[should_panic] - fn entity_ref_except_conflicts_with_self() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - // This should panic, because we have a mutable borrow on - // `TestComponent` but have a simultaneous indirect immutable borrow on - // that component via `EntityRefExcept`. - world.run_system_once(system).unwrap(); - - fn system(_: Query<(&mut TestComponent, EntityRefExcept)>) {} - } - - // Test that an `EntityRefExcept` that doesn't include a component C among - // its exclusions can't coexist with a mutable query for that component. - #[test] - #[should_panic] - fn entity_ref_except_conflicts_with_other() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - // This should panic, because we have a mutable borrow on - // `TestComponent` but have a simultaneous indirect immutable borrow on - // that component via `EntityRefExcept`. - world.run_system_once(system).unwrap(); - - fn system(_: Query<&mut TestComponent>, _: Query>) {} - } - - // Test that an `EntityRefExcept` with an exception for some component C can - // coexist with a query for that component C. - #[test] - fn entity_ref_except_doesnt_conflict() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - world.run_system_once(system).unwrap(); - - fn system( - _: Query<&mut TestComponent>, - query: Query>, - ) { - for entity_ref in query.iter() { - assert!(matches!( - entity_ref.get::(), - Some(TestComponent2(0)) - )); - } - } - } - - /// Tests that components can be mutably accessed through an - /// `EntityMutExcept`. - #[test] - fn entity_mut_except() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - let mut query = world.query::>(); - - let mut found = false; - for mut entity_mut in query.iter_mut(&mut world) { - found = true; - assert!(entity_mut.get::().is_none()); - assert!(entity_mut.get_ref::().is_none()); - assert!(entity_mut.get_mut::().is_none()); - assert!(matches!( - entity_mut.get::(), - Some(TestComponent2(0)) - )); - } - - assert!(found); - } - - // Test that a single query can't both contain a mutable reference to a - // component C and an `EntityMutExcept` that doesn't include C among its - // exclusions. - #[test] - #[should_panic] - fn entity_mut_except_conflicts_with_self() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - // This should panic, because we have a mutable borrow on - // `TestComponent` but have a simultaneous indirect immutable borrow on - // that component via `EntityRefExcept`. - world.run_system_once(system).unwrap(); - - fn system(_: Query<(&mut TestComponent, EntityMutExcept)>) {} - } - - // Test that an `EntityMutExcept` that doesn't include a component C among - // its exclusions can't coexist with a query for that component. - #[test] - #[should_panic] - fn entity_mut_except_conflicts_with_other() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - // This should panic, because we have a mutable borrow on - // `TestComponent` but have a simultaneous indirect immutable borrow on - // that component via `EntityRefExcept`. - world.run_system_once(system).unwrap(); - - fn system(_: Query<&mut TestComponent>, mut query: Query>) { - for mut entity_mut in query.iter_mut() { - assert!(entity_mut - .get_mut::() - .is_some_and(|component| component.0 == 0)); - } - } - } - - // Test that an `EntityMutExcept` with an exception for some component C can - // coexist with a query for that component C. - #[test] - fn entity_mut_except_doesnt_conflict() { - let mut world = World::new(); - world.spawn(TestComponent(0)).insert(TestComponent2(0)); - - world.run_system_once(system).unwrap(); - - fn system( - _: Query<&mut TestComponent>, - mut query: Query>, - ) { - for mut entity_mut in query.iter_mut() { - assert!(entity_mut - .get_mut::() - .is_some_and(|component| component.0 == 0)); - } - } - } - - #[test] - fn entity_mut_except_registers_components() { - // Checks for a bug where `EntityMutExcept` would not register the component and - // would therefore not include an exception, causing it to conflict with the later query. - fn system1(_query: Query>, _: Query<&mut TestComponent>) {} - let mut world = World::new(); - world.run_system_once(system1).unwrap(); - - fn system2(_: Query<&mut TestComponent>, _query: Query>) {} - let mut world = World::new(); - world.run_system_once(system2).unwrap(); - } - - #[derive(Component)] - struct A; - - #[test] - fn disjoint_access() { - fn disjoint_readonly(_: Query>, _: Query>) {} - - fn disjoint_mutable(_: Query>, _: Query>) {} - - assert_is_system(disjoint_readonly); - assert_is_system(disjoint_mutable); - } - - #[test] - fn ref_compatible() { - fn borrow_system(_: Query<(EntityRef, &A)>, _: Query<&A>) {} - - assert_is_system(borrow_system); - } - - #[test] - fn ref_compatible_with_resource() { - fn borrow_system(_: Query, _: Res) {} - - assert_is_system(borrow_system); - } - - #[test] - fn ref_compatible_with_resource_mut() { - fn borrow_system(_: Query, _: ResMut) {} - - assert_is_system(borrow_system); - } - - #[test] - #[should_panic] - fn ref_incompatible_with_mutable_component() { - fn incompatible_system(_: Query<(EntityRef, &mut A)>) {} - - assert_is_system(incompatible_system); - } - - #[test] - #[should_panic] - fn ref_incompatible_with_mutable_query() { - fn incompatible_system(_: Query, _: Query<&mut A>) {} - - assert_is_system(incompatible_system); - } - - #[test] - fn mut_compatible_with_entity() { - fn borrow_mut_system(_: Query<(Entity, EntityMut)>) {} - - assert_is_system(borrow_mut_system); - } - - #[test] - fn mut_compatible_with_resource() { - fn borrow_mut_system(_: Res, _: Query) {} - - assert_is_system(borrow_mut_system); - } - - #[test] - fn mut_compatible_with_resource_mut() { - fn borrow_mut_system(_: ResMut, _: Query) {} - - assert_is_system(borrow_mut_system); - } - - #[test] - #[should_panic] - fn mut_incompatible_with_read_only_component() { - fn incompatible_system(_: Query<(EntityMut, &A)>) {} - - assert_is_system(incompatible_system); - } - - #[test] - #[should_panic] - fn mut_incompatible_with_mutable_component() { - fn incompatible_system(_: Query<(EntityMut, &mut A)>) {} - - assert_is_system(incompatible_system); - } - - #[test] - #[should_panic] - fn mut_incompatible_with_read_only_query() { - fn incompatible_system(_: Query, _: Query<&A>) {} - - assert_is_system(incompatible_system); - } - - #[test] - #[should_panic] - fn mut_incompatible_with_mutable_query() { - fn incompatible_system(_: Query, _: Query<&mut A>) {} - - assert_is_system(incompatible_system); - } - - #[test] - fn filtered_entity_ref_normal() { - let mut world = World::new(); - let a_id = world.register_component::(); - - let e: FilteredEntityRef = world.spawn(A).into(); - - assert!(e.get::().is_some()); - assert!(e.get_ref::().is_some()); - assert!(e.get_change_ticks::().is_some()); - assert!(e.get_by_id(a_id).is_some()); - assert!(e.get_change_ticks_by_id(a_id).is_some()); - } - - #[test] - fn filtered_entity_ref_missing() { - let mut world = World::new(); - let a_id = world.register_component::(); - - let e: FilteredEntityRef = world.spawn(()).into(); - - assert!(e.get::().is_none()); - assert!(e.get_ref::().is_none()); - assert!(e.get_change_ticks::().is_none()); - assert!(e.get_by_id(a_id).is_none()); - assert!(e.get_change_ticks_by_id(a_id).is_none()); - } - - #[test] - fn filtered_entity_mut_normal() { - let mut world = World::new(); - let a_id = world.register_component::(); - - let mut e: FilteredEntityMut = world.spawn(A).into(); - - assert!(e.get::().is_some()); - assert!(e.get_ref::().is_some()); - assert!(e.get_mut::().is_some()); - assert!(e.get_change_ticks::().is_some()); - assert!(e.get_by_id(a_id).is_some()); - assert!(e.get_mut_by_id(a_id).is_some()); - assert!(e.get_change_ticks_by_id(a_id).is_some()); - } - - #[test] - fn filtered_entity_mut_missing() { - let mut world = World::new(); - let a_id = world.register_component::(); - - let mut e: FilteredEntityMut = world.spawn(()).into(); - - assert!(e.get::().is_none()); - assert!(e.get_ref::().is_none()); - assert!(e.get_mut::().is_none()); - assert!(e.get_change_ticks::().is_none()); - assert!(e.get_by_id(a_id).is_none()); - assert!(e.get_mut_by_id(a_id).is_none()); - assert!(e.get_change_ticks_by_id(a_id).is_none()); - } - - #[derive(Component, PartialEq, Eq, Debug)] - struct X(usize); - - #[derive(Component, PartialEq, Eq, Debug)] - struct Y(usize); - - #[test] - fn get_components() { - let mut world = World::default(); - let e1 = world.spawn((X(7), Y(10))).id(); - let e2 = world.spawn(X(8)).id(); - let e3 = world.spawn_empty().id(); - - assert_eq!( - Some((&X(7), &Y(10))), - world.entity(e1).get_components::<(&X, &Y)>() - ); - assert_eq!(None, world.entity(e2).get_components::<(&X, &Y)>()); - assert_eq!(None, world.entity(e3).get_components::<(&X, &Y)>()); - } - - #[test] - fn get_by_id_array() { - let mut world = World::default(); - let e1 = world.spawn((X(7), Y(10))).id(); - let e2 = world.spawn(X(8)).id(); - let e3 = world.spawn_empty().id(); - - let x_id = world.register_component::(); - let y_id = world.register_component::(); - - assert_eq!( - Ok((&X(7), &Y(10))), - world - .entity(e1) - .get_by_id([x_id, y_id]) - .map(|[x_ptr, y_ptr]| { - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(y_id)), - world - .entity(e2) - .get_by_id([x_id, y_id]) - .map(|[x_ptr, y_ptr]| { - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(x_id)), - world - .entity(e3) - .get_by_id([x_id, y_id]) - .map(|[x_ptr, y_ptr]| { - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }) - }) - ); - } - - #[test] - fn get_by_id_vec() { - let mut world = World::default(); - let e1 = world.spawn((X(7), Y(10))).id(); - let e2 = world.spawn(X(8)).id(); - let e3 = world.spawn_empty().id(); - - let x_id = world.register_component::(); - let y_id = world.register_component::(); - - assert_eq!( - Ok((&X(7), &Y(10))), - world - .entity(e1) - .get_by_id(&[x_id, y_id] as &[ComponentId]) - .map(|ptrs| { - let Ok([x_ptr, y_ptr]): Result<[Ptr; 2], _> = ptrs.try_into() else { - panic!("get_by_id(slice) didn't return 2 elements") - }; - - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(y_id)), - world - .entity(e2) - .get_by_id(&[x_id, y_id] as &[ComponentId]) - .map(|ptrs| { - let Ok([x_ptr, y_ptr]): Result<[Ptr; 2], _> = ptrs.try_into() else { - panic!("get_by_id(slice) didn't return 2 elements") - }; - - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(x_id)), - world - .entity(e3) - .get_by_id(&[x_id, y_id] as &[ComponentId]) - .map(|ptrs| { - let Ok([x_ptr, y_ptr]): Result<[Ptr; 2], _> = ptrs.try_into() else { - panic!("get_by_id(slice) didn't return 2 elements") - }; - - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.deref::() }, unsafe { y_ptr.deref::() }) - }) - ); - } - - #[test] - fn get_mut_by_id_array() { - let mut world = World::default(); - let e1 = world.spawn((X(7), Y(10))).id(); - let e2 = world.spawn(X(8)).id(); - let e3 = world.spawn_empty().id(); - - let x_id = world.register_component::(); - let y_id = world.register_component::(); - - assert_eq!( - Ok((&mut X(7), &mut Y(10))), - world - .entity_mut(e1) - .get_mut_by_id([x_id, y_id]) - .map(|[x_ptr, y_ptr]| { - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.into_inner().deref_mut::() }, unsafe { - y_ptr.into_inner().deref_mut::() - }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(y_id)), - world - .entity_mut(e2) - .get_mut_by_id([x_id, y_id]) - .map(|[x_ptr, y_ptr]| { - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.into_inner().deref_mut::() }, unsafe { - y_ptr.into_inner().deref_mut::() - }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(x_id)), - world - .entity_mut(e3) - .get_mut_by_id([x_id, y_id]) - .map(|[x_ptr, y_ptr]| { - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.into_inner().deref_mut::() }, unsafe { - y_ptr.into_inner().deref_mut::() - }) - }) - ); - - assert_eq!( - Err(EntityComponentError::AliasedMutability(x_id)), - world - .entity_mut(e1) - .get_mut_by_id([x_id, x_id]) - .map(|_| { unreachable!() }) - ); - assert_eq!( - Err(EntityComponentError::AliasedMutability(x_id)), - world - .entity_mut(e3) - .get_mut_by_id([x_id, x_id]) - .map(|_| { unreachable!() }) - ); - } - - #[test] - fn get_mut_by_id_vec() { - let mut world = World::default(); - let e1 = world.spawn((X(7), Y(10))).id(); - let e2 = world.spawn(X(8)).id(); - let e3 = world.spawn_empty().id(); - - let x_id = world.register_component::(); - let y_id = world.register_component::(); - - assert_eq!( - Ok((&mut X(7), &mut Y(10))), - world - .entity_mut(e1) - .get_mut_by_id(&[x_id, y_id] as &[ComponentId]) - .map(|ptrs| { - let Ok([x_ptr, y_ptr]): Result<[MutUntyped; 2], _> = ptrs.try_into() else { - panic!("get_mut_by_id(slice) didn't return 2 elements") - }; - - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.into_inner().deref_mut::() }, unsafe { - y_ptr.into_inner().deref_mut::() - }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(y_id)), - world - .entity_mut(e2) - .get_mut_by_id(&[x_id, y_id] as &[ComponentId]) - .map(|ptrs| { - let Ok([x_ptr, y_ptr]): Result<[MutUntyped; 2], _> = ptrs.try_into() else { - panic!("get_mut_by_id(slice) didn't return 2 elements") - }; - - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.into_inner().deref_mut::() }, unsafe { - y_ptr.into_inner().deref_mut::() - }) - }) - ); - assert_eq!( - Err(EntityComponentError::MissingComponent(x_id)), - world - .entity_mut(e3) - .get_mut_by_id(&[x_id, y_id] as &[ComponentId]) - .map(|ptrs| { - let Ok([x_ptr, y_ptr]): Result<[MutUntyped; 2], _> = ptrs.try_into() else { - panic!("get_mut_by_id(slice) didn't return 2 elements") - }; - - // SAFETY: components match the id they were fetched with - (unsafe { x_ptr.into_inner().deref_mut::() }, unsafe { - y_ptr.into_inner().deref_mut::() - }) - }) - ); - - assert_eq!( - Err(EntityComponentError::AliasedMutability(x_id)), - world - .entity_mut(e1) - .get_mut_by_id(&[x_id, x_id]) - .map(|_| { unreachable!() }) - ); - assert_eq!( - Err(EntityComponentError::AliasedMutability(x_id)), - world - .entity_mut(e3) - .get_mut_by_id(&[x_id, x_id]) - .map(|_| { unreachable!() }) - ); - } - - #[test] - fn get_mut_by_id_unchecked() { - let mut world = World::default(); - let e1 = world.spawn((X(7), Y(10))).id(); - let x_id = world.register_component::(); - let y_id = world.register_component::(); - - let e1_mut = &world.get_entity_mut([e1]).unwrap()[0]; - // SAFETY: The entity e1 contains component X. - let x_ptr = unsafe { e1_mut.get_mut_by_id_unchecked(x_id) }.unwrap(); - // SAFETY: The entity e1 contains component Y, with components X and Y being mutually independent. - let y_ptr = unsafe { e1_mut.get_mut_by_id_unchecked(y_id) }.unwrap(); - - // SAFETY: components match the id they were fetched with - let x_component = unsafe { x_ptr.into_inner().deref_mut::() }; - x_component.0 += 1; - // SAFETY: components match the id they were fetched with - let y_component = unsafe { y_ptr.into_inner().deref_mut::() }; - y_component.0 -= 1; - - assert_eq!((&mut X(8), &mut Y(9)), (x_component, y_component)); - } - - #[derive(EntityEvent)] - struct TestEvent(Entity); - - #[test] - fn adding_observer_updates_location() { - let mut world = World::new(); - let entity = world - .spawn_empty() - .observe(|event: On, mut commands: Commands| { - commands - .entity(event.event_target()) - .insert(TestComponent(0)); - }) - .id(); - - // this should not be needed, but is currently required to tease out the bug - world.flush(); - - let mut a = world.entity_mut(entity); - // SAFETY: this _intentionally_ doesn't update the location, to ensure that we're actually testing - // that observe() updates location - unsafe { a.world_mut().trigger(TestEvent(entity)) } - a.observe(|_: On| {}); // this flushes commands implicitly by spawning - let location = a.location(); - assert_eq!(world.entities().get(entity), Some(location)); - } - - #[test] - #[should_panic] - fn location_on_despawned_entity_panics() { - let mut world = World::new(); - world.add_observer(|add: On, mut commands: Commands| { - commands.entity(add.entity).despawn(); - }); - let entity = world.spawn_empty().id(); - let mut a = world.entity_mut(entity); - a.insert(TestComponent(0)); - a.location(); - } - - #[derive(Resource)] - struct TestFlush(usize); - - fn count_flush(world: &mut World) { - world.resource_mut::().0 += 1; - } - - #[test] - fn archetype_modifications_trigger_flush() { - let mut world = World::new(); - world.insert_resource(TestFlush(0)); - world.add_observer(|_: On, mut commands: Commands| { - commands.queue(count_flush); - }); - world.add_observer(|_: On, mut commands: Commands| { - commands.queue(count_flush); - }); - world.commands().queue(count_flush); - let entity = world.spawn_empty().id(); - assert_eq!(world.resource::().0, 1); - world.commands().queue(count_flush); - world.flush_commands(); - let mut a = world.entity_mut(entity); - assert_eq!(a.world().resource::().0, 2); - a.insert(TestComponent(0)); - assert_eq!(a.world().resource::().0, 3); - a.remove::(); - assert_eq!(a.world().resource::().0, 4); - a.insert(TestComponent(0)); - assert_eq!(a.world().resource::().0, 5); - let _ = a.take::(); - assert_eq!(a.world().resource::().0, 6); - a.insert(TestComponent(0)); - assert_eq!(a.world().resource::().0, 7); - a.retain::<()>(); - assert_eq!(a.world().resource::().0, 8); - a.insert(TestComponent(0)); - assert_eq!(a.world().resource::().0, 9); - a.clear(); - assert_eq!(a.world().resource::().0, 10); - a.insert(TestComponent(0)); - assert_eq!(a.world().resource::().0, 11); - a.despawn(); - assert_eq!(world.resource::().0, 12); - } - - #[derive(Resource)] - struct TestVec(Vec<&'static str>); - - #[derive(Component)] - #[component(on_add = ord_a_hook_on_add, on_insert = ord_a_hook_on_insert, on_replace = ord_a_hook_on_replace, on_remove = ord_a_hook_on_remove)] - struct OrdA; - - fn ord_a_hook_on_add(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { - world.resource_mut::().0.push("OrdA hook on_add"); - world.commands().entity(entity).insert(OrdB); - } - - fn ord_a_hook_on_insert(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) { - world - .resource_mut::() - .0 - .push("OrdA hook on_insert"); - world.commands().entity(entity).remove::(); - world.commands().entity(entity).remove::(); - } - - fn ord_a_hook_on_replace(mut world: DeferredWorld, _: HookContext) { - world - .resource_mut::() - .0 - .push("OrdA hook on_replace"); - } - - fn ord_a_hook_on_remove(mut world: DeferredWorld, _: HookContext) { - world - .resource_mut::() - .0 - .push("OrdA hook on_remove"); - } - - fn ord_a_observer_on_add(_event: On, mut res: ResMut) { - res.0.push("OrdA observer on_add"); - } - - fn ord_a_observer_on_insert(_event: On, mut res: ResMut) { - res.0.push("OrdA observer on_insert"); - } - - fn ord_a_observer_on_replace(_event: On, mut res: ResMut) { - res.0.push("OrdA observer on_replace"); - } - - fn ord_a_observer_on_remove(_event: On, mut res: ResMut) { - res.0.push("OrdA observer on_remove"); - } - - #[derive(Component)] - #[component(on_add = ord_b_hook_on_add, on_insert = ord_b_hook_on_insert, on_replace = ord_b_hook_on_replace, on_remove = ord_b_hook_on_remove)] - struct OrdB; - - fn ord_b_hook_on_add(mut world: DeferredWorld, _: HookContext) { - world.resource_mut::().0.push("OrdB hook on_add"); - world.commands().queue(|world: &mut World| { - world - .resource_mut::() - .0 - .push("OrdB command on_add"); - }); - } - - fn ord_b_hook_on_insert(mut world: DeferredWorld, _: HookContext) { - world - .resource_mut::() - .0 - .push("OrdB hook on_insert"); - } - - fn ord_b_hook_on_replace(mut world: DeferredWorld, _: HookContext) { - world - .resource_mut::() - .0 - .push("OrdB hook on_replace"); - } - - fn ord_b_hook_on_remove(mut world: DeferredWorld, _: HookContext) { - world - .resource_mut::() - .0 - .push("OrdB hook on_remove"); - } - - fn ord_b_observer_on_add(_event: On, mut res: ResMut) { - res.0.push("OrdB observer on_add"); - } - - fn ord_b_observer_on_insert(_event: On, mut res: ResMut) { - res.0.push("OrdB observer on_insert"); - } - - fn ord_b_observer_on_replace(_event: On, mut res: ResMut) { - res.0.push("OrdB observer on_replace"); - } - - fn ord_b_observer_on_remove(_event: On, mut res: ResMut) { - res.0.push("OrdB observer on_remove"); - } - - #[test] - fn command_ordering_is_correct() { - let mut world = World::new(); - world.insert_resource(TestVec(Vec::new())); - world.add_observer(ord_a_observer_on_add); - world.add_observer(ord_a_observer_on_insert); - world.add_observer(ord_a_observer_on_replace); - world.add_observer(ord_a_observer_on_remove); - world.add_observer(ord_b_observer_on_add); - world.add_observer(ord_b_observer_on_insert); - world.add_observer(ord_b_observer_on_replace); - world.add_observer(ord_b_observer_on_remove); - let _entity = world.spawn(OrdA).id(); - let expected = [ - "OrdA hook on_add", // adds command to insert OrdB - "OrdA observer on_add", - "OrdA hook on_insert", // adds command to despawn entity - "OrdA observer on_insert", - "OrdB hook on_add", // adds command to just add to this log - "OrdB observer on_add", - "OrdB hook on_insert", - "OrdB observer on_insert", - "OrdB command on_add", // command added by OrdB hook on_add, needs to run before despawn command - "OrdA observer on_replace", // start of despawn - "OrdA hook on_replace", - "OrdA observer on_remove", - "OrdA hook on_remove", - "OrdB observer on_replace", - "OrdB hook on_replace", - "OrdB observer on_remove", - "OrdB hook on_remove", - ]; - world.flush(); - assert_eq!(world.resource_mut::().0.as_slice(), &expected[..]); - } - - #[test] - fn entity_world_mut_clone_and_move_components() { - #[derive(Component, Clone, PartialEq, Debug)] - struct A; - - #[derive(Component, Clone, PartialEq, Debug)] - struct B; - - #[derive(Component, Clone, PartialEq, Debug)] - struct C(u32); - - let mut world = World::new(); - let entity_a = world.spawn((A, B, C(5))).id(); - let entity_b = world.spawn((A, C(4))).id(); - - world.entity_mut(entity_a).clone_components::(entity_b); - assert_eq!(world.entity(entity_a).get::(), Some(&B)); - assert_eq!(world.entity(entity_b).get::(), Some(&B)); - - world.entity_mut(entity_a).move_components::(entity_b); - assert_eq!(world.entity(entity_a).get::(), None); - assert_eq!(world.entity(entity_b).get::(), Some(&C(5))); - - assert_eq!(world.entity(entity_a).get::(), Some(&A)); - assert_eq!(world.entity(entity_b).get::(), Some(&A)); - } - - #[test] - fn entity_world_mut_clone_with_move_and_require() { - #[derive(Component, Clone, PartialEq, Debug)] - #[require(B(3))] - struct A; - - #[derive(Component, Clone, PartialEq, Debug, Default)] - #[require(C(3))] - struct B(u32); - - #[derive(Component, Clone, PartialEq, Debug, Default)] - #[require(D)] - struct C(u32); - - #[derive(Component, Clone, PartialEq, Debug, Default)] - struct D; - - let mut world = World::new(); - let entity_a = world.spawn((A, B(5))).id(); - let entity_b = world.spawn_empty().id(); - - world - .entity_mut(entity_a) - .clone_with_opt_in(entity_b, |builder| { - builder - .move_components(true) - .allow::() - .without_required_components(|builder| { - builder.allow::(); - }); - }); - - assert_eq!(world.entity(entity_a).get::(), None); - assert_eq!(world.entity(entity_b).get::(), Some(&A)); - - assert_eq!(world.entity(entity_a).get::(), Some(&B(5))); - assert_eq!(world.entity(entity_b).get::(), Some(&B(3))); - - assert_eq!(world.entity(entity_a).get::(), None); - assert_eq!(world.entity(entity_b).get::(), Some(&C(3))); - - assert_eq!(world.entity(entity_a).get::(), None); - assert_eq!(world.entity(entity_b).get::(), Some(&D)); - } - - #[test] - fn update_despawned_by_after_observers() { - let mut world = World::new(); - - #[derive(Component)] - #[component(on_remove = get_tracked)] - struct C; - - static TRACKED: OnceLock<(MaybeLocation, Tick)> = OnceLock::new(); - fn get_tracked(world: DeferredWorld, HookContext { entity, .. }: HookContext) { - TRACKED.get_or_init(|| { - let by = world - .entities - .entity_get_spawned_or_despawned_by(entity) - .map(|l| l.unwrap()); - let at = world - .entities - .entity_get_spawn_or_despawn_tick(entity) - .unwrap(); - (by, at) - }); - } - - #[track_caller] - fn caller_spawn(world: &mut World) -> (Entity, MaybeLocation, Tick) { - let caller = MaybeLocation::caller(); - (world.spawn(C).id(), caller, world.change_tick()) - } - let (entity, spawner, spawn_tick) = caller_spawn(&mut world); - - assert_eq!( - spawner, - world - .entities() - .entity_get_spawned_or_despawned_by(entity) - .map(|l| l.unwrap()) - ); - - #[track_caller] - fn caller_despawn(world: &mut World, entity: Entity) -> (MaybeLocation, Tick) { - world.despawn(entity); - (MaybeLocation::caller(), world.change_tick()) - } - let (despawner, despawn_tick) = caller_despawn(&mut world, entity); - - assert_eq!((spawner, spawn_tick), *TRACKED.get().unwrap()); - assert_eq!( - despawner, - world - .entities() - .entity_get_spawned_or_despawned_by(entity) - .map(|l| l.unwrap()) - ); - assert_eq!( - despawn_tick, - world - .entities() - .entity_get_spawn_or_despawn_tick(entity) - .unwrap() - ); - } - - #[test] - fn with_component_activates_hooks() { - use core::sync::atomic::{AtomicBool, AtomicU8, Ordering}; - - #[derive(Component, PartialEq, Eq, Debug)] - #[component(immutable)] - struct Foo(bool); - - static EXPECTED_VALUE: AtomicBool = AtomicBool::new(false); - - static ADD_COUNT: AtomicU8 = AtomicU8::new(0); - static REMOVE_COUNT: AtomicU8 = AtomicU8::new(0); - static REPLACE_COUNT: AtomicU8 = AtomicU8::new(0); - static INSERT_COUNT: AtomicU8 = AtomicU8::new(0); - - let mut world = World::default(); - - world.register_component::(); - world - .register_component_hooks::() - .on_add(|world, context| { - ADD_COUNT.fetch_add(1, Ordering::Relaxed); - - assert_eq!( - world.get(context.entity), - Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) - ); - }) - .on_remove(|world, context| { - REMOVE_COUNT.fetch_add(1, Ordering::Relaxed); - - assert_eq!( - world.get(context.entity), - Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) - ); - }) - .on_replace(|world, context| { - REPLACE_COUNT.fetch_add(1, Ordering::Relaxed); - - assert_eq!( - world.get(context.entity), - Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) - ); - }) - .on_insert(|world, context| { - INSERT_COUNT.fetch_add(1, Ordering::Relaxed); - - assert_eq!( - world.get(context.entity), - Some(&Foo(EXPECTED_VALUE.load(Ordering::Relaxed))) - ); - }); - - let entity = world.spawn(Foo(false)).id(); - - assert_eq!(ADD_COUNT.load(Ordering::Relaxed), 1); - assert_eq!(REMOVE_COUNT.load(Ordering::Relaxed), 0); - assert_eq!(REPLACE_COUNT.load(Ordering::Relaxed), 0); - assert_eq!(INSERT_COUNT.load(Ordering::Relaxed), 1); - - let mut entity = world.entity_mut(entity); - - let archetype_pointer_before = &raw const *entity.archetype(); - - assert_eq!(entity.get::(), Some(&Foo(false))); - - entity.modify_component(|foo: &mut Foo| { - foo.0 = true; - EXPECTED_VALUE.store(foo.0, Ordering::Relaxed); - }); - - let archetype_pointer_after = &raw const *entity.archetype(); - - assert_eq!(entity.get::(), Some(&Foo(true))); - - assert_eq!(ADD_COUNT.load(Ordering::Relaxed), 1); - assert_eq!(REMOVE_COUNT.load(Ordering::Relaxed), 0); - assert_eq!(REPLACE_COUNT.load(Ordering::Relaxed), 1); - assert_eq!(INSERT_COUNT.load(Ordering::Relaxed), 2); - - assert_eq!(archetype_pointer_before, archetype_pointer_after); - } - - #[test] - fn bundle_remove_only_triggers_for_present_components() { - let mut world = World::default(); - - #[derive(Component)] - struct A; - - #[derive(Component)] - struct B; - - #[derive(Resource, PartialEq, Eq, Debug)] - struct Tracker { - a: bool, - b: bool, - } - - world.insert_resource(Tracker { a: false, b: false }); - let entity = world.spawn(A).id(); - - world.add_observer(|_: On, mut tracker: ResMut| { - tracker.a = true; - }); - world.add_observer(|_: On, mut tracker: ResMut| { - tracker.b = true; - }); - - world.entity_mut(entity).remove::<(A, B)>(); - - assert_eq!( - world.resource::(), - &Tracker { - a: true, - // The entity didn't have a B component, so it should not have been triggered. - b: false, - } - ); - } - - #[test] - fn spawned_after_swap_remove() { - #[derive(Component)] - struct Marker; - - let mut world = World::new(); - let id1 = world.spawn(Marker).id(); - let _id2 = world.spawn(Marker).id(); - let id3 = world.spawn(Marker).id(); - - let e1_spawned = world.entity(id1).spawned_by(); - - let spawn = world.entity(id3).spawned_by(); - world.entity_mut(id1).despawn(); - let e1_despawned = world.entities().entity_get_spawned_or_despawned_by(id1); - - // These assertions are only possible if the `track_location` feature is enabled - if let (Some(e1_spawned), Some(e1_despawned)) = - (e1_spawned.into_option(), e1_despawned.into_option()) - { - assert!(e1_despawned.is_some()); - assert_ne!(Some(e1_spawned), e1_despawned); - } - - let spawn_after = world.entity(id3).spawned_by(); - assert_eq!(spawn, spawn_after); - } - - #[test] - fn spawned_by_set_before_flush() { - #[derive(Component)] - #[component(on_despawn = on_despawn)] - struct C; - - fn on_despawn(mut world: DeferredWorld, context: HookContext) { - let spawned = world.entity(context.entity).spawned_by(); - world.commands().queue(move |world: &mut World| { - // The entity has finished despawning... - assert!(world.get_entity(context.entity).is_err()); - let despawned = world - .entities() - .entity_get_spawned_or_despawned_by(context.entity); - // These assertions are only possible if the `track_location` feature is enabled - if let (Some(spawned), Some(despawned)) = - (spawned.into_option(), despawned.into_option()) - { - // ... so ensure that `despawned_by` has been written - assert!(despawned.is_some()); - assert_ne!(Some(spawned), despawned); - } - }); - } - - let mut world = World::new(); - let original = world.spawn(C).id(); - world.despawn(original); - } -} From 2ec21821b85d04dfa9dc09d9cb0b68bc2868b3dd Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 14:30:03 +0100 Subject: [PATCH 53/69] further cleanup --- benches/benches/bevy_ecs/scheduling/run_condition.rs | 4 ---- crates/bevy_ecs/src/system/system_param.rs | 1 - crates/bevy_ecs/src/world/entity_access/entity_ref.rs | 3 +++ crates/bevy_ecs/src/world/entity_access/world_mut.rs | 8 ++++++++ crates/bevy_ecs/src/world/mod.rs | 3 +-- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/benches/benches/bevy_ecs/scheduling/run_condition.rs b/benches/benches/bevy_ecs/scheduling/run_condition.rs index 823bc457c8db5..083ed177d2f7a 100644 --- a/benches/benches/bevy_ecs/scheduling/run_condition.rs +++ b/benches/benches/bevy_ecs/scheduling/run_condition.rs @@ -55,11 +55,7 @@ pub fn run_condition_no(criterion: &mut Criterion) { group.finish(); } -<<<<<<< HEAD -#[derive(Resource)] -======= #[derive(Component)] ->>>>>>> 8253eabe593ce35845212dad9a3281e6b37fe18b struct TestBool(pub bool); pub fn run_condition_yes_with_query(criterion: &mut Criterion) { diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index bfa5ff8c117d9..650fcdd092505 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -21,7 +21,6 @@ use crate::{ use alloc::{borrow::Cow, boxed::Box, vec::Vec}; pub use bevy_ecs_macros::SystemParam; use bevy_platform::cell::SyncCell; -use bevy_ptr::UnsafeCellDeref; use bevy_utils::prelude::DebugName; use core::{ any::Any, diff --git a/crates/bevy_ecs/src/world/entity_access/entity_ref.rs b/crates/bevy_ecs/src/world/entity_access/entity_ref.rs index e65060d115716..b3fb4d0fb5091 100644 --- a/crates/bevy_ecs/src/world/entity_access/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_access/entity_ref.rs @@ -131,6 +131,9 @@ impl<'w> EntityRef<'w> { unsafe { self.cell.get_change_ticks::() } } + /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. + /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. + /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. pub fn get_changed_by(&self) -> Option { // SAFETY: We have read-only access to all components of this entity. unsafe { self.cell.get_changed_by::() } diff --git a/crates/bevy_ecs/src/world/entity_access/world_mut.rs b/crates/bevy_ecs/src/world/entity_access/world_mut.rs index 79e9b7d338359..74226fdf66bea 100644 --- a/crates/bevy_ecs/src/world/entity_access/world_mut.rs +++ b/crates/bevy_ecs/src/world/entity_access/world_mut.rs @@ -611,6 +611,14 @@ impl<'w> EntityWorldMut<'w> { self.as_readonly().get_change_ticks::() } + /// Get the [`MaybeLocation`] from where the given [`Component`] was last changed from. + /// This contains information regarding the last place (in code) that changed this component and can be useful for debugging. + /// For more information, see [`Location`](https://doc.rust-lang.org/nightly/core/panic/struct.Location.html), and enable the `track_location` feature. + /// + /// # Panics + /// + /// If the entity has been despawned while this `EntityWorldMut` is still alive. + #[inline] pub fn get_changed_by(&self) -> Option { self.as_readonly().get_changed_by::() } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c63953fe4d0ac..a67ef67063fd4 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -100,8 +100,7 @@ pub struct World { /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs /// A resource exists if all of the below is true: /// 1. `resource_entities` has an entry for the [`ComponentId`] associated with the resource. - /// 2. The entity associated with the resource has two components: - /// the resource (as a component) and [`IsResource`]. + /// 2. The entity associated with the resource has two components: the resource (as a component) and [`IsResource`]. /// /// If the resource does not exists, none of the above must be true, any inbetween state is invalid. pub resource_entities: HashMap, From ea36fece668973f07549b0abc9f86a67a7d84510 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 18:38:04 +0100 Subject: [PATCH 54/69] more cleanup --- crates/bevy_scene/src/dynamic_scene_builder.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 9eb66a4fdaa42..0bbaa3c84ee16 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -727,13 +727,8 @@ mod tests { #[test] fn should_use_from_reflect() { -<<<<<<< HEAD - #[derive(Resource, Reflect)] - #[reflect(Resource)] -======= #[derive(Component, Reflect)] #[reflect(Component)] ->>>>>>> 8253eabe593ce35845212dad9a3281e6b37fe18b struct SomeType(i32); #[derive(Resource, Reflect)] @@ -768,11 +763,7 @@ mod tests { assert!(resource .try_as_reflect() .expect("resource should be concrete due to `FromReflect`") -<<<<<<< HEAD .is::()); */ -======= - .is::()); ->>>>>>> 8253eabe593ce35845212dad9a3281e6b37fe18b } } From 1203662ebcf3b9f096d09086cc7569d5a50d63d2 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 18:40:38 +0100 Subject: [PATCH 55/69] more more cleanup --- crates/bevy_scene/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 70ac33d85ad97..3d4919a57e2c2 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -49,10 +49,7 @@ use bevy_app::prelude::*; use { bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs, - bevy_ecs::{ - entity_disabling::{DefaultQueryFilters, Internal}, - resource::IsResource, - }, + bevy_ecs::{entity_disabling::DefaultQueryFilters, resource::IsResource}, }; /// Plugin that provides scene functionality to an [`App`]. @@ -69,7 +66,6 @@ impl Plugin for ScenePlugin { .register_type::() .register_type::() .register_type::() - .register_type::() .register_type::() .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); From e751f15025f7fb3d85857c316443ad48f449e921 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 18:55:27 +0100 Subject: [PATCH 56/69] removed a test and made IsResource automatically disabled (temporary) --- crates/bevy_ecs/src/entity_disabling.rs | 3 ++ crates/bevy_ecs/src/system/builder.rs | 43 +------------------ .../bevy_scene/src/dynamic_scene_builder.rs | 1 - 3 files changed, 4 insertions(+), 43 deletions(-) diff --git a/crates/bevy_ecs/src/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 4140cf2a73c4b..fc5e1ee05aed7 100644 --- a/crates/bevy_ecs/src/entity_disabling.rs +++ b/crates/bevy_ecs/src/entity_disabling.rs @@ -99,6 +99,7 @@ use crate::{ component::{ComponentId, Components, StorageType}, query::FilteredAccess, + resource::IsResource, world::{FromWorld, World}, }; use bevy_ecs_macros::{Component, Resource}; @@ -182,7 +183,9 @@ impl FromWorld for DefaultQueryFilters { fn from_world(world: &mut World) -> Self { let mut filters = DefaultQueryFilters::empty(); let disabled_component_id = world.register_component::(); + let is_resource_id = world.register_component::(); filters.register_disabling_component(disabled_component_id); + filters.register_disabling_component(is_resource_id); filters } } diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 937911ca834c1..ece720063893c 100644 --- a/crates/bevy_ecs/src/system/builder.rs +++ b/crates/bevy_ecs/src/system/builder.rs @@ -626,7 +626,7 @@ mod tests { system::{Local, RunSystemOnce}, }; use alloc::vec; - use bevy_reflect::{FromType, Reflect, ReflectRef}; + use bevy_reflect::Reflect; use super::*; @@ -1011,45 +1011,4 @@ mod tests { .build_state(&mut world) .build_system(|_r: Res, _fr: FilteredResourcesMut| {}); } - - #[test] - #[should_panic] - fn filtered_resource_mut_conflicts_write_with_resmut() { - let mut world = World::new(); - ( - ParamBuilder::resource_mut(), - FilteredResourcesMutParamBuilder::new(|builder| { - builder.add_write::(); - }), - ) - .build_state(&mut world) - .build_system(|_r: ResMut, _fr: FilteredResourcesMut| {}); - } - - #[test] - fn filtered_resource_reflect() { - let mut world = World::new(); - world.insert_resource(R { foo: 7 }); - - let system = (FilteredResourcesParamBuilder::new(|builder| { - builder.add_read::(); - }),) - .build_state(&mut world) - .build_system(|res: FilteredResources| { - let reflect_resource = >::from_type(); - let ReflectRef::Struct(reflect_struct) = - reflect_resource.reflect(res).unwrap().reflect_ref() - else { - panic!() - }; - *reflect_struct - .field("foo") - .unwrap() - .try_downcast_ref::() - .unwrap() - }); - - let output = world.run_system_once(system).unwrap(); - assert_eq!(output, 7); - } } diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 0bbaa3c84ee16..2c0401d05f9bb 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -417,7 +417,6 @@ impl<'w> DynamicSceneBuilder<'w> { mod tests { use bevy_ecs::{ component::Component, - entity_disabling::Internal, prelude::{Entity, Resource}, query::With, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, From 2fa538e718e13b99dfe18ec4ae02cd8e96546948 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 20:57:10 +0100 Subject: [PATCH 57/69] rewrite resource component derive --- crates/bevy_ecs/macros/src/component.rs | 107 +++++++++++++++++- crates/bevy_ecs/src/resource.rs | 70 +++++++++++- crates/bevy_ecs/src/world/mod.rs | 47 ++++---- .../bevy_ecs/src/world/unsafe_world_cell.rs | 21 ++-- 4 files changed, 200 insertions(+), 45 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 712e2ddcfbf79..e27c06b9e7a8f 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -14,11 +14,6 @@ use syn::{ }; pub fn derive_resource(input: TokenStream) -> TokenStream { - // The resource derive *also* implements the Component trait - // We generate the Component implementation first, then add the Resource implementation, - // so then we can pick up all of the attributes from the Component derive - let component_derive_token_stream = derive_component(input.clone()); - let mut ast = parse_macro_input!(input as DeriveInput); let bevy_ecs_path: Path = crate::bevy_ecs_path(); @@ -37,14 +32,94 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { return err.into_compile_error().into(); } + // implement the Component trait + let attrs = match parse_resource_attr(&ast) { + Ok(attrs) => attrs, + Err(e) => return e.into_compile_error().into(), + }; + + let map_entities = map_entities( + &ast.data, + &bevy_ecs_path, + Ident::new("this", Span::call_site()), + false, + false, + attrs.map_entities + ).map(|map_entities_impl| quote! { + fn map_entities(this: &mut Self, mapper: &mut M) { + use #bevy_ecs_path::entity::MapEntities; + #map_entities_impl + } + }); + + let storage = storage_path(&bevy_ecs_path, StorageTy::Table); + + let on_add_path = Some(quote!(#bevy_ecs_path::resource::on_add_hook)); + let on_remove_path = Some(quote!(#bevy_ecs_path::resource::on_remove_hook)); + let on_insert_path = None; + let on_replace_path = None; + let on_despawn_path = None; + + let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path); + let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path); + let on_insert = hook_register_function_call(&bevy_ecs_path, quote! {on_insert}, on_insert_path); + let on_replace = + hook_register_function_call(&bevy_ecs_path, quote! {on_replace}, on_replace_path); + let on_despawn = + hook_register_function_call(&bevy_ecs_path, quote! {on_despawn}, on_despawn_path); + ast.generics .make_where_clause() .predicates .push(parse_quote! { Self: Send + Sync + 'static }); + let mut register_required = Vec::with_capacity(1); + register_required.push(quote! { + required_components.register_required::<#bevy_ecs_path::resource::IsResource>(<#bevy_ecs_path::resource::IsResource as Default>::default); + }); + let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); + let clone_behavior = quote!( + use #bevy_ecs_path::relationship::{ + RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect, + RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy + }; + (&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::::default()).default_clone_behavior() + ); + + // This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top + // level components are initialized first, giving them precedence over recursively defined constructors for the same component type + let component_derive_token_stream = TokenStream::from(quote! { + impl #impl_generics #bevy_ecs_path::component::Component for #struct_name #type_generics #where_clause { + const STORAGE_TYPE: #bevy_ecs_path::component::StorageType = #storage; + type Mutability = #bevy_ecs_path::component::Mutable; + fn register_required_components( + _requiree: #bevy_ecs_path::component::ComponentId, + required_components: &mut #bevy_ecs_path::component::RequiredComponentsRegistrator, + ) { + #(#register_required)* + } + + #on_add + #on_insert + #on_replace + #on_remove + #on_despawn + + fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior { + #clone_behavior + } + + #map_entities + + fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor> { + None + } + } + }); + let resource_impl_token_stream = TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause { } @@ -386,6 +461,7 @@ pub(crate) fn map_entities( } pub const COMPONENT: &str = "component"; +pub const RESOURCE: &str = "resource"; pub const STORAGE: &str = "storage"; pub const REQUIRE: &str = "require"; pub const RELATIONSHIP: &str = "relationship"; @@ -638,6 +714,27 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { Ok(attrs) } +struct ResourceAttrs { + map_entities: Option, +} + +fn parse_resource_attr(ast: &DeriveInput) -> Result { + let mut attrs = ResourceAttrs { map_entities: None }; + for attr in ast.attrs.iter() { + if attr.path().is_ident(RESOURCE) { + attr.parse_nested_meta(|nested| { + if nested.path.is_ident(MAP_ENTITIES) { + attrs.map_entities = Some(nested.input.parse::()?); + Ok(()) + } else { + Err(nested.error("Unsupported attribute")) + } + })?; + } + } + Ok(attrs) +} + impl Parse for Require { fn parse(input: syn::parse::ParseStream) -> Result { let mut path = input.parse::()?; diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index da3db289f295d..b93a3fb52c698 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -1,12 +1,20 @@ //! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World). -use crate::component::Mutable; -use crate::prelude::Component; -use crate::prelude::ReflectComponent; +use core::ops::{Deref, DerefMut}; + +use crate::{ + component::{Component, ComponentId, Mutable}, + entity::Entity, + lifecycle::HookContext, + reflect::ReflectComponent, + storage::SparseSet, + world::DeferredWorld, +}; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; // The derive macro for the `Resource` trait pub use bevy_ecs_macros::Resource; +use bevy_platform::cell::SyncUnsafeCell; /// A type that can be inserted into a [`World`] as a singleton. /// @@ -79,6 +87,62 @@ pub use bevy_ecs_macros::Resource; )] pub trait Resource: Component {} +/// A cache that links each `ComponentId` from a resource to the corresponding entity. +#[derive(Default)] +pub struct ResourceCache(SyncUnsafeCell>); + +impl Deref for ResourceCache { + type Target = SparseSet; + + fn deref(&self) -> &Self::Target { + // SAFETY: There are no other mutable references to the map. + // The underlying `SyncUnsafeCell` is never exposed outside this module, + // so mutable references are only created by the resource hooks. + // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), + // and that would conflict with the `DeferredWorld` passed to the resource hook. + unsafe { &*self.0.get() } + } +} + +impl DerefMut for ResourceCache { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.get_mut() + } +} + +pub(crate) fn on_add_hook(mut deferred_world: DeferredWorld, context: HookContext) { + let world = deferred_world.deref(); + + if let Some(&offending_entity) = world.resource_entities.get(context.component_id) + && offending_entity != context.entity + { + // the resource already exists and we need to overwrite it + deferred_world.commands().entity(offending_entity).despawn(); + } + + // SAFETY: We have exclusive world access (as long as we don't make structural changes). + let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; + // SAFETY: There are no shared references to the map. + // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), + // and that would conflict with the `DeferredWorld` passed to the resource hook. + unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); +} + +pub(crate) fn on_remove_hook(mut deferred_world: DeferredWorld, context: HookContext) { + let world = deferred_world.deref(); + // If the resource is already linked to a new (different) entity, we don't remove it. + if let Some(entity) = world.resource_entities.get(context.component_id) + && *entity == context.entity + { + // SAFETY: We have exclusive world access (as long as we don't make structural changes). + let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; + // SAFETY: There are no shared references to the map. + // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), + // and that would conflict with the `DeferredWorld` passed to the resource hook. + unsafe { &mut *cache.0.get() }.remove(context.component_id); + } +} + /// A marker component for entities which store resources. #[cfg_attr( feature = "bevy_reflect", diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a67ef67063fd4..c997c685cb05a 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -52,7 +52,7 @@ use crate::{ prelude::{Add, Despawn, DetectChangesMut, Insert, Remove, Replace, Without}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, - resource::{IsResource, Resource}, + resource::{IsResource, Resource, ResourceCache}, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, system::Commands, @@ -64,7 +64,6 @@ use crate::{ }, }; use alloc::{boxed::Box, vec::Vec}; -use bevy_platform::collections::HashMap; use bevy_platform::sync::atomic::{AtomicU32, Ordering}; use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr}; use bevy_utils::prelude::DebugName; @@ -96,14 +95,7 @@ pub struct World { pub(crate) entities: Entities, pub(crate) components: Components, pub(crate) component_ids: ComponentIds, - /// A lookup for the entities on which resources are stored. - /// It uses `ComponentId`s instead of `TypeId`s for untyped APIs - /// A resource exists if all of the below is true: - /// 1. `resource_entities` has an entry for the [`ComponentId`] associated with the resource. - /// 2. The entity associated with the resource has two components: the resource (as a component) and [`IsResource`]. - /// - /// If the resource does not exists, none of the above must be true, any inbetween state is invalid. - pub resource_entities: HashMap, + pub(crate) resource_entities: ResourceCache, pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, @@ -1716,7 +1708,7 @@ impl World { let caller = MaybeLocation::caller(); let component_id = self.components_registrator().register_resource::(); - if !self.resource_entities.contains_key(&component_id) { + if !self.resource_entities.contains(component_id) { let value = R::from_world(self); move_as_ptr!(value); @@ -1751,7 +1743,7 @@ impl World { ) { let component_id = self.components_registrator().register_resource::(); - if !self.resource_entities.contains_key(&component_id) { + if !self.resource_entities.contains(component_id) { // the resource doesn't exist, so we make a new one. let resource_bundle = (value, IsResource); move_as_ptr!(resource_bundle); @@ -1759,7 +1751,7 @@ impl World { let entity = self.spawn_with_caller(resource_bundle, caller).id(); self.resource_entities.insert(component_id, entity); } else { - let entity = self.resource_entities.get(&component_id).unwrap(); + let entity = self.resource_entities.get(component_id).unwrap(); move_as_ptr!(value); if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { entity_mut.insert_with_caller( @@ -1836,11 +1828,11 @@ impl World { #[inline] pub fn remove_resource(&mut self) -> Option { let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let entity = self.resource_entities.remove(&component_id)?; + let entity = self.resource_entities.remove(component_id)?; let mut entity_ref = self.get_entity_mut(entity).ok()?; let value = entity_ref.take::()?; entity_ref.despawn(); - self.resource_entities.remove(&component_id); + self.resource_entities.remove(component_id); Some(value) } @@ -1878,7 +1870,7 @@ impl World { /// Returns `true` if a resource with provided `component_id` exists. Otherwise returns `false`. #[inline] pub fn contains_resource_by_id(&self, component_id: ComponentId) -> bool { - if let Some(entity) = self.resource_entities.get(&component_id) + if let Some(entity) = self.resource_entities.get(component_id) && let Ok(entity_ref) = self.get_entity(*entity) { return entity_ref.contains_id(component_id) && entity_ref.contains::(); @@ -1968,7 +1960,7 @@ impl World { &self, component_id: ComponentId, ) -> Option { - let entity = self.resource_entities.get(&component_id)?; + let entity = self.resource_entities.get(component_id)?; let entity_ref = self.get_entity(*entity).ok()?; entity_ref.get_change_ticks_by_id(component_id) } @@ -2098,11 +2090,11 @@ impl World { if !self.contains_resource_by_id(component_id) { let value = func(); - if let Some(entity) = self.resource_entities.get(&component_id) { + if let Some(entity) = self.resource_entities.get(component_id) { if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { entity_mut.insert(value); } else { - self.resource_entities.remove(&component_id); + self.resource_entities.remove(component_id); self.insert_resource_with_caller(value, caller); } } else { @@ -2596,7 +2588,7 @@ impl World { let change_tick = self.change_tick(); let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let entity = self.resource_entities.get(&component_id)?; + let entity = self.resource_entities.get(component_id)?; let mut entity_mut = self.get_entity_mut(*entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; @@ -2616,7 +2608,7 @@ impl World { // to fully remove the resource, we remove the entity and the resource_entities entry entity_mut.despawn(); - self.resource_entities.remove(&component_id); + self.resource_entities.remove(component_id); // There may be commands that are registered by the closure, which interact with the resource. // In make sure that the resource is available, we avoid flushing until the resource is fully inserted back. @@ -2732,7 +2724,7 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { - if !self.resource_entities.contains_key(&component_id) { + if !self.resource_entities.contains(component_id) { let is_resource = IsResource; move_as_ptr!(is_resource); @@ -3282,10 +3274,10 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { - let component_ids: Vec = self.resource_entities.keys().copied().collect(); + let component_ids = self.resource_entities.indices(); component_ids.into_iter().filter_map(|component_id| { - let component_info = self.components().get_info(component_id)?; - let resource = self.get_resource_by_id(component_id)?; + let component_info = self.components().get_info(*component_id)?; + let resource = self.get_resource_by_id(*component_id)?; Some((component_info, resource)) }) } @@ -3357,7 +3349,8 @@ impl World { /// ``` pub fn iter_resources_mut(&mut self) -> impl Iterator)> { let unsafe_world = self.as_unsafe_world_cell(); - let resource_entities = unsafe_world.resource_entities(); + // SAFETY: exclusive world access to all resources + let resource_entities = unsafe { unsafe_world.resource_entities() }; let components = unsafe_world.components(); resource_entities @@ -3427,7 +3420,7 @@ impl World { /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only /// use this in cases where the actual types are not known at compile time.** pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> Option<()> { - if let Some(entity) = self.resource_entities.remove(&component_id) { + if let Some(entity) = self.resource_entities.remove(component_id) { self.despawn(entity).then_some::<()>(()) } else { None diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index c222ad10573e0..7bac8687bf1a1 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -15,11 +15,10 @@ use crate::{ observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReleaseStateQueryData}, - resource::Resource, + resource::{Resource, ResourceCache}, storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, }; -use bevy_platform::collections::HashMap; use bevy_platform::sync::atomic::Ordering; use bevy_ptr::{Ptr, UnsafeCellDeref}; use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr}; @@ -280,8 +279,11 @@ impl<'w> UnsafeWorldCell<'w> { } /// Retrieves this world's resource-entity map. + /// + /// # Safety + /// The caller must have exclusive read or write access to the resources that are updated in the cache. #[inline] - pub fn resource_entities(self) -> &'w HashMap { + pub unsafe fn resource_entities(self) -> &'w ResourceCache { // SAFETY: // - we only access world metadata &unsafe { self.world_metadata() }.resource_entities @@ -461,10 +463,9 @@ impl<'w> UnsafeWorldCell<'w> { /// - no mutable reference to the resource exists at the same time #[inline] pub unsafe fn get_resource_by_id(self, component_id: ComponentId) -> Option> { - let entity = self.resource_entities().get(&component_id)?; + // SAFETY: We have permission to access the resource of `component_id`. + let entity = unsafe { self.resource_entities() }.get(component_id)?; let entity_cell = self.get_entity(*entity).ok()?; - // SAFETY: caller ensures that `self` has permission to access `R` - // caller ensures that no mutable reference exists to `R` entity_cell.get_by_id(component_id) } @@ -547,10 +548,9 @@ impl<'w> UnsafeWorldCell<'w> { component_id: ComponentId, ) -> Option> { self.assert_allows_mutable_access(); - let entity = self.resource_entities().get(&component_id)?; + // SAFETY: We have permission to access the resource of `component_id`. + let entity = unsafe { self.resource_entities() }.get(component_id)?; let entity_cell = self.get_entity(*entity).ok()?; - // SAFETY: we only access data that the caller has ensured is unaliased and `self` - // has permission to access. entity_cell.get_mut_by_id(component_id).ok() } @@ -625,7 +625,8 @@ impl<'w> UnsafeWorldCell<'w> { self, component_id: ComponentId, ) -> Option<(Ptr<'w>, ComponentTickCells<'w>)> { - let entity = self.resource_entities().get(&component_id)?; + // SAFETY: We have permission to access the resource of `component_id`. + let entity = unsafe { self.resource_entities() }.get(component_id)?; let storage_type = self.components().get_info(component_id)?.storage_type(); let location = self.get_entity(*entity).ok()?.location(); // SAFETY: From 740752d8d9cb88fdb2de6727744c9cc33d849306 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Sun, 26 Oct 2025 21:06:47 +0100 Subject: [PATCH 58/69] change the hooks to be public under the Resource trait --- crates/bevy_ecs/macros/src/component.rs | 4 +- crates/bevy_ecs/src/resource.rs | 70 +++++++++++++------------ 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index e27c06b9e7a8f..0c6e63e983f3b 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -54,8 +54,8 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { let storage = storage_path(&bevy_ecs_path, StorageTy::Table); - let on_add_path = Some(quote!(#bevy_ecs_path::resource::on_add_hook)); - let on_remove_path = Some(quote!(#bevy_ecs_path::resource::on_remove_hook)); + let on_add_path = Some(quote!(::on_add_hook)); + let on_remove_path = Some(quote!(::on_remove_hook)); let on_insert_path = None; let on_replace_path = None; let on_despawn_path = None; diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index b93a3fb52c698..bb5e91520d5f9 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -85,7 +85,42 @@ use bevy_platform::cell::SyncUnsafeCell; label = "invalid `Resource`", note = "consider annotating `{Self}` with `#[derive(Resource)]`" )] -pub trait Resource: Component {} +pub trait Resource: Component { + /// The `on_add` component hook that maintains the uniqueness property of a resource. + fn on_add_hook(mut deferred_world: DeferredWorld, context: HookContext) { + let world = deferred_world.deref(); + + if let Some(&offending_entity) = world.resource_entities.get(context.component_id) + && offending_entity != context.entity + { + // the resource already exists and we need to overwrite it + deferred_world.commands().entity(offending_entity).despawn(); + } + + // SAFETY: We have exclusive world access (as long as we don't make structural changes). + let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; + // SAFETY: There are no shared references to the map. + // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), + // and that would conflict with the `DeferredWorld` passed to the resource hook. + unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); + } + + /// The `on_remove` component hook that maintains the uniqueness property of a resource. + fn on_remove_hook(mut deferred_world: DeferredWorld, context: HookContext) { + let world = deferred_world.deref(); + // If the resource is already linked to a new (different) entity, we don't remove it. + if let Some(entity) = world.resource_entities.get(context.component_id) + && *entity == context.entity + { + // SAFETY: We have exclusive world access (as long as we don't make structural changes). + let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; + // SAFETY: There are no shared references to the map. + // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), + // and that would conflict with the `DeferredWorld` passed to the resource hook. + unsafe { &mut *cache.0.get() }.remove(context.component_id); + } + } +} /// A cache that links each `ComponentId` from a resource to the corresponding entity. #[derive(Default)] @@ -110,39 +145,6 @@ impl DerefMut for ResourceCache { } } -pub(crate) fn on_add_hook(mut deferred_world: DeferredWorld, context: HookContext) { - let world = deferred_world.deref(); - - if let Some(&offending_entity) = world.resource_entities.get(context.component_id) - && offending_entity != context.entity - { - // the resource already exists and we need to overwrite it - deferred_world.commands().entity(offending_entity).despawn(); - } - - // SAFETY: We have exclusive world access (as long as we don't make structural changes). - let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; - // SAFETY: There are no shared references to the map. - // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), - // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); -} - -pub(crate) fn on_remove_hook(mut deferred_world: DeferredWorld, context: HookContext) { - let world = deferred_world.deref(); - // If the resource is already linked to a new (different) entity, we don't remove it. - if let Some(entity) = world.resource_entities.get(context.component_id) - && *entity == context.entity - { - // SAFETY: We have exclusive world access (as long as we don't make structural changes). - let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; - // SAFETY: There are no shared references to the map. - // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), - // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &mut *cache.0.get() }.remove(context.component_id); - } -} - /// A marker component for entities which store resources. #[cfg_attr( feature = "bevy_reflect", From 737f04c408eebd62d7d2be8fb23eb2f697022b87 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Oct 2025 00:14:48 +0100 Subject: [PATCH 59/69] fixed compile problems, goto v2 implementation with hooks --- crates/bevy_ecs/macros/src/component.rs | 30 +-- crates/bevy_ecs/macros/src/lib.rs | 5 +- crates/bevy_ecs/src/world/mod.rs | 194 ++++++++---------- crates/bevy_scene/src/dynamic_scene.rs | 5 +- .../bevy_scene/src/dynamic_scene_builder.rs | 8 +- crates/bevy_scene/src/scene.rs | 2 +- 6 files changed, 99 insertions(+), 145 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 0c6e63e983f3b..6ef8116f68b83 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -33,18 +33,13 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { } // implement the Component trait - let attrs = match parse_resource_attr(&ast) { - Ok(attrs) => attrs, - Err(e) => return e.into_compile_error().into(), - }; - let map_entities = map_entities( &ast.data, &bevy_ecs_path, Ident::new("this", Span::call_site()), false, false, - attrs.map_entities + None ).map(|map_entities_impl| quote! { fn map_entities(this: &mut Self, mapper: &mut M) { use #bevy_ecs_path::entity::MapEntities; @@ -120,6 +115,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { } }); + // implement the Resource trait let resource_impl_token_stream = TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause { } @@ -461,7 +457,6 @@ pub(crate) fn map_entities( } pub const COMPONENT: &str = "component"; -pub const RESOURCE: &str = "resource"; pub const STORAGE: &str = "storage"; pub const REQUIRE: &str = "require"; pub const RELATIONSHIP: &str = "relationship"; @@ -714,27 +709,6 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { Ok(attrs) } -struct ResourceAttrs { - map_entities: Option, -} - -fn parse_resource_attr(ast: &DeriveInput) -> Result { - let mut attrs = ResourceAttrs { map_entities: None }; - for attr in ast.attrs.iter() { - if attr.path().is_ident(RESOURCE) { - attr.parse_nested_meta(|nested| { - if nested.path.is_ident(MAP_ENTITIES) { - attrs.map_entities = Some(nested.input.parse::()?); - Ok(()) - } else { - Err(nested.error("Unsupported attribute")) - } - })?; - } - } - Ok(attrs) -} - impl Parse for Require { fn parse(input: syn::parse::ParseStream) -> Result { let mut path = input.parse::()?; diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 99c70a8919669..1f225b1f9e335 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -600,10 +600,7 @@ pub fn derive_message(input: TokenStream) -> TokenStream { } /// Implement the `Resource` trait. -#[proc_macro_derive( - Resource, - attributes(component, require, relationship, relationship_target, entities) -)] +#[proc_macro_derive(Resource)] pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index c997c685cb05a..18932a1646526 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -32,8 +32,8 @@ pub use spawn_batch::*; use crate::{ archetype::{ArchetypeId, Archetypes}, bundle::{ - Bundle, BundleId, BundleInfo, BundleInserter, BundleSpawner, Bundles, InsertMode, - NoBundleEffect, + Bundle, BundleId, BundleInfo, BundleInserter, BundleSpawner, Bundles, DynamicBundle, + InsertMode, NoBundleEffect, }, change_detection::{ CheckChangeTicks, ComponentTicks, ComponentTicksMut, MaybeLocation, MutUntyped, Tick, @@ -229,6 +229,12 @@ impl World { &self.components } + /// Retrieves this world's [`ResourceCache`]. + #[inline] + pub fn resource_entities(&self) -> &ResourceCache { + &self.resource_entities + } + /// Prepares a [`ComponentsQueuedRegistrator`] for the world. /// **NOTE:** [`ComponentsQueuedRegistrator`] is easily misused. /// See its docs for important notes on when and how it should be used. @@ -1705,21 +1711,17 @@ impl World { #[inline] #[track_caller] pub fn init_resource(&mut self) -> ComponentId { - let caller = MaybeLocation::caller(); - let component_id = self.components_registrator().register_resource::(); - - if !self.resource_entities.contains(component_id) { - let value = R::from_world(self); - move_as_ptr!(value); - - let entity = self - .spawn_with_caller(value, caller) - .insert(IsResource) - .id(); - self.resource_entities.insert(component_id, entity); + // The default component hook behavior for adding an already existing reasource is to replace it. + // We don't want that here. + if self.contains_resource::() { + return self.resource_id::().unwrap(); // must exist } - component_id + let caller = MaybeLocation::caller(); + let resource = R::from_world(self); + move_as_ptr!(resource); + self.spawn_with_caller(resource, caller); + self.resource_id::().unwrap() // must exist } /// Inserts a new resource with the given `value`. @@ -1741,31 +1743,20 @@ impl World { value: R, caller: MaybeLocation, ) { - let component_id = self.components_registrator().register_resource::(); - - if !self.resource_entities.contains(component_id) { - // the resource doesn't exist, so we make a new one. - let resource_bundle = (value, IsResource); - move_as_ptr!(resource_bundle); - - let entity = self.spawn_with_caller(resource_bundle, caller).id(); - self.resource_entities.insert(component_id, entity); + move_as_ptr!(value); + // if the resource already exists, we replace it on the same entity + if let Some(component_id) = self.resource_id::() + && let Some(entity) = self.resource_entities.get(component_id) + && let Ok(mut entity_mut) = self.get_entity_mut(*entity) + { + entity_mut.insert_with_caller( + value, + InsertMode::Replace, + caller, + RelationshipHookMode::Run, + ); } else { - let entity = self.resource_entities.get(component_id).unwrap(); - move_as_ptr!(value); - if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { - entity_mut.insert_with_caller( - value, - InsertMode::Replace, - caller, - RelationshipHookMode::Run, - ); - } else { - panic!( - "Resource {} is registered in `resource_entities` but the associated entity does not exist.", - DebugName::type_name::() - ); - } + self.spawn_with_caller(value, caller); } } @@ -1827,12 +1818,10 @@ impl World { /// Removes the resource of a given type and returns it, if it exists. Otherwise returns `None`. #[inline] pub fn remove_resource(&mut self) -> Option { - let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let entity = self.resource_entities.remove(component_id)?; - let mut entity_ref = self.get_entity_mut(entity).ok()?; - let value = entity_ref.take::()?; - entity_ref.despawn(); - self.resource_entities.remove(component_id); + let component_id = self.resource_id::()?; + let entity = *self.resource_entities.get(component_id)?; + let value = self.get_entity_mut(entity).ok()?.take::()?; + self.despawn(entity); Some(value) } @@ -2085,24 +2074,10 @@ impl World { &mut self, func: impl FnOnce() -> R, ) -> Mut<'_, R> { - let caller = MaybeLocation::caller(); - let component_id = self.components_registrator().register_resource::(); - - if !self.contains_resource_by_id(component_id) { - let value = func(); - if let Some(entity) = self.resource_entities.get(component_id) { - if let Ok(mut entity_mut) = self.get_entity_mut(*entity) { - entity_mut.insert(value); - } else { - self.resource_entities.remove(component_id); - self.insert_resource_with_caller(value, caller); - } - } else { - self.insert_resource_with_caller(value, caller); - } + if !self.contains_resource::() { + self.insert_resource_with_caller(func(), MaybeLocation::caller()); } - // SAFETY: If it didn't exist, we've just created the resource. - unsafe { self.get_resource_mut::().debug_checked_unwrap() } + self.get_resource_mut::().unwrap() // must exist } /// Gets a mutable reference to the resource of type `T` if it exists, @@ -2139,14 +2114,11 @@ impl World { /// ``` #[track_caller] pub fn get_resource_or_init(&mut self) -> Mut<'_, R> { - let caller = MaybeLocation::caller(); - if !self.contains_resource::() { - let value = R::from_world(self); - self.insert_resource_with_caller(value, caller); + self.init_resource::(); } - // SAFETY: The resource either exists or we've just created it. - unsafe { self.get_resource_mut::().debug_checked_unwrap() } + + self.get_resource_mut::().unwrap() // must exist } /// Gets an immutable reference to the non-send resource of the given type, if it exists. @@ -2587,9 +2559,9 @@ impl World { let last_change_tick = self.last_change_tick(); let change_tick = self.change_tick(); - let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let entity = self.resource_entities.get(component_id)?; - let mut entity_mut = self.get_entity_mut(*entity).ok()?; + let component_id = self.components.valid_resource_id::()?; + let entity = *self.resource_entities.get(component_id)?; + let mut entity_mut = self.get_entity_mut(entity).ok()?; let mut ticks = entity_mut.get_change_ticks::()?; let mut changed_by = entity_mut.get_changed_by::()?; @@ -2606,16 +2578,6 @@ impl World { }, }; - // to fully remove the resource, we remove the entity and the resource_entities entry - entity_mut.despawn(); - self.resource_entities.remove(component_id); - - // There may be commands that are registered by the closure, which interact with the resource. - // In make sure that the resource is available, we avoid flushing until the resource is fully inserted back. - // Since spawn calls flush, we do it before calling the closure, and check afterwards that it was not removed. - // world.flush() is now called at the end of `insert_with_caller()`. - let entity = self.spawn(IsResource).id(); - let result = f(self, value_mut); assert!(!self.contains_resource::(), "Resource `{}` was inserted during a call to World::resource_scope.\n\ @@ -2628,19 +2590,49 @@ impl World { move_as_ptr!(value); - entity_mut.insert_with_caller( - value, - InsertMode::Replace, - changed_by, - RelationshipHookMode::Skip, - ); + // See EntityWorldMut::insert_with_caller for the original code. + // This is copied here to update the change ticks. This way we can ensure that the commands + // ran during self.flush(), interact with the correct ticks on the resource component. + { + let location = entity_mut.location(); + let mut bundle_inserter = BundleInserter::new::( + // SAFETY: We update the entity location like in EntityWorldMut::insert_with_caller + unsafe { entity_mut.world_mut() }, + location.archetype_id, + ticks.changed, + ); + // SAFETY: + // - `location` matches current entity and thus must currently exist in the source + // archetype for this inserter and its location within the archetype. + // - `T` matches the type used to create the `BundleInserter`. + // - `apply_effect` is called exactly once after this function. + // - The value pointed at by `bundle` is not accessed for anything other than `apply_effect` + // and the caller ensures that the value is not accessed or dropped after this function + // returns. + let (bundle, _) = value.partial_move(|bundle| unsafe { + bundle_inserter.insert( + entity, + location, + bundle, + InsertMode::Replace, + changed_by, + RelationshipHookMode::Run, + ) + }); + entity_mut.update_location(); - // Update ticks - entity_mut.get_mut::()?.set_last_added(ticks.added); - entity_mut.get_mut::()?.set_last_changed(ticks.changed); + // set the added tick to the original + entity_mut.get_mut::()?.set_last_added(ticks.added); - let entity = entity_mut.id(); - self.resource_entities.insert(component_id, entity); + // SAFETY: We update the entity location afterwards. + unsafe { entity_mut.world_mut() }.flush(); + + entity_mut.update_location(); + // SAFETY: + // - This is called exactly once after the `BundleInsert::insert` call before returning to safe code. + // - `bundle` points to the same `B` that `BundleInsert::insert` was called on. + unsafe { R::apply_effect(bundle, &mut entity_mut) }; + } Some(result) } @@ -2724,17 +2716,13 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { - if !self.resource_entities.contains(component_id) { - let is_resource = IsResource; - move_as_ptr!(is_resource); - - // Since we don't know the type, we use a placeholder type. - let entity = self - .spawn_with_caller(is_resource, caller) - .insert_by_id(component_id, value) - .id(); - self.resource_entities.insert(component_id, entity); - } + self.spawn_empty().insert_by_id_with_caller( + component_id, + value, + InsertMode::Replace, + caller, + RelationshipHookMode::Run, + ); } /// Inserts a new `!Send` resource with the given `value`. Will replace the value if it already diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index ab60a137d3eb1..458ae95871eb4 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -119,9 +119,6 @@ impl DynamicScene { { let component_id = reflect_component.register_component(world); - // Really, really, really, hacky, but it does work, which is funny - world.resource_entities.insert(component_id, entity); - // SAFETY: we registered the component above. the info exists #[expect(unsafe_code, reason = "this is faster")] let component_info = @@ -206,7 +203,7 @@ mod tests { use crate::dynamic_scene_builder::DynamicSceneBuilder; #[derive(Resource, Reflect, MapEntities, Debug)] - #[reflect(Resource, MapEntities)] + #[reflect(Resource)] struct TestResource { #[entities] entity_a: Entity, diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 2c0401d05f9bb..a99c498db0703 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -358,7 +358,7 @@ impl<'w> DynamicSceneBuilder<'w> { let type_registry = self.original_world.resource::().read(); - for (resource_id, entity) in self.original_world.resource_entities.iter() { + for (resource_id, entity) in self.original_world.resource_entities().iter() { if (Some(*resource_id) == original_world_dqf_id) || (Some(*resource_id) == original_world_atr_id) { @@ -590,7 +590,6 @@ mod tests { let atr = AppTypeRegistry::default(); atr.write().register::(); atr.write().register::(); - atr.write().register::(); world.insert_resource(atr); world.insert_resource(ResourceA); @@ -601,9 +600,8 @@ mod tests { assert_eq!(scene.resources.len(), 1); assert_eq!(scene.resources[0].components.len(), 3); - assert!(scene.resources[0].components[0].represents::()); - assert!(scene.resources[0].components[1].represents::()); - assert!(scene.resources[0].components[2].represents::()); + assert!(scene.resources[0].components[0].represents::()); + assert!(scene.resources[0].components[1].represents::()); } #[test] diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 2658d7b6180a4..fe358718c1122 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -71,7 +71,7 @@ impl Scene { .get_resource_id(TypeId::of::()); // Resources archetype - for (component_id, entity) in self.world.resource_entities.iter() { + for (component_id, entity) in self.world.resource_entities().iter() { if Some(*component_id) == self_dqf_id { continue; } From 8a1727b105ffa1ae67e635cf9ab1000923a54a67 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Oct 2025 11:58:13 +0100 Subject: [PATCH 60/69] deprecate dyanamic (send) resources --- crates/bevy_ecs/src/component/info.rs | 22 ++-------- crates/bevy_ecs/src/component/register.rs | 45 +++++--------------- crates/bevy_ecs/src/world/mod.rs | 50 +++++------------------ 3 files changed, 26 insertions(+), 91 deletions(-) diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 949000faf95a9..b18f405332eb1 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -302,19 +302,7 @@ impl ComponentDescriptor { /// /// The [`StorageType`] for resources is always [`StorageType::Table`]. pub fn new_resource() -> Self { - Self { - name: DebugName::type_name::(), - // PERF: `SparseStorage` may actually be a more - // reasonable choice as `storage_type` for resources. - storage_type: StorageType::Table, - is_send_and_sync: true, - type_id: Some(TypeId::of::()), - layout: Layout::new::(), - drop: needs_drop::().then_some(Self::drop_ptr:: as _), - mutable: true, - clone_behavior: ComponentCloneBehavior::Default, - relationship_accessor: None, - } + Self::new::() } pub(super) fn new_non_send(storage_type: StorageType) -> Self { @@ -406,7 +394,7 @@ impl Components { #[inline] pub fn num_queued(&self) -> usize { let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner); - queued.components.len() + queued.dynamic_registrations.len() + queued.resources.len() + queued.components.len() + queued.dynamic_registrations.len() } /// Returns `true` if there are any components registered with this instance. Otherwise, this returns `false`. @@ -422,7 +410,7 @@ impl Components { .queued .get_mut() .unwrap_or_else(PoisonError::into_inner); - queued.components.len() + queued.dynamic_registrations.len() + queued.resources.len() + queued.components.len() + queued.dynamic_registrations.len() } /// A faster version of [`Self::any_queued`]. @@ -469,7 +457,6 @@ impl Components { queued .components .values() - .chain(queued.resources.values()) .chain(queued.dynamic_registrations.iter()) .find(|queued| queued.id == id) .map(|queued| Cow::Owned(queued.descriptor.clone())) @@ -491,7 +478,6 @@ impl Components { queued .components .values() - .chain(queued.resources.values()) .chain(queued.dynamic_registrations.iter()) .find(|queued| queued.id == id) .map(|queued| queued.descriptor.name.clone()) @@ -683,7 +669,7 @@ impl Components { self.queued .read() .unwrap_or_else(PoisonError::into_inner) - .resources + .components .get(&type_id) .map(|queued| queued.id) }) diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index a35f8e223546d..7c365e184c79f 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -138,21 +138,6 @@ impl<'w> ComponentsRegistrator<'w> { registrator.register(self); } - // resources - while let Some(registrator) = { - let queued = self - .components - .queued - .get_mut() - .unwrap_or_else(PoisonError::into_inner); - queued.resources.keys().next().copied().map(|type_id| { - // SAFETY: the id just came from a valid iterator. - unsafe { queued.resources.remove(&type_id).debug_checked_unwrap() } - }) - } { - registrator.register(self); - } - // dynamic let queued = &mut self .components @@ -289,12 +274,7 @@ impl<'w> ComponentsRegistrator<'w> { /// * [`ComponentsRegistrator::register_resource_with_descriptor()`] #[inline] pub fn register_resource(&mut self) -> ComponentId { - // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] - unsafe { - self.register_resource_with(TypeId::of::(), || { - ComponentDescriptor::new_resource::() - }) - } + self.register_component::() } /// Registers a [non-send resource](crate::system::NonSend) of type `T` with this instance. @@ -330,7 +310,7 @@ impl<'w> ComponentsRegistrator<'w> { .queued .get_mut() .unwrap_or_else(PoisonError::into_inner) - .resources + .components .remove(&type_id) { // If we are trying to register something that has already been queued, we respect the queue. @@ -347,7 +327,7 @@ impl<'w> ComponentsRegistrator<'w> { id } - /// Registers a [`Resource`] described by `descriptor`. + /// Registers a non-send resource described by `descriptor`. /// /// # Note /// @@ -357,9 +337,9 @@ impl<'w> ComponentsRegistrator<'w> { /// # See also /// /// * [`Components::resource_id()`] - /// * [`ComponentsRegistrator::register_resource()`] + /// * [`ComponentsRegistrator::register_non_send()`] #[inline] - pub fn register_resource_with_descriptor( + pub fn register_non_send_with_descriptor( &mut self, descriptor: ComponentDescriptor, ) -> ComponentId { @@ -419,7 +399,6 @@ impl QueuedRegistration { #[derive(Default)] pub struct QueuedComponents { pub(super) components: TypeIdMap, - pub(super) resources: TypeIdMap, pub(super) dynamic_registrations: Vec, } @@ -430,17 +409,15 @@ impl Debug for QueuedComponents { .iter() .map(|(type_id, queued)| (type_id, queued.id)) .collect::>(); - let resources = self - .resources - .iter() - .map(|(type_id, queued)| (type_id, queued.id)) - .collect::>(); let dynamic_registrations = self .dynamic_registrations .iter() .map(|queued| queued.id) .collect::>(); - write!(f, "components: {components:?}, resources: {resources:?}, dynamic_registrations: {dynamic_registrations:?}") + write!( + f, + "components: {components:?}, dynamic_registrations: {dynamic_registrations:?}" + ) } } @@ -526,7 +503,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { .queued .write() .unwrap_or_else(PoisonError::into_inner) - .resources + .components .entry(type_id) .or_insert_with(|| { // SAFETY: The id was just generated. @@ -684,7 +661,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. /// See type level docs for details. #[inline] - pub fn queue_register_resource_with_descriptor( + pub fn queue_register_non_send_with_descriptor( &self, descriptor: ComponentDescriptor, ) -> ComponentId { diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 18932a1646526..3a163a8e0dcdc 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1684,21 +1684,21 @@ impl World { .map(Into::into) } - /// Registers a new [`Resource`] type and returns the [`ComponentId`] created for it. + /// Registers a new non-send resource type and returns the [`ComponentId`] created for it. /// - /// This enables the dynamic registration of new [`Resource`] definitions at runtime for + /// This enables the dynamic registration of new non-send resources definitions at runtime for /// advanced use cases. /// /// # Note /// - /// Registering a [`Resource`] does not insert it into [`World`]. For insertion, you could use - /// [`World::insert_resource_by_id`]. - pub fn register_resource_with_descriptor( + /// Registering a non-send resource does not insert it into [`World`]. For insertion, you could use + /// [`World::insert_non_send_by_id`]. + pub fn register_non_send_with_descriptor( &mut self, descriptor: ComponentDescriptor, ) -> ComponentId { self.components_registrator() - .register_resource_with_descriptor(descriptor) + .register_non_send_with_descriptor(descriptor) } /// Initializes a new resource and returns the [`ComponentId`] created for it. @@ -3930,35 +3930,7 @@ mod tests { } #[test] - fn dynamic_resource() { - let mut world = World::new(); - - let descriptor = ComponentDescriptor::new_resource::(); - - let component_id = world.register_resource_with_descriptor(descriptor); - - let value = 0; - OwningPtr::make(value, |ptr| { - // SAFETY: value is valid for the layout of `TestResource` - unsafe { - world.insert_resource_by_id(component_id, ptr, MaybeLocation::caller()); - } - }); - - // SAFETY: We know that the resource is of type `TestResource` - let resource = unsafe { - world - .get_resource_by_id(component_id) - .unwrap() - .deref::() - }; - assert_eq!(resource.0, 0); - - assert!(world.remove_resource_by_id(component_id).is_some()); - } - - #[test] - fn custom_resource_with_layout() { + fn custom_non_send_with_layout() { static DROP_COUNT: AtomicU32 = AtomicU32::new(0); let mut world = World::new(); @@ -3980,26 +3952,26 @@ mod tests { ) }; - let component_id = world.register_resource_with_descriptor(descriptor); + let component_id = world.register_non_send_with_descriptor(descriptor); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| { // SAFETY: value is valid for the component layout unsafe { - world.insert_resource_by_id(component_id, ptr, MaybeLocation::caller()); + world.insert_non_send_by_id(component_id, ptr, MaybeLocation::caller()); } }); // SAFETY: [u8; 8] is the correct type for the resource let data = unsafe { world - .get_resource_by_id(component_id) + .get_non_send_by_id(component_id) .unwrap() .deref::<[u8; 8]>() }; assert_eq!(*data, [0, 1, 2, 3, 4, 5, 6, 7]); - assert!(world.remove_resource_by_id(component_id).is_some()); + assert!(world.remove_non_send_by_id(component_id).is_some()); assert_eq!(DROP_COUNT.load(Ordering::SeqCst), 1); } From ff0e1954c4592902ffb5d0be2c6a0204e930d95a Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Oct 2025 14:28:24 +0100 Subject: [PATCH 61/69] made ReflectResource deref to ReflectComponent --- crates/bevy_ecs/macros/src/component.rs | 10 +----- crates/bevy_ecs/src/component/info.rs | 2 +- crates/bevy_ecs/src/component/register.rs | 12 +++---- crates/bevy_ecs/src/reflect/resource.rs | 20 ++++++------ crates/bevy_remote/src/builtin_methods.rs | 14 +++------ crates/bevy_scene/src/dynamic_scene.rs | 18 +++++++---- .../bevy_scene/src/dynamic_scene_builder.rs | 31 ++++++++++--------- crates/bevy_scene/src/scene_spawner.rs | 4 +-- 8 files changed, 52 insertions(+), 59 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 6ef8116f68b83..c8e470da03326 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -76,14 +76,6 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - let clone_behavior = quote!( - use #bevy_ecs_path::relationship::{ - RelationshipCloneBehaviorBase, RelationshipCloneBehaviorViaClone, RelationshipCloneBehaviorViaReflect, - RelationshipTargetCloneBehaviorViaClone, RelationshipTargetCloneBehaviorViaReflect, RelationshipTargetCloneBehaviorHierarchy - }; - (&&&&&&&#bevy_ecs_path::relationship::RelationshipCloneBehaviorSpecialization::::default()).default_clone_behavior() - ); - // This puts `register_required` before `register_recursive_requires` to ensure that the constructors of _all_ top // level components are initialized first, giving them precedence over recursively defined constructors for the same component type let component_derive_token_stream = TokenStream::from(quote! { @@ -104,7 +96,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { #on_despawn fn clone_behavior() -> #bevy_ecs_path::component::ComponentCloneBehavior { - #clone_behavior + #bevy_ecs_path::component::ComponentCloneBehavior::Default } #map_entities diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index b18f405332eb1..364c3003fe269 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -713,7 +713,7 @@ impl Components { /// The [`ComponentId`] must be unique. /// The [`TypeId`] and [`ComponentId`] must not be registered or queued. #[inline] - pub(super) unsafe fn register_resource_unchecked( + pub(super) unsafe fn register_non_send_unchecked( &mut self, type_id: TypeId, component_id: ComponentId, diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 7c365e184c79f..a7f3687e794f3 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -284,19 +284,19 @@ impl<'w> ComponentsRegistrator<'w> { pub fn register_non_send(&mut self) -> ComponentId { // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] unsafe { - self.register_resource_with(TypeId::of::(), || { + self.register_non_send_with(TypeId::of::(), || { ComponentDescriptor::new_non_send::(StorageType::default()) }) } } - /// Same as [`Components::register_resource_unchecked`] but handles safety. + /// Same as [`Components::register_non_send_unchecked`] but handles safety. /// /// # Safety /// /// The [`ComponentDescriptor`] must match the [`TypeId`]. #[inline] - unsafe fn register_resource_with( + unsafe fn register_non_send_with( &mut self, type_id: TypeId, descriptor: impl FnOnce() -> ComponentDescriptor, @@ -322,7 +322,7 @@ impl<'w> ComponentsRegistrator<'w> { // SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`] unsafe { self.components - .register_resource_unchecked(type_id, id, descriptor()); + .register_non_send_unchecked(type_id, id, descriptor()); } id } @@ -610,7 +610,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { unsafe { registrator .components - .register_resource_unchecked(type_id, id, descriptor); + .register_non_send_unchecked(type_id, id, descriptor); } }, ) @@ -644,7 +644,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { unsafe { registrator .components - .register_resource_unchecked(type_id, id, descriptor); + .register_non_send_unchecked(type_id, id, descriptor); } }, ) diff --git a/crates/bevy_ecs/src/reflect/resource.rs b/crates/bevy_ecs/src/reflect/resource.rs index 5c334bb8ca879..61ce6fc880f5a 100644 --- a/crates/bevy_ecs/src/reflect/resource.rs +++ b/crates/bevy_ecs/src/reflect/resource.rs @@ -4,10 +4,9 @@ //! //! See the module doc for [`reflect::component`](`crate::reflect::component`). -use crate::{ - reflect::{ReflectComponent, ReflectComponentFns}, - resource::Resource, -}; +use core::ops::Deref; + +use crate::{reflect::ReflectComponent, resource::Resource}; use bevy_reflect::{FromReflect, FromType, TypePath}; /// A struct used to operate on reflected [`Resource`] of a type. @@ -15,17 +14,18 @@ use bevy_reflect::{FromReflect, FromType, TypePath}; /// A [`ReflectResource`] for type `T` can be obtained via /// [`bevy_reflect::TypeRegistration::data`]. #[derive(Clone)] -pub struct ReflectResource(ReflectComponentFns); +pub struct ReflectResource(ReflectComponent); + +impl Deref for ReflectResource { + type Target = ReflectComponent; -impl ReflectResource { - /// Use as a [`ReflectComponent`]. - pub fn as_reflect_component(self) -> ReflectComponent { - ReflectComponent::new(self.0) + fn deref(&self) -> &Self::Target { + &self.0 } } impl FromType for ReflectResource { fn from_type() -> Self { - ReflectResource(ReflectComponentFns::new::()) + ReflectResource(>::from_type()) } } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index e2cc305e58911..6131443f80b4a 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -520,11 +520,7 @@ pub fn process_remote_get_resources_request( .map_err(BrpError::resource_error)?; let entity_ref = world.get_entity(entity).map_err(BrpError::resource_error)?; - let Some(reflected) = reflect_resource - .clone() - .as_reflect_component() - .reflect(entity_ref) - else { + let Some(reflected) = reflect_resource.reflect(entity_ref) else { return Err(BrpError::resource_not_present(&resource_path)); }; @@ -1047,7 +1043,7 @@ pub fn process_remote_insert_resources_request( .ok_or(anyhow!("Resource is not registered: `{}`", resource_path)) .map_err(BrpError::resource_error)?; // get the entity if it already exists, otherwise spawn a new one. - if let Some(entity) = world.resource_entities.get(&resource_id) { + if let Some(entity) = world.resource_entities().get(resource_id) { world.entity_mut(*entity).insert_reflect(reflected_resource); } else { world.spawn_empty().insert_reflect(reflected_resource); @@ -1142,8 +1138,6 @@ pub fn process_remote_mutate_resources_request( // Get the actual resource value from the world as a `dyn Reflect`. let mut reflected_resource = reflect_resource - .clone() - .as_reflect_component() .reflect_mut(world.entity_mut(entity)) .ok_or(BrpError::resource_not_present(&resource_path))?; @@ -1639,8 +1633,8 @@ fn get_resource_entity<'r>( .get_resource_id(type_id) .ok_or(anyhow!("Resource not registered: `{}`", resource_path))?; let entity = world - .resource_entities - .get(&component_id) + .resource_entities() + .get(component_id) .ok_or(anyhow!("Resource entity does not exist."))?; Ok(*entity) } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 458ae95871eb4..c29d40d263f7a 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,8 +1,10 @@ +use std::ops::Deref; + use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; use bevy_ecs::{ entity::{Entity, EntityHashMap, SceneEntityMapper}, - reflect::{AppTypeRegistry, ReflectComponent}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, resource::IsResource, world::World, }; @@ -110,11 +112,15 @@ impl DynamicScene { } })?; let reflect_component = - registration.data::().ok_or_else(|| { - SceneSpawnError::UnregisteredComponent { + if let Some(reflect_component) = registration.data::() { + Ok(reflect_component) + } else if let Some(reflect_resource) = registration.data::() { + Ok(reflect_resource.deref()) + } else { + Err(SceneSpawnError::UnregisteredComponent { type_path: type_info.type_path().to_string(), - } - })?; + }) + }?; { let component_id = reflect_component.register_component(world); @@ -192,7 +198,7 @@ mod tests { component::Component, entity::{Entity, EntityHashMap, EntityMapper, MapEntities}, hierarchy::ChildOf, - reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, resource::Resource, world::World, }; diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index a99c498db0703..afc7292071785 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -1,5 +1,3 @@ -use core::any::TypeId; - use crate::reflect_utils::clone_reflect_value; use crate::{DynamicEntity, DynamicScene, SceneFilter}; use alloc::collections::BTreeMap; @@ -7,7 +5,7 @@ use bevy_ecs::{ component::Component, entity_disabling::DefaultQueryFilters, prelude::Entity, - reflect::{AppTypeRegistry, ReflectComponent}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, resource::Resource, world::World, }; @@ -346,15 +344,9 @@ impl<'w> DynamicSceneBuilder<'w> { #[must_use] pub fn extract_resources(mut self) -> Self { // Don't extract the DefaultQueryFilters resource - let original_world_dqf_id = self - .original_world - .components() - .get_valid_resource_id(TypeId::of::()); + let original_world_dqf_id = self.original_world.resource_id::(); // Don't extract the AppTypeRegistry resource - let original_world_atr_id = self - .original_world - .components() - .get_valid_resource_id(TypeId::of::()); + let original_world_atr_id = self.original_world.resource_id::(); let type_registry = self.original_world.resource::().read(); @@ -392,9 +384,18 @@ impl<'w> DynamicSceneBuilder<'w> { // The resource_id has been approved, so we don't do any other checks let type_registration = type_registry.get(type_id)?; - let component = type_registration - .data::()? - .reflect(original_entity)?; + // A resource entity can have both components and resources, we handle both cases: + let component = if let Some(reflect_resource) = + type_registration.data::() + { + reflect_resource.reflect(original_entity) + } else if let Some(reflect_component) = + type_registration.data::() + { + reflect_component.reflect(original_entity) + } else { + None + }?; let component = clone_reflect_value(component.as_partial_reflect(), type_registration); @@ -599,7 +600,7 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert_eq!(scene.resources[0].components.len(), 3); + assert_eq!(scene.resources[0].components.len(), 2); assert!(scene.resources[0].components[0].represents::()); assert!(scene.resources[0].components[1].represents::()); } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index d1b9535b8970c..51aed8230f7f7 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -709,7 +709,7 @@ mod tests { component::Component, hierarchy::Children, observer::On, - prelude::ReflectComponent, + prelude::{ReflectComponent, ReflectResource}, query::With, system::{Commands, Query, Res, ResMut, RunSystemOnce}, }; @@ -850,7 +850,7 @@ mod tests { struct ComponentF; #[derive(Resource, Default, Reflect)] - #[reflect(Component)] + #[reflect(Resource)] struct TriggerCount(u32); fn setup() -> App { From c9f7ec5d42876d0c4d917977d6750728f188038e Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Oct 2025 17:16:44 +0100 Subject: [PATCH 62/69] made resource entity stable, fixed bevy_scene --- crates/bevy_ecs/macros/src/component.rs | 2 +- crates/bevy_ecs/src/lib.rs | 10 ++- crates/bevy_ecs/src/resource.rs | 21 +----- crates/bevy_ecs/src/world/mod.rs | 38 ++++++---- crates/bevy_scene/src/dynamic_scene.rs | 9 +-- .../bevy_scene/src/dynamic_scene_builder.rs | 35 ++++++---- crates/bevy_scene/src/scene.rs | 70 ++----------------- crates/bevy_ui_render/src/debug_overlay.rs | 1 - 8 files changed, 67 insertions(+), 119 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index c8e470da03326..9f9fa6a01f1f9 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -50,7 +50,7 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { let storage = storage_path(&bevy_ecs_path, StorageTy::Table); let on_add_path = Some(quote!(::on_add_hook)); - let on_remove_path = Some(quote!(::on_remove_hook)); + let on_remove_path = None; let on_insert_path = None; let on_replace_path = None; let on_despawn_path = None; diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index fa32c90757af0..3a3508a2a4e38 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1239,41 +1239,48 @@ mod tests { #[test] fn resource() { use crate::resource::Resource; + use std::println; #[derive(Resource, PartialEq, Debug)] struct Num(i32); #[derive(Resource, PartialEq, Debug)] struct BigNum(u64); - + println!("WHat"); let mut world = World::default(); assert!(world.get_resource::().is_none()); assert!(!world.contains_resource::()); assert!(!world.is_resource_added::()); assert!(!world.is_resource_changed::()); + println!("WHat"); world.insert_resource(Num(123)); let resource_id = world .components() .get_resource_id(TypeId::of::()) .unwrap(); + println!("WHat"); assert_eq!(world.resource::().0, 123); assert!(world.contains_resource::()); assert!(world.is_resource_added::()); assert!(world.is_resource_changed::()); + println!("WHat"); world.insert_resource(BigNum(456)); assert_eq!(world.resource::().0, 456u64); + println!("WHat"); world.insert_resource(BigNum(789)); assert_eq!(world.resource::().0, 789); + println!("WHat"); { let mut value = world.resource_mut::(); assert_eq!(value.0, 789); value.0 = 10; } + println!("WHat"); assert_eq!( world.resource::().0, @@ -1303,6 +1310,7 @@ mod tests { Some(&BigNum(1)), "re-inserting resources works" ); + println!("WHat"); assert_eq!( world.get_resource::(), diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index bb5e91520d5f9..01918ac0915a5 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -104,22 +104,6 @@ pub trait Resource: Component { // and that would conflict with the `DeferredWorld` passed to the resource hook. unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); } - - /// The `on_remove` component hook that maintains the uniqueness property of a resource. - fn on_remove_hook(mut deferred_world: DeferredWorld, context: HookContext) { - let world = deferred_world.deref(); - // If the resource is already linked to a new (different) entity, we don't remove it. - if let Some(entity) = world.resource_entities.get(context.component_id) - && *entity == context.entity - { - // SAFETY: We have exclusive world access (as long as we don't make structural changes). - let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; - // SAFETY: There are no shared references to the map. - // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), - // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &mut *cache.0.get() }.remove(context.component_id); - } - } } /// A cache that links each `ComponentId` from a resource to the corresponding entity. @@ -194,10 +178,11 @@ mod tests { assert_eq!(world.entities().len(), start + 3); assert!(world.remove_resource_by_id(id).is_some()); assert_eq!(world.entities().len(), start + 2); + // the entity is stable: removing the resource should only remove the component from the entity, not despawn the entity world.remove_resource::(); - assert_eq!(world.entities().len(), start + 1); + assert_eq!(world.entities().len(), start + 2); // 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); + assert_eq!(world.entities().len(), start + 2); } } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 3a163a8e0dcdc..a740e0ae070f7 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1711,17 +1711,25 @@ impl World { #[inline] #[track_caller] pub fn init_resource(&mut self) -> ComponentId { - // The default component hook behavior for adding an already existing reasource is to replace it. - // We don't want that here. - if self.contains_resource::() { - return self.resource_id::().unwrap(); // must exist - } - + let resource_id = self.register_resource::(); let caller = MaybeLocation::caller(); let resource = R::from_world(self); move_as_ptr!(resource); - self.spawn_with_caller(resource, caller); - self.resource_id::().unwrap() // must exist + + if let Some(entity) = self.resource_entities.get(resource_id) { + let mut entity_mut = self + .get_entity_mut(*entity) + .expect("ResourceCache is in sync"); + entity_mut.insert_with_caller( + resource, + InsertMode::Keep, // don't change it if it already exists + caller, + RelationshipHookMode::Run, + ); + } else { + self.spawn_with_caller(resource, caller); // ResourceCache is updated automatically + } + resource_id } /// Inserts a new resource with the given `value`. @@ -1747,8 +1755,10 @@ impl World { // if the resource already exists, we replace it on the same entity if let Some(component_id) = self.resource_id::() && let Some(entity) = self.resource_entities.get(component_id) - && let Ok(mut entity_mut) = self.get_entity_mut(*entity) { + let mut entity_mut = self + .get_entity_mut(*entity) + .expect("ResourceCache is in sync"); entity_mut.insert_with_caller( value, InsertMode::Replace, @@ -1818,10 +1828,12 @@ impl World { /// Removes the resource of a given type and returns it, if it exists. Otherwise returns `None`. #[inline] pub fn remove_resource(&mut self) -> Option { - let component_id = self.resource_id::()?; - let entity = *self.resource_entities.get(component_id)?; - let value = self.get_entity_mut(entity).ok()?.take::()?; - self.despawn(entity); + let resource_id = self.resource_id::()?; + let entity = *self.resource_entities.get(resource_id)?; + let value = self + .get_entity_mut(entity) + .expect("ResourceCache is in sync") + .take::()?; Some(value) } diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index c29d40d263f7a..ee42c1f6d74a5 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -5,7 +5,6 @@ use bevy_asset::Asset; use bevy_ecs::{ entity::{Entity, EntityHashMap, SceneEntityMapper}, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, - resource::IsResource, world::World, }; use bevy_reflect::{PartialReflect, TypePath}; @@ -50,6 +49,8 @@ impl DynamicScene { /// Create a new dynamic scene from a given world. pub fn from_world(world: &World) -> Self { + let resource_entities: Vec = world.resource_entities().values().copied().collect(); + DynamicSceneBuilder::from_world(world) .extract_entities( // we do this instead of a query, in order to completely sidestep default query filters. @@ -59,11 +60,7 @@ impl DynamicScene { .iter() .flat_map(bevy_ecs::archetype::Archetype::entities) .map(bevy_ecs::archetype::ArchetypeEntity::id) - .filter(|id| { - world - .get_entity(*id) - .is_ok_and(|entity| !entity.contains::()) - }), + .filter(|entity| !resource_entities.contains(entity)), ) .extract_resources() .build() diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index afc7292071785..c194ff844b080 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use crate::reflect_utils::clone_reflect_value; use crate::{DynamicEntity, DynamicScene, SceneFilter}; use alloc::collections::BTreeMap; @@ -344,9 +346,15 @@ impl<'w> DynamicSceneBuilder<'w> { #[must_use] pub fn extract_resources(mut self) -> Self { // Don't extract the DefaultQueryFilters resource - let original_world_dqf_id = self.original_world.resource_id::(); + let original_world_dqf_id = self + .original_world + .components() + .valid_resource_id::(); // Don't extract the AppTypeRegistry resource - let original_world_atr_id = self.original_world.resource_id::(); + let original_world_atr_id = self + .original_world + .components() + .valid_resource_id::(); let type_registry = self.original_world.resource::().read(); @@ -362,7 +370,10 @@ impl<'w> DynamicSceneBuilder<'w> { .components() .get_info(*resource_id) .and_then(bevy_ecs::component::ComponentInfo::type_id) - .is_some_and(|type_id| self.resource_filter.is_denied_by_id(type_id)) + .is_some_and(|type_id| { + self.resource_filter.is_denied_by_id(type_id) + || !type_registry.contains(type_id) + }) { continue; } @@ -384,18 +395,12 @@ impl<'w> DynamicSceneBuilder<'w> { // The resource_id has been approved, so we don't do any other checks let type_registration = type_registry.get(type_id)?; - // A resource entity can have both components and resources, we handle both cases: - let component = if let Some(reflect_resource) = - type_registration.data::() - { - reflect_resource.reflect(original_entity) - } else if let Some(reflect_component) = - type_registration.data::() - { - reflect_component.reflect(original_entity) - } else { - None - }?; + let component = type_registration + .data::() + .or(type_registration + .data::() + .map(|rr| rr.deref()))? + .reflect(original_entity)?; let component = clone_reflect_value(component.as_partial_reflect(), type_registration); diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index fe358718c1122..c216cede98f20 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,5 +1,3 @@ -use core::any::TypeId; - use crate::reflect_utils::clone_reflect_value; use crate::{DynamicScene, SceneSpawnError}; use bevy_asset::Asset; @@ -65,68 +63,6 @@ impl Scene { ) -> Result<(), SceneSpawnError> { let type_registry = type_registry.read(); - let self_dqf_id = self - .world - .components() - .get_resource_id(TypeId::of::()); - - // Resources archetype - for (component_id, entity) in self.world.resource_entities().iter() { - if Some(*component_id) == self_dqf_id { - continue; - } - - let entity_ref = self - .world - .get_entity(*entity) - .expect("Resource entity should exist in the world."); - - if !entity_ref.contains_id(*component_id) { - continue; - } - - let component_info = self - .world - .components() - .get_info(*component_id) - .expect("component_ids in archetypes should have ComponentInfo"); - - let type_id = component_info - .type_id() - .expect("reflected resources must have a type_id"); - - let registration = - type_registry - .get(type_id) - .ok_or_else(|| SceneSpawnError::UnregisteredType { - std_type_name: component_info.name(), - })?; - let reflect_component = registration.data::().ok_or_else(|| { - SceneSpawnError::UnregisteredResource { - type_path: registration.type_info().type_path().to_string(), - } - })?; - - let Some(component) = reflect_component - .reflect(self.world.entity(*entity)) - .map(|component| clone_reflect_value(component.as_partial_reflect(), registration)) - else { - continue; - }; - - // If this component references entities in the scene, - // update them to the entities in the world. - SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { - reflect_component.apply_or_insert_mapped( - &mut world.entity_mut(*entity), - component.as_partial_reflect(), - &type_registry, - mapper, - RelationshipHookMode::Skip, - ); - }); - } - // Ensure that all scene entities have been allocated in the destination // world before handling components that may contain references that need mapping. for archetype in self.world.archetypes().iter() { @@ -137,7 +73,13 @@ impl Scene { } } + let self_dqf_id = self.world.resource_id::(); + for archetype in self.world.archetypes().iter() { + if self_dqf_id.is_some_and(|dqf_id| archetype.contains(dqf_id)) { + continue; + } + for scene_entity in archetype.entities() { let entity = *entity_map .get(&scene_entity.id()) diff --git a/crates/bevy_ui_render/src/debug_overlay.rs b/crates/bevy_ui_render/src/debug_overlay.rs index e78f1da86da60..b05cfbaa33774 100644 --- a/crates/bevy_ui_render/src/debug_overlay.rs +++ b/crates/bevy_ui_render/src/debug_overlay.rs @@ -8,7 +8,6 @@ use bevy_asset::AssetId; use bevy_camera::visibility::InheritedVisibility; use bevy_color::Hsla; use bevy_ecs::entity::Entity; -use bevy_ecs::prelude::ReflectComponent; use bevy_ecs::prelude::ReflectResource; use bevy_ecs::resource::Resource; use bevy_ecs::system::Commands; From 86962a8be10096ba179962661d87a508898c6231 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Oct 2025 17:49:30 +0100 Subject: [PATCH 63/69] fixed bevy_remote, added logging to hooks --- crates/bevy_ecs/macros/src/component.rs | 3 +- crates/bevy_ecs/src/resource.rs | 32 +++++++++++++++---- crates/bevy_remote/src/schemas/json_schema.rs | 5 --- 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 9f9fa6a01f1f9..2e9709932ac23 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -53,7 +53,8 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { let on_remove_path = None; let on_insert_path = None; let on_replace_path = None; - let on_despawn_path = None; + let on_despawn_path = + Some(quote!(::on_despawn_hook)); let on_add = hook_register_function_call(&bevy_ecs_path, quote! {on_add}, on_add_path); let on_remove = hook_register_function_call(&bevy_ecs_path, quote! {on_remove}, on_remove_path); diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 01918ac0915a5..ee254e6ccffe8 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -1,6 +1,7 @@ //! Resources are unique, singleton-like data types that can be accessed from systems and stored in the [`World`](crate::world::World). use core::ops::{Deref, DerefMut}; +use log::warn; use crate::{ component::{Component, ComponentId, Mutable}, @@ -90,19 +91,38 @@ pub trait Resource: Component { fn on_add_hook(mut deferred_world: DeferredWorld, context: HookContext) { let world = deferred_world.deref(); - if let Some(&offending_entity) = world.resource_entities.get(context.component_id) - && offending_entity != context.entity - { - // the resource already exists and we need to overwrite it - deferred_world.commands().entity(offending_entity).despawn(); + if let Some(&original_entity) = world.resource_entities.get(context.component_id) { + if original_entity != context.entity { + // the resource already exists and the new one should be removed + deferred_world + .commands() + .entity(context.entity) + .remove_by_id(context.component_id); + let name = deferred_world + .components() + .get_name(context.component_id) + .expect("resource is registered"); + warn!("Tried inserting {} on the wrong entity.", name); + } + } else { + // SAFETY: We have exclusive world access (as long as we don't make structural changes). + let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; + // SAFETY: There are no shared references to the map. + // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), + // and that would conflict with the `DeferredWorld` passed to the resource hook. + unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); } + } + /// The `on_despawn` component hook that maintains the uniqueness property of a resource. + fn on_despawn_hook(mut deferred_world: DeferredWorld, context: HookContext) { + warn!("Resource entities are not supposed to be despawned."); // SAFETY: We have exclusive world access (as long as we don't make structural changes). let cache = unsafe { deferred_world.as_unsafe_world_cell().resource_entities() }; // SAFETY: There are no shared references to the map. // We only expose `&ResourceCache` to code with access to a resource (such as `&World`), // and that would conflict with the `DeferredWorld` passed to the resource hook. - unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); + unsafe { &mut *cache.0.get() }.remove(context.component_id); } } diff --git a/crates/bevy_remote/src/schemas/json_schema.rs b/crates/bevy_remote/src/schemas/json_schema.rs index f4721d4d0255a..8355422bd9481 100644 --- a/crates/bevy_remote/src/schemas/json_schema.rs +++ b/crates/bevy_remote/src/schemas/json_schema.rs @@ -395,10 +395,6 @@ mod tests { .clone(); let (_, schema) = export_type(&foo_registration, &SchemaTypesMetadata::default()); - assert!( - schema.reflect_types.contains(&"Component".to_owned()), - "Should be a component" // Resources are Components - ); assert!( schema.reflect_types.contains(&"Resource".to_owned()), "Should be a resource" @@ -599,7 +595,6 @@ mod tests { "reflectTypes": [ "Resource", "Default", - "Component", ], "kind": "Struct", "type": "object", From 3989f2e41cc5fc7c7b24661dadcd8a5cc8f4776a Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Mon, 27 Oct 2025 17:51:13 +0100 Subject: [PATCH 64/69] remove pointless printing --- crates/bevy_ecs/src/lib.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 3a3508a2a4e38..faa99aba8069c 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1252,35 +1252,29 @@ mod tests { assert!(!world.contains_resource::()); assert!(!world.is_resource_added::()); assert!(!world.is_resource_changed::()); - println!("WHat"); world.insert_resource(Num(123)); let resource_id = world .components() .get_resource_id(TypeId::of::()) .unwrap(); - println!("WHat"); assert_eq!(world.resource::().0, 123); assert!(world.contains_resource::()); assert!(world.is_resource_added::()); assert!(world.is_resource_changed::()); - println!("WHat"); world.insert_resource(BigNum(456)); assert_eq!(world.resource::().0, 456u64); - println!("WHat"); world.insert_resource(BigNum(789)); assert_eq!(world.resource::().0, 789); - println!("WHat"); { let mut value = world.resource_mut::(); assert_eq!(value.0, 789); value.0 = 10; } - println!("WHat"); assert_eq!( world.resource::().0, From 6ceed0d62b3583c8b7728e725bdccee3a4290c35 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Oct 2025 03:13:15 +0100 Subject: [PATCH 65/69] fixed ci --- crates/bevy_ecs/src/world/mod.rs | 14 ++++++++------ crates/bevy_scene/src/scene.rs | 21 ++++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a740e0ae070f7..ca1d104d04e24 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -3274,12 +3274,14 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { - let component_ids = self.resource_entities.indices(); - component_ids.into_iter().filter_map(|component_id| { - let component_info = self.components().get_info(*component_id)?; - let resource = self.get_resource_by_id(*component_id)?; - Some((component_info, resource)) - }) + self.resource_entities + .indices() + .iter() + .filter_map(|component_id| { + let component_info = self.components().get_info(*component_id)?; + let resource = self.get_resource_by_id(*component_id)?; + Some((component_info, resource)) + }) } /// Mutably iterates over all resources in the world. diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index c216cede98f20..031a38a910379 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,3 +1,5 @@ +use std::ops::Deref; + use crate::reflect_utils::clone_reflect_value; use crate::{DynamicScene, SceneSpawnError}; use bevy_asset::Asset; @@ -5,7 +7,7 @@ use bevy_ecs::{ component::ComponentCloneBehavior, entity::{Entity, EntityHashMap, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, - reflect::{AppTypeRegistry, ReflectComponent}, + reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, relationship::RelationshipHookMode, world::World, }; @@ -104,12 +106,17 @@ impl Scene { .ok_or_else(|| SceneSpawnError::UnregisteredType { std_type_name: component_info.name(), })?; - let reflect_component = - registration.data::().ok_or_else(|| { - SceneSpawnError::UnregisteredComponent { - type_path: registration.type_info().type_path().to_string(), - } - })?; + let reflect_component = if let Some(reflect_component) = + registration.data::() + { + Ok(reflect_component) + } else if let Some(reflect_resource) = registration.data::() { + Ok(reflect_resource.deref()) + } else { + Err(SceneSpawnError::UnregisteredComponent { + type_path: registration.type_info().type_path().to_string(), + }) + }?; let Some(component) = reflect_component .reflect(self.world.entity(scene_entity.id())) From b2b22481258eec37b69474e968bd85fafe237f36 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Oct 2025 03:22:32 +0100 Subject: [PATCH 66/69] fix ci some more --- crates/bevy_scene/src/dynamic_scene.rs | 2 +- crates/bevy_scene/src/dynamic_scene_builder.rs | 4 ++-- crates/bevy_scene/src/scene.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index ee42c1f6d74a5..c95f7fe23395c 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use core::ops::Deref; use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index c194ff844b080..ccc52772926f8 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use core::ops::Deref; use crate::reflect_utils::clone_reflect_value; use crate::{DynamicEntity, DynamicScene, SceneFilter}; @@ -399,7 +399,7 @@ impl<'w> DynamicSceneBuilder<'w> { .data::() .or(type_registration .data::() - .map(|rr| rr.deref()))? + .map(Deref::deref))? .reflect(original_entity)?; let component = diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 031a38a910379..63d16685af234 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,4 +1,4 @@ -use std::ops::Deref; +use core::ops::Deref; use crate::reflect_utils::clone_reflect_value; use crate::{DynamicScene, SceneSpawnError}; From 50286198152e1eaa73b4f326541392ed92686657 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Oct 2025 03:32:00 +0100 Subject: [PATCH 67/69] updated example asset --- assets/scenes/load_scene_example.scn.ron | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index 477bee30545df..c7bbb17ea75ad 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -1,7 +1,12 @@ ( resources: { - "scene::ResourceA": ( - score: 1, + 4294967291: ( + components: { + "bevy_ecs::resource::IsResource": (), + "scene::ResourceA": ( + score: 1, + ), + }, ), }, entities: { From 039cd0bff80346a0df09a733abded34113d3ee24 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Oct 2025 03:49:27 +0100 Subject: [PATCH 68/69] fix ci some more --- crates/bevy_ecs/src/component/register.rs | 3 +-- crates/bevy_remote/src/builtin_methods.rs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index a7f3687e794f3..5e15f98c3c2db 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -271,7 +271,6 @@ impl<'w> ComponentsRegistrator<'w> { /// # See also /// /// * [`Components::resource_id()`] - /// * [`ComponentsRegistrator::register_resource_with_descriptor()`] #[inline] pub fn register_resource(&mut self) -> ComponentId { self.register_component::() @@ -652,7 +651,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { }) } - /// This is a queued version of [`ComponentsRegistrator::register_resource_with_descriptor`]. + /// This is a queued version of [`ComponentsRegistrator::register_non_send_with_descriptor`]. /// This will reserve an id and queue the registration. /// These registrations will be carried out at the next opportunity. /// diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index 6131443f80b4a..0e207254da07f 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -1210,7 +1210,7 @@ pub fn process_remote_remove_resources_request( let app_type_registry = world.resource::().clone(); let type_registry = app_type_registry.read(); - let entity = get_resource_entity(&type_registry, &resource_path, &world) + let entity = get_resource_entity(&type_registry, &resource_path, world) .map_err(BrpError::resource_error)?; world.despawn(entity); @@ -1621,7 +1621,7 @@ fn get_resource_type_registration<'r>( .ok_or_else(|| anyhow!("Unknown resource type: `{}`", resource_path)) } -fn get_resource_entity<'r>( +fn get_resource_entity( type_registry: &TypeRegistry, resource_path: &str, world: &World, From 1014aadb18e09c3305e0c0b16ad79d65095274f9 Mon Sep 17 00:00:00 2001 From: Trashtalk Date: Tue, 28 Oct 2025 04:02:51 +0100 Subject: [PATCH 69/69] fix ci some more --- crates/bevy_ecs/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index faa99aba8069c..28ee3fc49ffef 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1239,14 +1239,12 @@ mod tests { #[test] fn resource() { use crate::resource::Resource; - use std::println; #[derive(Resource, PartialEq, Debug)] struct Num(i32); #[derive(Resource, PartialEq, Debug)] struct BigNum(u64); - println!("WHat"); let mut world = World::default(); assert!(world.get_resource::().is_none()); assert!(!world.contains_resource::()); @@ -1304,7 +1302,6 @@ mod tests { Some(&BigNum(1)), "re-inserting resources works" ); - println!("WHat"); assert_eq!( world.get_resource::(),