diff --git a/assets/scenes/load_scene_example.scn.ron b/assets/scenes/load_scene_example.scn.ron index 477bee30545df..035fe4d073322 100644 --- a/assets/scenes/load_scene_example.scn.ron +++ b/assets/scenes/load_scene_example.scn.ron @@ -1,7 +1,13 @@ ( resources: { - "scene::ResourceA": ( - score: 1, + 4294967299: ( + components: { + "bevy_ecs::resource::ResourceComponent": (( + score: 1, + )), + "bevy_ecs::resource::IsResource": (), + "bevy_ecs::entity_disabling::Internal": (), + }, ), }, entities: { diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 6f14e9e980f1e..a193a892585de 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -25,9 +25,24 @@ 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 map_entities_impl = map_entities( + &ast.data, + &bevy_ecs_path, + Ident::new("self", Span::call_site()), + false, + false, + None, + ); + TokenStream::from(quote! { impl #impl_generics #bevy_ecs_path::resource::Resource for #struct_name #type_generics #where_clause { } + + impl #impl_generics #bevy_ecs_path::entity::MapEntities for #struct_name #type_generics #where_clause { + fn map_entities(&mut self, mapper: &mut MAPENT) { + #map_entities_impl + } + } }) } diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index a388a30cb696d..ba920ddae485f 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -600,7 +600,7 @@ pub fn derive_message(input: TokenStream) -> TokenStream { } /// Implement the `Resource` trait. -#[proc_macro_derive(Resource)] +#[proc_macro_derive(Resource, attributes(entities))] pub fn derive_resource(input: TokenStream) -> TokenStream { component::derive_resource(input) } @@ -677,7 +677,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/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 0e222692d7fb1..fcb74003db6eb 100644 --- a/crates/bevy_ecs/src/component/info.rs +++ b/crates/bevy_ecs/src/component/info.rs @@ -20,7 +20,7 @@ use crate::{ }, lifecycle::ComponentHooks, query::DebugCheckedUnwrap as _, - resource::Resource, + resource::{Resource, ResourceComponent}, storage::SparseSetIndex, }; @@ -290,18 +290,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, - } + Self::new::>() } pub(super) fn new_non_send(storage_type: StorageType) -> Self { @@ -348,7 +337,6 @@ impl ComponentDescriptor { pub struct Components { pub(super) components: Vec>, pub(super) indices: TypeIdMap, - pub(super) resource_indices: TypeIdMap, // This is kept internal and local to verify that no deadlocks can occor. pub(super) queued: bevy_platform::sync::RwLock, } @@ -587,8 +575,12 @@ impl Components { /// Type-erased equivalent of [`Components::valid_resource_id()`]. #[inline] + #[deprecated( + since = "0.18.0", + note = "Use valid_resource_id::() or get_valid_id(TypeId::of::>()) for normal resources. Use get_valid_id(TypeId::of::()) for non-send resources." + )] 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. @@ -613,7 +605,7 @@ impl Components { /// * [`Components::get_resource_id()`] #[inline] pub fn valid_resource_id(&self) -> Option { - self.get_valid_resource_id(TypeId::of::()) + self.get_valid_id(TypeId::of::>()) } /// Type-erased equivalent of [`Components::component_id()`]. @@ -665,15 +657,12 @@ impl Components { /// Type-erased equivalent of [`Components::resource_id()`]. #[inline] + #[deprecated( + since = "0.18.0", + note = "Use resource_id::() or get_id(TypeId::of::>()) instead for normal resources. Use get_id(TypeId::of::()) for non-send resources." + )] pub fn get_resource_id(&self, type_id: TypeId) -> Option { - self.resource_indices.get(&type_id).copied().or_else(|| { - self.queued - .read() - .unwrap_or_else(PoisonError::into_inner) - .resources - .get(&type_id) - .map(|queued| queued.id) - }) + self.get_id(type_id) } /// Returns the [`ComponentId`] of the given [`Resource`] type `T`. @@ -705,7 +694,7 @@ impl Components { /// * [`Components::get_resource_id()`] #[inline] pub fn resource_id(&self) -> Option { - self.get_resource_id(TypeId::of::()) + self.get_id(TypeId::of::>()) } /// # Safety @@ -724,7 +713,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..f358eb8d240be 100644 --- a/crates/bevy_ecs/src/component/register.rs +++ b/crates/bevy_ecs/src/component/register.rs @@ -10,7 +10,7 @@ use crate::{ Component, ComponentDescriptor, ComponentId, Components, RequiredComponents, StorageType, }, query::DebugCheckedUnwrap as _, - resource::Resource, + resource::{IsResource, Resource, ResourceComponent}, }; /// Generates [`ComponentId`]s. @@ -289,12 +289,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. @@ -310,6 +305,37 @@ impl<'w> ComponentsRegistrator<'w> { } } + // Adds the necessary resource hooks and required components. + // This ensures that a resource registered with a custom descriptor functions as expected. + // Panics if the component is not registered. + // This has no effect on non-send resources. + fn add_resource_hooks_and_required_components(&mut self, id: ComponentId) { + if self + .get_info(id) + .expect("component was just registered") + .is_send_and_sync() + { + let hooks = self + .components + .get_hooks_mut(id) + .expect("component was just registered"); + hooks.on_add(crate::resource::on_add_hook); + hooks.on_remove(crate::resource::on_remove_hook); + + let is_resource_id = self.register_component::(); + // SAFETY: + // - The IsResource component id matches + // - The constructor constructs an IsResource + unsafe { + let _ = self.components.register_required_components::( + id, + is_resource_id, + || IsResource, + ); + } + } + } + /// Same as [`Components::register_resource_unchecked`] but handles safety. /// /// # Safety @@ -321,7 +347,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; } @@ -344,6 +370,10 @@ impl<'w> ComponentsRegistrator<'w> { self.components .register_resource_unchecked(type_id, id, descriptor()); } + + // registering a resource with this method leaves hooks and required_components empty, so we add them afterwards + self.add_resource_hooks_and_required_components(id); + id } @@ -368,6 +398,10 @@ impl<'w> ComponentsRegistrator<'w> { unsafe { self.components.register_component_inner(id, descriptor); } + + // registering a resource with this method leaves hooks and required_components empty, so we add them afterwards + self.add_resource_hooks_and_required_components(id); + id } @@ -619,26 +653,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// See type level docs for details. #[inline] pub fn queue_register_resource(&self) -> ComponentId { - let type_id = TypeId::of::(); - self.get_resource_id(type_id).unwrap_or_else(|| { - // SAFETY: We just checked that this type was not already registered. - unsafe { - self.register_arbitrary_resource( - type_id, - ComponentDescriptor::new_resource::(), - move |registrator, id, descriptor| { - // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. - // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. - #[expect(unused_unsafe, reason = "More precise to specify.")] - unsafe { - registrator - .components - .register_resource_unchecked(type_id, id, descriptor); - } - }, - ) - } - }) + self.queue_register_component::>() } /// This is a queued version of [`ComponentsRegistrator::register_non_send`]. @@ -654,7 +669,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { #[inline] pub fn queue_register_non_send(&self) -> ComponentId { let type_id = TypeId::of::(); - self.get_resource_id(type_id).unwrap_or_else(|| { + self.get_id(type_id).unwrap_or_else(|| { // SAFETY: We just checked that this type was not already registered. unsafe { self.register_arbitrary_resource( @@ -695,6 +710,9 @@ impl<'w> ComponentsQueuedRegistrator<'w> { .components .register_component_inner(id, descriptor); } + + // registering a resource with this method leaves hooks and required_components empty, so we add them afterwards + registrator.add_resource_hooks_and_required_components(id); }) } } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index d34932cb06751..6d2a5091b7e2b 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1236,7 +1236,7 @@ mod tests { #[test] fn resource() { - use crate::resource::Resource; + use crate::resource::{Resource, ResourceComponent}; #[derive(Resource, PartialEq, Debug)] struct Num(i32); @@ -1253,7 +1253,7 @@ mod tests { world.insert_resource(Num(123)); let resource_id = world .components() - .get_resource_id(TypeId::of::()) + .get_id(TypeId::of::>()) .unwrap(); assert_eq!(world.resource::().0, 123); @@ -1310,7 +1310,7 @@ mod tests { let current_resource_id = world .components() - .get_resource_id(TypeId::of::()) + .get_id(TypeId::of::>()) .unwrap(); assert_eq!( resource_id, current_resource_id, @@ -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(1).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(1).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/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/resource.rs b/crates/bevy_ecs/src/resource.rs index 7da4f31113f4f..af66029259be0 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -1,5 +1,19 @@ //! 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; + +use crate::{ + component::ComponentId, + entity::MapEntities, + entity_disabling::Internal, + lifecycle::HookContext, + prelude::{Component, Entity, ReflectComponent}, + storage::SparseSet, + world::DeferredWorld, +}; +use bevy_platform::cell::SyncUnsafeCell; +use bevy_reflect::{prelude::ReflectDefault, Reflect}; + // The derive macro for the `Resource` trait pub use bevy_ecs_macros::Resource; @@ -72,4 +86,103 @@ 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: Send + Sync + 'static + MapEntities {} + +/// A cache that links each `ComponentId` from a resource to the corresponding entity. +#[derive(Default)] +pub struct ResourceCache(SyncUnsafeCell>); + +impl ResourceCache { + fn inner(&self) -> &SparseSet { + // SAFETY: pointer was just created, so it's convertible + unsafe { &*self.0.get() } + } + + fn inner_mut(&mut self) -> &mut SparseSet { + self.0.get_mut() + } + + pub(crate) fn get(&self, id: ComponentId) -> Option<&Entity> { + self.inner().get(id) + } + + pub(crate) fn contains(&self, id: ComponentId) -> bool { + self.inner().contains(id) + } + + pub(crate) fn len(&self) -> usize { + self.inner().len() + } + + pub(crate) fn clear(&mut self) { + self.inner_mut().clear(); + } + + pub(crate) fn entities(&self) -> impl Iterator { + self.inner().values() + } + + /// Returns an iterator visiting all component/entity pairs in arbitrary order, with references to the values. + pub fn iter(&self) -> impl Iterator { + self.inner().iter() + } + + pub(crate) fn component_ids(&self) -> &[ComponentId] { + self.inner().indices() + } +} + +/// A component that contains the resource of type `T`. +/// +/// When creating a resource, a [`ResourceComponent`] is inserted on a new entity in the world. +/// +/// 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. +/// Moreover, this component requires both marker components [`IsResource`] and [`Internal`]. +/// The former can be used to quickly iterate over all resources through a query, +/// while the latter marks the associated entity as internal, ensuring that it won't show up on broad queries such as +/// `world.query::()`. +#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Component))] +#[derive(Component)] +#[require(IsResource, Internal)] +#[component(on_add = on_add_hook, on_remove = on_remove_hook)] +#[repr(transparent)] +pub struct ResourceComponent(#[entities] pub R); + +pub(crate) fn on_add_hook(mut deferred_world: DeferredWorld, context: HookContext) { + let world = deferred_world.deref(); + if world.resource_entities.contains(context.component_id) { + // the resource already exists and we need to overwrite it + let offending_entity = *world.resource_entities.get(context.component_id).unwrap(); + if context.entity != offending_entity { + deferred_world.commands().entity(offending_entity).despawn(); + } + } + + // we update the cache + let cache = deferred_world.as_unsafe_world_cell().resource_entities(); + // SAFETY: We only update a cache and don't perform any structural changes (component adds / removals) + 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 + { + let cache = deferred_world.as_unsafe_world_cell().resource_entities(); + // SAFETY: We only update a cache and don't perform any structural changes (component adds / removals) + unsafe { &mut *cache.0.get() }.insert(context.component_id, context.entity); + } +} + +/// A marker component for entities which store resources. +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Component, Default, Debug) +)] +#[derive(Component, Debug, Default)] +#[require(Internal)] +pub struct IsResource; diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index a03825f9eda56..872ed97165634 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -1162,22 +1162,22 @@ mod tests { ( "system_d".to_string(), "system_a".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], + vec!["bevy_ecs::resource::ResourceComponent".into()], ), ( "system_d".to_string(), "system_e".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], + vec!["bevy_ecs::resource::ResourceComponent".into()], ), ( "system_b".to_string(), "system_a".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], + vec!["bevy_ecs::resource::ResourceComponent".into()], ), ( "system_b".to_string(), "system_e".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], + vec!["bevy_ecs::resource::ResourceComponent".into()], ), ]; @@ -1221,7 +1221,7 @@ mod tests { ( "resmut_system (in set (resmut_system, resmut_system))".to_string(), "resmut_system (in set (resmut_system, resmut_system))".to_string(), - vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()], + vec!["bevy_ecs::resource::ResourceComponent".into()], ) ); } 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/storage/resource.rs b/crates/bevy_ecs/src/storage/resource.rs index 1a90cc511ccc0..2a88a98be771b 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 before 0.18 as part of resource-as-component effort" + )] 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 before 0.18 as part of resource-as-component effort" + )] pub(crate) unsafe fn insert_with_ticks( &mut self, value: OwningPtr<'_>, diff --git a/crates/bevy_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index 22b6db8e4e936..38b2900216dc4 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1167,11 +1167,8 @@ mod tests { let y_access = y.initialize(&mut world); let conflicts = x_access.get_conflicts(&y_access); - let b_id = world - .components() - .get_resource_id(TypeId::of::()) - .unwrap(); - let d_id = world.components().get_id(TypeId::of::()).unwrap(); + let b_id = world.components().resource_id::().unwrap(); + let d_id = world.components().component_id::().unwrap(); assert_eq!(conflicts, vec![b_id, d_id].into()); } diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index 8986ec812a462..456e9522bdcad 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -459,10 +459,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 @@ -522,8 +521,9 @@ 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_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 8af9361ec068d..d9f67eed2c77d 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -2,14 +2,16 @@ 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, }, - resource::Resource, + resource::{Resource, ResourceComponent}, storage::ResourceData, system::{Query, Single, SystemMeta}, world::{ @@ -752,157 +754,179 @@ 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: +// - 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; + 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_component::>(), + Query::>, With>::init_state(world), + ) } fn init_access( - &component_id: &Self::State, + (component_id, query_state): &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, - _world: &mut World, + world: &mut 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); + + Query::init_access(query_state, system_meta, component_access_set, world); } #[inline] unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, + (_, query_state): &mut Self::State, + system_meta: &SystemMeta, 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) - { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( + query_state.update_archetypes_unsafe_world_cell(world); + // SAFETY: + // - This is not a mutable query. + // - 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, + 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, + (_, query_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::() - ); - }); + 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 { + query_state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + }; + let result = query + .single_inner() + .expect("The query was expected to contain exactly one matching entity."); 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()), + value: &result.value.0, + ticks: result.ticks, + changed_by: result.changed_by, } } } -// SAFETY: Res ComponentId access is applied to SystemMeta. If this Res -// conflicts with any prior access, a panic will occur. +// 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; + 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_component::>(), + Query::>, With>::init_state(world), + ) } fn init_access( - &component_id: &Self::State, + (component_id, query_state): &Self::State, system_meta: &mut SystemMeta, component_access_set: &mut FilteredAccessSet, - _world: &mut World, + world: &mut 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); + + Query::init_access(query_state, system_meta, component_access_set, world); } #[inline] unsafe fn validate_param( - &mut component_id: &mut Self::State, - _system_meta: &SystemMeta, + (_, query_state): &mut Self::State, + system_meta: &SystemMeta, 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) - { - Ok(()) - } else { - Err(SystemParamValidationError::invalid::( + query_state.update_archetypes_unsafe_world_cell(world); + // SAFETY: + // - We have unique mutable access to the resource. + // - 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, + 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, + (_, query_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::() - ); - }); + 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 { + query_state.query_unchecked_manual_with_ticks(world, system_meta.last_run, change_tick) + }; + let result = query + .single_inner() + .expect("The query was expected to contain exactly one matching entity."); + 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, + value: &mut result.value.0, + ticks: result.ticks, + changed_by: result.changed_by, } } } diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index 576773bb70b6e..9719fb4311981 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -995,8 +995,9 @@ 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/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index f19a587d07d9b..a55d3175a517f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -146,6 +146,15 @@ 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. + #[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. /// @@ -1747,6 +1756,18 @@ 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::() + } + /// 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 ccee6ff8d5be7..cfe2ad84e64b7 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -32,25 +32,25 @@ 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::{MaybeLocation, MutUntyped, TicksMut}, + change_detection::{DetectChangesMut, MaybeLocation, MutUntyped, TicksMut}, component::{ CheckChangeTicks, Component, ComponentDescriptor, ComponentId, ComponentIds, ComponentInfo, ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable, RequiredComponents, RequiredComponentsError, Tick, }, entity::{Entities, Entity, EntityDoesNotExistError}, - entity_disabling::DefaultQueryFilters, + entity_disabling::{DefaultQueryFilters, Internal}, error::{DefaultErrorHandler, ErrorHandler}, lifecycle::{ComponentHooks, RemovedComponentMessages, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, message::{Message, MessageId, Messages, WriteBatchIds}, observer::Observers, - prelude::{Add, Despawn, Insert, Remove, Replace}, + prelude::{Add, Despawn, Insert, Remove, Replace, Without}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, - resource::Resource, + resource::{Resource, ResourceCache, ResourceComponent}, schedule::{Schedule, ScheduleLabel, Schedules}, storage::{ResourceData, Storages}, system::Commands, @@ -63,7 +63,7 @@ use crate::{ }; use alloc::{boxed::Box, vec::Vec}; use bevy_platform::sync::atomic::{AtomicU32, Ordering}; -use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr, UnsafeCellDeref}; +use bevy_ptr::{move_as_ptr, MovingPtr, OwningPtr, Ptr}; use bevy_utils::prelude::DebugName; use core::{any::TypeId, fmt}; use log::warn; @@ -97,6 +97,7 @@ pub struct World { pub(crate) bundles: Bundles, pub(crate) observers: Observers, pub(crate) removed_components: RemovedComponentMessages, + pub(crate) resource_entities: ResourceCache, pub(crate) change_tick: AtomicU32, pub(crate) last_change_tick: Tick, pub(crate) last_check_tick: Tick, @@ -115,6 +116,7 @@ impl Default for World { bundles: Default::default(), observers: Observers::default(), removed_components: Default::default(), + resource_entities: Default::default(), // Default value is `1`, and `last_change_tick`s default to `0`, such that changes // are detected on first system runs and for direct world queries. change_tick: AtomicU32::new(1), @@ -224,6 +226,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. @@ -615,7 +623,7 @@ impl World { /// Returns [`None`] if the [`Resource`] type has not yet been initialized within the /// [`World`] using [`World::register_resource`], [`World::init_resource`] or [`World::insert_resource`]. pub fn resource_id(&self) -> Option { - self.components.get_resource_id(TypeId::of::()) + self.components.get_id(TypeId::of::>()) } /// Returns [`EntityRef`]s that expose read-only operations for the given @@ -1700,23 +1708,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 - .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); - } - }); + // 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 bundle = ResourceComponent(R::from_world(self)); + move_as_ptr!(bundle); + self.spawn_with_caller(bundle, caller); + self.resource_id::().unwrap() // must exist } /// Inserts a new resource with the given `value`. @@ -1738,13 +1740,22 @@ impl World { value: R, caller: MaybeLocation, ) { - let component_id = self.components_registrator().register_resource::(); - 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); - } - }); + let resource_component = ResourceComponent(value); + move_as_ptr!(resource_component); + // 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( + resource_component, + InsertMode::Replace, + caller, + RelationshipHookMode::Run, + ); + } else { + self.spawn_with_caller(resource_component, caller); + } } /// Initializes a new non-send resource and returns the [`ComponentId`] created for it. @@ -1805,10 +1816,14 @@ 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 (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 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.0) } /// Removes a `!Send` resource from the world and returns it, if present. @@ -1824,7 +1839,7 @@ impl World { /// thread than where the value was inserted from. #[inline] pub fn remove_non_send_resource(&mut self) -> Option { - let component_id = self.components.get_valid_resource_id(TypeId::of::())?; + let component_id = self.components.get_valid_id(TypeId::of::())?; let (ptr, _, _) = self .storages .non_send_resources @@ -1838,25 +1853,24 @@ impl World { #[inline] 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) + .valid_resource_id::() + .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 + self.resource_entities .get(component_id) - .is_some_and(ResourceData::is_present) + .and_then(|entity| self.get_entity(*entity).ok()) + .is_some_and(|entity_ref| entity_ref.contains_id(component_id)) } /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. #[inline] pub fn contains_non_send(&self) -> bool { self.components - .get_valid_resource_id(TypeId::of::()) + .get_valid_id(TypeId::of::()) .and_then(|component_id| self.storages.non_send_resources.get(component_id)) .is_some_and(ResourceData::is_present) } @@ -1879,7 +1893,7 @@ impl World { /// was called. pub fn is_resource_added(&self) -> bool { self.components - .get_valid_resource_id(TypeId::of::()) + .get_valid_id(TypeId::of::>()) .is_some_and(|component_id| self.is_resource_added_by_id(component_id)) } @@ -1891,14 +1905,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 @@ -1910,7 +1918,7 @@ impl World { /// was called. pub fn is_resource_changed(&self) -> bool { self.components - .get_valid_resource_id(TypeId::of::()) + .valid_resource_id::() .is_some_and(|component_id| self.is_resource_changed_by_id(component_id)) } @@ -1922,20 +1930,14 @@ 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. pub fn get_resource_change_ticks(&self) -> Option { self.components - .get_valid_resource_id(TypeId::of::()) + .valid_resource_id::() .and_then(|component_id| self.get_resource_change_ticks_by_id(component_id)) } @@ -1946,10 +1948,9 @@ impl World { &self, component_id: ComponentId, ) -> Option { - self.storages - .resources - .get(component_id) - .and_then(ResourceData::get_ticks) + 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) } /// Gets a reference to the resource of the given type @@ -2072,28 +2073,11 @@ impl World { &mut self, 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::() { + self.insert_resource(func()); } - // 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::() } + self.get_resource_mut::().unwrap() // must exist } /// Gets a mutable reference to the resource of type `T` if it exists, @@ -2130,40 +2114,11 @@ 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()) - { - 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); - } - }); + if !self.contains_resource::() { + self.init_resource::(); } - // 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::() } + self.get_resource_mut::().unwrap() // must exist } /// Gets an immutable reference to the non-send resource of the given type, if it exists. @@ -2604,15 +2559,14 @@ 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 (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 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::>()?; + let mut value = entity_mut.take::>()?.0; + let value_mut = Mut { value: &mut value, ticks: TicksMut { @@ -2621,22 +2575,67 @@ impl World { last_run: last_change_tick, this_run: change_tick, }, - changed_by: caller.as_mut(), + changed_by: changed_by.as_mut(), }; + 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::()); - 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); - }) - } - })?; + let mut entity_mut = self + .get_entity_mut(entity) + .expect("Reserved resource entity was destroyed."); + + let value = ResourceComponent(value); + move_as_ptr!(value); + + // 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(); + + // set the added tick to the original + entity_mut + .get_mut::>()? + .set_last_added(ticks.added); + + // 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 { >::apply_effect(bundle, &mut entity_mut) }; + } Some(result) } @@ -2720,13 +2719,13 @@ impl World { value: OwningPtr<'_>, caller: MaybeLocation, ) { - let change_tick = self.change_tick(); - - 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); - } + 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 @@ -2758,19 +2757,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] @@ -3028,7 +3014,6 @@ impl World { let Storages { ref mut tables, ref mut sparse_sets, - ref mut resources, ref mut non_send_resources, } = self.storages; @@ -3036,7 +3021,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); @@ -3055,18 +3039,26 @@ 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(); - } - - /// 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(); } + /// Despawns all entities in this [`World`]. + pub fn clear_entities(&mut self) { + 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`]. /// /// **Note:** Any resource fetch to this [`World`] will fail unless they are re-initialized, @@ -3075,7 +3067,12 @@ 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.resource_entities.entities().copied().collect(); + for entity in resource_entities { + self.despawn(entity); + } + self.resource_entities.clear(); + self.storages.non_send_resources.clear(); self.storages.non_send_resources.clear(); } @@ -3269,18 +3266,12 @@ 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.resource_entities.component_ids().to_vec(); + 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. @@ -3293,6 +3284,7 @@ impl World { /// ``` /// # use bevy_ecs::prelude::*; /// # use bevy_ecs::change_detection::MutUntyped; + /// # use bevy_ecs::resource::ResourceComponent; /// # use std::collections::HashMap; /// # use std::any::TypeId; /// # #[derive(Resource)] @@ -3312,7 +3304,7 @@ impl World { /// let mut mutators: HashMap)>> = HashMap::default(); /// /// // Add mutator closure for `A` - /// mutators.insert(TypeId::of::(), Box::new(|mut_untyped| { + /// mutators.insert(TypeId::of::>(), Box::new(|mut_untyped| { /// // Note: `MutUntyped::as_mut()` automatically marks the resource as changed /// // for ECS change detection, and gives us a `PtrMut` we can use to mutate the resource. /// // SAFETY: We assert ptr is the same type of A with TypeId of A @@ -3322,7 +3314,7 @@ impl World { /// })); /// /// // Add mutator closure for `B` - /// mutators.insert(TypeId::of::(), Box::new(|mut_untyped| { + /// mutators.insert(TypeId::of::>(), Box::new(|mut_untyped| { /// // SAFETY: We assert ptr is the same type of B with TypeId of B /// let b = unsafe { &mut mut_untyped.as_mut().deref_mut::() }; /// # b.0 += 1; @@ -3350,40 +3342,25 @@ impl World { /// ``` #[inline] pub fn iter_resources_mut(&mut self) -> impl Iterator)> { - self.storages - .resources + let unsafe_world = self.as_unsafe_world_cell(); + let resource_entities = unsafe_world.resource_entities(); + let components = unsafe_world.components(); + + resource_entities .iter() - .filter_map(|(component_id, data)| { + .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 (ptr, ticks, caller) = data.get_with_ticks()?; + let component_info = + unsafe { components.get_info(component_id).debug_checked_unwrap() }; + let entity_cell = unsafe_world.get_entity(entity).ok()?; // 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 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()) }, - }; + // - 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)) }) @@ -3434,11 +3411,8 @@ 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<()> { - self.storages - .resources - .get_mut(component_id)? - .remove_and_drop(); - Some(()) + let entity = self.resource_entities.get(component_id)?; + self.despawn(*entity).then_some(()) } /// Removes the resource of a given type, if it exists. Otherwise returns `None`. @@ -3639,7 +3613,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.resource_entities.len()) .finish() } } @@ -3705,9 +3679,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::Resource, + resource::{Resource, ResourceComponent}, world::{error::EntityMutableFetchError, DeferredWorld}, }; use alloc::{ @@ -3856,7 +3830,7 @@ mod tests { world.insert_resource(TestResource(42)); let component_id = world .components() - .get_valid_resource_id(TypeId::of::()) + .valid_resource_id::() .unwrap(); let resource = world.get_resource_by_id(component_id).unwrap(); @@ -3872,7 +3846,7 @@ mod tests { world.insert_resource(TestResource(42)); let component_id = world .components() - .get_valid_resource_id(TypeId::of::()) + .get_valid_id(TypeId::of::>()) .unwrap(); { @@ -3903,12 +3877,18 @@ mod tests { let mut iter = world.iter_resources(); let (info, ptr) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); + assert_eq!( + info.name(), + DebugName::type_name::>() + ); // SAFETY: We know that the resource is of type `TestResource` assert_eq!(unsafe { ptr.deref::().0 }, 42); let (info, ptr) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); + assert_eq!( + info.name(), + DebugName::type_name::>() + ); assert_eq!( // SAFETY: We know that the resource is of type `TestResource2` unsafe { &ptr.deref::().0 }, @@ -3931,14 +3911,20 @@ mod tests { let mut iter = world.iter_resources_mut(); let (info, mut mut_untyped) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); + assert_eq!( + info.name(), + DebugName::type_name::>() + ); // SAFETY: We know that the resource is of type `TestResource` unsafe { mut_untyped.as_mut().deref_mut::().0 = 43; }; let (info, mut mut_untyped) = iter.next().unwrap(); - assert_eq!(info.name(), DebugName::type_name::()); + assert_eq!( + info.name(), + DebugName::type_name::>() + ); // SAFETY: We know that the resource is of type `TestResource2` unsafe { mut_untyped.as_mut().deref_mut::().0 = "Hello, world?".to_string(); @@ -3970,14 +3956,14 @@ mod tests { } }); - // SAFETY: We know that the resource is of type `TestResource` + // SAFETY: We know that the resource is of type `ResourceComponent` let resource = unsafe { world .get_resource_by_id(component_id) .unwrap() - .deref::() + .deref::>() }; - assert_eq!(resource.0, 0); + assert_eq!(resource.0 .0, 0); assert!(world.remove_resource_by_id(component_id).is_some()); } @@ -4139,7 +4125,7 @@ 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() { + for entity in world.iter_entities().filter(|e| !e.contains::()) { let counter = entity_counters.entry(entity.id()).or_insert(0); *counter += 1; } @@ -4239,7 +4225,10 @@ 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(2); core::mem::swap( diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 16b7a863b4442..c832e2976742e 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -12,7 +12,7 @@ use crate::{ observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReleaseStateQueryData}, - resource::Resource, + resource::{Resource, ResourceCache, ResourceComponent}, storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, }; @@ -330,6 +330,14 @@ impl<'w> UnsafeWorldCell<'w> { unsafe { self.world_metadata() }.last_change_tick() } + /// Retrieves this world's resource-entity map. + #[inline] + pub fn resource_entities(self) -> &'w ResourceCache { + // SAFETY: + // - we only access world metadata + &unsafe { self.world_metadata() }.resource_entities + } + /// Increments the world's current change tick and returns the old value. #[inline] pub fn increment_change_tick(self) -> Tick { @@ -401,7 +409,9 @@ impl<'w> UnsafeWorldCell<'w> { /// - no mutable reference to the resource exists at the same time #[inline] pub unsafe fn get_resource(self) -> Option<&'w R> { - let component_id = self.components().get_valid_resource_id(TypeId::of::())?; + let component_id = self + .components() + .get_valid_id(TypeId::of::>())?; // SAFETY: caller ensures `self` has permission to access the resource // caller also ensure that no mutable reference to the resource exists unsafe { @@ -419,7 +429,9 @@ impl<'w> UnsafeWorldCell<'w> { /// - no mutable reference to the resource exists at the same time #[inline] pub unsafe fn get_resource_ref(self) -> Option> { - let component_id = self.components().get_valid_resource_id(TypeId::of::())?; + let component_id = self + .components() + .get_valid_id(TypeId::of::>())?; // SAFETY: caller ensures `self` has permission to access the resource // caller also ensures that no mutable reference to the resource exists @@ -455,12 +467,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> { - // 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() + let entity = self.resource_entities().get(component_id)?; + let entity_cell = self.get_entity(*entity).ok()?; + entity_cell.get_by_id(component_id) } /// Gets a reference to the non-send resource of the given type if it exists @@ -471,7 +480,7 @@ impl<'w> UnsafeWorldCell<'w> { /// - no mutable reference to the resource exists at the same time #[inline] pub unsafe fn get_non_send_resource(self) -> Option<&'w R> { - let component_id = self.components().get_valid_resource_id(TypeId::of::())?; + let component_id = self.components().get_valid_id(TypeId::of::())?; // SAFETY: caller ensures that `self` has permission to access `R` // caller ensures that no mutable reference exists to `R` unsafe { @@ -514,13 +523,15 @@ impl<'w> UnsafeWorldCell<'w> { #[inline] pub unsafe fn get_resource_mut(self) -> Option> { self.assert_allows_mutable_access(); - let component_id = self.components().get_valid_resource_id(TypeId::of::())?; + let component_id = self + .components() + .get_valid_id(TypeId::of::>())?; // SAFETY: // - caller ensures `self` has permission to access the resource mutably // - caller ensures no other references to the resource exist unsafe { self.get_resource_mut_by_id(component_id) - // `component_id` was gotten from `TypeId::of::()` + // `component_id` was gotten from `TypeId::of::>()` .map(|ptr| ptr.with_type::()) } } @@ -542,31 +553,10 @@ impl<'w> UnsafeWorldCell<'w> { component_id: ComponentId, ) -> Option> { self.assert_allows_mutable_access(); - // 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()) }, - }) + let entity = self.resource_entities().get(component_id)?; + let entity_cell = self.get_entity(*entity).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 @@ -578,7 +568,7 @@ impl<'w> UnsafeWorldCell<'w> { #[inline] pub unsafe fn get_non_send_resource_mut(self) -> Option> { self.assert_allows_mutable_access(); - let component_id = self.components().get_valid_resource_id(TypeId::of::())?; + let component_id = self.components().get_valid_id(TypeId::of::())?; // SAFETY: // - caller ensures that `self` has permission to access the resource // - caller ensures that the resource is unaliased @@ -646,14 +636,15 @@ impl<'w> UnsafeWorldCell<'w> { TickCells<'w>, MaybeLocation<&'w UnsafeCell<&'static Location<'static>>>, )> { + 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: // - 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. @@ -900,6 +891,32 @@ impl<'w> UnsafeEntityCell<'w> { } } + /// 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. + /// + /// # 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_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_changed_by( + 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. /// @@ -1312,6 +1329,37 @@ 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 useful for debugging. +/// +/// # 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_changed_by( + 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() 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", diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index f9ef1616ee4e6..cd0f9734b8769 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,14 +1,13 @@ 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}, + resource::IsResource, world::World, }; -use bevy_reflect::{PartialReflect, TypePath}; +use bevy_reflect::{PartialReflect, Reflect, TypePath}; -use crate::reflect_utils::clone_reflect_value; use bevy_ecs::component::ComponentCloneBehavior; use bevy_ecs::relationship::RelationshipHookMode; @@ -29,7 +28,7 @@ use { #[derive(Asset, TypePath, Default)] pub struct DynamicScene { /// Resources stored in the dynamic scene. - pub resources: Vec>, + pub resources: Vec, /// Entities contained in the dynamic scene. pub entities: Vec, } @@ -45,6 +44,18 @@ pub struct DynamicEntity { pub components: Vec>, } +impl DynamicEntity { + /// Returns true if and only if any of the components on the entity represents T. + pub fn contains(&self) -> bool + where + T: Reflect + TypePath, + { + self.components + .iter() + .any(|component| component.represents::()) + } +} + impl DynamicScene { /// Create a new dynamic scene from a given scene. pub fn from_scene(scene: &Scene) -> Self { @@ -61,7 +72,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() @@ -82,7 +98,7 @@ impl DynamicScene { // First ensure that every entity in the scene has a corresponding world // entity in the entity map. - for scene_entity in &self.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. @@ -91,7 +107,7 @@ impl DynamicScene { .or_insert_with(|| world.spawn_empty().id()); } - for scene_entity in &self.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) @@ -142,45 +158,6 @@ 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); - } - Ok(()) } @@ -230,8 +207,9 @@ mod tests { component::Component, entity::{Entity, EntityHashMap, EntityMapper, MapEntities}, hierarchy::ChildOf, + prelude::Resource, reflect::{AppTypeRegistry, ReflectComponent, ReflectMapEntities, ReflectResource}, - resource::Resource, + resource::ResourceComponent, world::World, }; @@ -240,7 +218,7 @@ mod tests { use crate::dynamic_scene::DynamicScene; use crate::dynamic_scene_builder::DynamicSceneBuilder; - #[derive(Resource, Reflect, MapEntities, Debug)] + #[derive(Resource, Reflect, Debug)] #[reflect(Resource, MapEntities)] struct TestResource { #[entities] @@ -252,7 +230,9 @@ mod tests { #[test] fn resource_entity_map_maps_entities() { let type_registry = AppTypeRegistry::default(); - type_registry.write().register::(); + type_registry + .write() + .register::>(); let mut source_world = World::new(); source_world.insert_resource(type_registry.clone()); diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index feddacfdfb091..2384e2cf419c9 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -1,17 +1,14 @@ -use core::any::TypeId; - 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, ReflectResource}, - resource::Resource, + reflect::{AppTypeRegistry, ReflectComponent}, + resource::{Resource, ResourceComponent}, 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. @@ -27,7 +24,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 registered 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. @@ -59,7 +56,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, @@ -166,7 +163,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// If `T` has already been denied, then it will be removed from the denylist. #[must_use] pub fn allow_resource(mut self) -> Self { - self.resource_filter = self.resource_filter.allow::(); + self.resource_filter = self.resource_filter.allow::>(); self } @@ -178,7 +175,7 @@ impl<'w> DynamicSceneBuilder<'w> { /// If `T` has already been allowed, then it will be removed from the allowlist. #[must_use] pub fn deny_resource(mut self) -> Self { - self.resource_filter = self.resource_filter.deny::(); + self.resource_filter = self.resource_filter.deny::>(); self } @@ -350,42 +347,62 @@ impl<'w> DynamicSceneBuilder<'w> { let original_world_dqf_id = self .original_world .components() - .get_valid_resource_id(TypeId::of::()); + .valid_resource_id::(); + // Don't extract the AppTypeRegistry resource + let original_world_atr_id = self + .original_world + .components() + .valid_resource_id::(); 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 (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; } - 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 self + .original_world + .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)) + { + continue; + } - if is_denied { - // Resource is either in the denylist or _not_ in the allowlist - return None; - } + let mut entry = DynamicEntity { + entity: *entity, + components: Vec::new(), + }; - let type_registration = type_registry.get(type_id)?; + 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()?; - let resource = type_registration - .data::()? - .reflect(self.original_world) - .ok()?; + // The resource_id has been approved, so we don't do any other checks + let type_registration = type_registry.get(type_id)?; - let resource = - clone_reflect_value(resource.as_partial_reflect(), type_registration); + let component = type_registration + .data::()? + .reflect(original_entity)?; - self.extracted_resources.insert(component_id, resource); - Some(()) - }; - extract_and_push(); + let component = + clone_reflect_value(component.as_partial_reflect(), type_registration); + + entry.components.push(component); + Some(()) + }; + extract_and_push(); + } + self.extracted_resources.insert(*entity, entry); } drop(type_registry); @@ -400,6 +417,7 @@ mod tests { prelude::{Entity, Resource}, query::With, reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, + resource::ResourceComponent, world::World, }; @@ -567,7 +585,7 @@ mod tests { let mut world = World::default(); let atr = AppTypeRegistry::default(); - atr.write().register::(); + atr.write().register::>(); world.insert_resource(atr); world.insert_resource(ResourceA); @@ -577,7 +595,7 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert!(scene.resources[0].contains::>()); } #[test] @@ -585,7 +603,7 @@ mod tests { let mut world = World::default(); let atr = AppTypeRegistry::default(); - atr.write().register::(); + atr.write().register::>(); world.insert_resource(atr); world.insert_resource(ResourceA); @@ -596,7 +614,7 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert!(scene.resources[0].contains::>()); } #[test] @@ -660,8 +678,8 @@ mod tests { let atr = AppTypeRegistry::default(); { let mut register = atr.write(); - register.register::(); - register.register::(); + register.register::>(); + register.register::>(); } world.insert_resource(atr); @@ -674,7 +692,7 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert!(scene.resources[0].contains::>()); } #[test] @@ -684,8 +702,8 @@ mod tests { let atr = AppTypeRegistry::default(); { let mut register = atr.write(); - register.register::(); - register.register::(); + register.register::>(); + register.register::>(); } world.insert_resource(atr); @@ -698,7 +716,7 @@ mod tests { .build(); assert_eq!(scene.resources.len(), 1); - assert!(scene.resources[0].represents::()); + assert!(scene.resources[0].contains::>()); } #[test] @@ -712,6 +730,7 @@ mod tests { { let mut register = atr.write(); register.register::(); + register.register::>(); } world.insert_resource(atr); @@ -729,10 +748,10 @@ mod tests { .expect("component should be concrete due to `FromReflect`") .is::()); - let resource = &scene.resources[0]; + let resource = &scene.resources[0].components[0]; assert!(resource .try_as_reflect() .expect("resource should be concrete due to `FromReflect`") - .is::()); + .is::>()); } } diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 8908fc466ff64..ab767a32890e7 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -47,6 +47,8 @@ pub mod prelude { } use bevy_app::prelude::*; +use bevy_ecs::entity_disabling::{DefaultQueryFilters, Internal}; +use bevy_ecs::resource::{IsResource, ResourceComponent}; #[cfg(feature = "serialize")] use {bevy_asset::AssetApp, bevy_ecs::schedule::IntoScheduleConfigs}; @@ -62,6 +64,11 @@ impl Plugin for ScenePlugin { .init_asset::() .init_asset_loader::() .init_resource::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::>() .add_systems(SpawnScene, (scene_spawner, scene_spawner_system).chain()); // Register component hooks for DynamicSceneRoot @@ -120,9 +127,7 @@ mod tests { use bevy_ecs::{ component::Component, entity::Entity, - entity_disabling::Internal, hierarchy::{ChildOf, Children}, - query::Allow, reflect::{AppTypeRegistry, ReflectComponent}, world::World, }; @@ -164,7 +169,9 @@ mod tests { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::() + .register_type::() + .register_type::(); let scene_handle = app .world_mut() @@ -290,7 +297,9 @@ mod tests { .register_type::() .register_type::() .register_type::() - .register_type::(); + .register_type::() + .register_type::() + .register_type::(); let scene_handle = app .world_mut() @@ -309,11 +318,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() diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index db92bdc468431..aa3b63cf42bc7 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -7,8 +7,9 @@ use bevy_ecs::{ component::ComponentCloneBehavior, entity::{Entity, EntityHashMap, SceneEntityMapper}, entity_disabling::DefaultQueryFilters, - reflect::{AppTypeRegistry, ReflectComponent, ReflectResource}, + reflect::{AppTypeRegistry, ReflectComponent}, relationship::RelationshipHookMode, + resource::ResourceComponent, world::World, }; use bevy_reflect::TypePath; @@ -65,24 +66,40 @@ impl Scene { ) -> Result<(), SceneSpawnError> { let type_registry = type_registry.read(); + // 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() { + for scene_entity in archetype.entities() { + entity_map + .entry(scene_entity.id()) + .or_insert_with(|| world.spawn_empty().id()); + } + } + let self_dqf_id = self .world .components() - .get_resource_id(TypeId::of::()); + .get_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, scene_entity) in self.world.resource_entities().iter() { + if Some(*component_id) == self_dqf_id { continue; } - if !resource_data.is_present() { + + let entity_ref = self + .world + .get_entity(*scene_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,22 +112,33 @@ 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); - } - - // 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() { - for scene_entity in archetype.entities() { - entity_map - .entry(scene_entity.id()) - .or_insert_with(|| world.spawn_empty().id()); - } + let Some(component) = reflect_component + .reflect(self.world.entity(*scene_entity)) + .map(|component| clone_reflect_value(component.as_partial_reflect(), registration)) + else { + continue; + }; + + let entity = *entity_map + .get(scene_entity) + .expect("should have previously spawned an entity"); + + // 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, + ); + }); } for archetype in self.world.archetypes().iter() { diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 11018fb4b0b4a..05d8938988a07 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -739,7 +739,8 @@ mod tests { app.add_plugins(ScheduleRunnerPlugin::default()) .add_plugins(AssetPlugin::default()) - .add_plugins(ScenePlugin); + .add_plugins(ScenePlugin) + .register_type::(); app.update(); let mut scene_world = World::new(); @@ -1062,7 +1063,9 @@ mod tests { .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::() - .register_type::(); + .register_type::() + .register_type::() + .register_type::(); app.update(); let mut scene_world = World::new(); diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 84badb5850bef..451938845ed57 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -81,8 +81,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, + &EntitiesSerializer { + entities: &self.scene.resources, registry: self.registry, }, )?; @@ -246,8 +246,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(SceneEntitiesDeserializer { + type_registry: self.type_registry, })? .ok_or_else(|| Error::missing_field(SCENE_RESOURCES))?; @@ -275,8 +275,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(SceneEntitiesDeserializer { + type_registry: self.type_registry, })?); } SceneField::Entities => { @@ -519,6 +519,7 @@ mod tests { prelude::{Component, ReflectComponent, ReflectResource, Resource, World}, query::{With, Without}, reflect::AppTypeRegistry, + resource::ResourceComponent, world::FromWorld, }; use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize}; @@ -610,7 +611,7 @@ mod tests { registry.register::<(f32, f32)>(); registry.register::(); registry.register::(); - registry.register::(); + registry.register::>(); } world.insert_resource(registry); world @@ -633,25 +634,29 @@ mod tests { let expected = r#"( resources: { - "bevy_scene::serde::tests::MyResource": ( - foo: 123, + 4294967290: ( + components: { + "bevy_ecs::resource::ResourceComponent": (( + foo: 123, + )), + }, ), }, 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), }, @@ -670,23 +675,27 @@ mod tests { let input = r#"( resources: { - "bevy_scene::serde::tests::MyResource": ( - foo: 123, + 8589934591: ( + components: { + "bevy_ecs::resource::ResourceComponent": (( + foo: 123, + )), + }, ), }, entities: { - 8589934591: ( + 8589934590: ( components: { "bevy_scene::serde::tests::Foo": (123), }, ), - 8589934590: ( + 8589934589: ( components: { "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), }, ), - 8589934589: ( + 8589934588: ( components: { "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), @@ -815,7 +824,7 @@ 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, 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 @@ -856,7 +865,7 @@ mod tests { assert_eq!( vec![ - 146, 128, 129, 206, 255, 255, 255, 255, 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, @@ -899,7 +908,7 @@ 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, 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, diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index 2fba727a82f9a..890d2705bdcac 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -24,6 +24,7 @@ //! won't work on WASM because WASM typically doesn't have direct filesystem access. //! +use bevy::ecs::resource::ResourceComponent; use bevy::{asset::LoadState, prelude::*, tasks::IoTaskPool}; use core::time::Duration; use std::{fs::File, io::Write}; @@ -35,6 +36,7 @@ use std::{fs::File, io::Write}; fn main() { App::new() .add_plugins(DefaultPlugins) + .register_type::>() .add_systems( Startup, (save_scene_system, load_scene_system, infotext_system),