diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index 146ce09c36f9d..705191c0ef3b6 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -39,7 +39,7 @@ use crate::{ use bevy_app::{AnimationSystems, App, Plugin, PostUpdate}; use bevy_asset::{Asset, AssetApp, AssetEventSystems, Assets}; -use bevy_ecs::{prelude::*, world::EntityMutExcept}; +use bevy_ecs::{prelude::*, resource::IsResource, world::EntityMutExcept}; use bevy_math::FloatOrd; use bevy_platform::{collections::HashMap, hash::NoOpHash}; use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; @@ -1032,7 +1032,10 @@ pub fn animate_targets( graphs: Res>, threaded_animation_graphs: Res, players: Query<(&AnimationPlayer, &AnimationGraphHandle)>, - mut targets: Query<(Entity, &AnimationTargetId, &AnimatedBy, AnimationEntityMut)>, + mut targets: Query< + (Entity, &AnimationTargetId, &AnimatedBy, AnimationEntityMut), + Without, + >, animation_evaluation_state: Local>>, ) { // Evaluate all animation targets in parallel. diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index ef56ed42cb05b..bc36f9fce33ba 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -471,10 +471,10 @@ impl App { self } - /// Inserts the [`!Send`](Send) resource into the app, overwriting any existing resource + /// Inserts the [`!Send`](Send) data into the app, overwriting any existing data /// of the same type. /// - /// There is also an [`init_non_send_resource`](Self::init_non_send_resource) for + /// There is also an [`init_non_send`](Self::init_non_send) for /// resources that implement [`Default`] /// /// # Examples @@ -488,20 +488,20 @@ impl App { /// } /// /// App::new() - /// .insert_non_send_resource(MyCounter { counter: 0 }); + /// .insert_non_send(MyCounter { counter: 0 }); /// ``` - pub fn insert_non_send_resource(&mut self, resource: R) -> &mut Self { - self.world_mut().insert_non_send_resource(resource); + pub fn insert_non_send(&mut self, resource: R) -> &mut Self { + self.world_mut().insert_non_send(resource); self } - /// Inserts the [`!Send`](Send) resource into the app if there is no existing instance of `R`. + /// Inserts the [`!Send`](Send) data into the app if there is no existing instance of `R`. /// /// `R` must implement [`FromWorld`]. /// If `R` implements [`Default`], [`FromWorld`] will be automatically implemented and /// initialize the [`Resource`] with [`Default::default`]. - pub fn init_non_send_resource(&mut self) -> &mut Self { - self.world_mut().init_non_send_resource::(); + pub fn init_non_send(&mut self) -> &mut Self { + self.world_mut().init_non_send::(); self } @@ -1961,7 +1961,7 @@ mod tests { } App::new() - .init_non_send_resource::() + .init_non_send::() .init_resource::(); } diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index f37defab0b111..0e18044b8ede4 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -32,18 +32,91 @@ pub fn derive_resource(input: TokenStream) -> TokenStream { return err.into_compile_error().into(); } + // implement the Component trait + let map_entities = map_entities( + &ast.data, + &bevy_ecs_path, + Ident::new("this", Span::call_site()), + false, + false, + None + ).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::resource_on_add_hook)); + let on_remove_path = None; + let on_insert_path = None; + let on_replace_path = None; + let on_despawn_path = Some(quote!(#bevy_ecs_path::resource::resource_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); + 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(); - TokenStream::from(quote! { + // 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 { + #bevy_ecs_path::component::ComponentCloneBehavior::Default + } + + #map_entities + + fn relationship_accessor() -> Option<#bevy_ecs_path::relationship::ComponentRelationshipAccessor> { + None + } + } + }); + + // 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 { } - }) + }); + + resource_impl_token_stream + .into_iter() + .chain(component_derive_token_stream) + .collect() } /// Component derive syntax is documented on both the macro and the trait. diff --git a/crates/bevy_ecs/macros/src/lib.rs b/crates/bevy_ecs/macros/src/lib.rs index 6936ca4aff3a6..b449d4b110f01 100644 --- a/crates/bevy_ecs/macros/src/lib.rs +++ b/crates/bevy_ecs/macros/src/lib.rs @@ -639,7 +639,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/constants.rs b/crates/bevy_ecs/src/component/constants.rs new file mode 100644 index 0000000000000..a0ee53e0fde16 --- /dev/null +++ b/crates/bevy_ecs/src/component/constants.rs @@ -0,0 +1,14 @@ +//! Constant components included in every world. + +/// `usize` for the [`Add`] component used in lifecycle observers. +pub const ADD: usize = 0; +/// `usize` for the [`Insert`] component used in lifecycle observers. +pub const INSERT: usize = 1; +/// `usize` for the [`Replace`] component used in lifecycle observers. +pub const REPLACE: usize = 2; +/// `usize` for the [`Remove`] component used in lifecycle observers. +pub const REMOVE: usize = 3; +/// `usize` for [`Despawn`] component used in lifecycle observers. +pub const DESPAWN: usize = 4; +/// `usize` of the [`IsResource`] component used to mark entities with resources. +pub const IS_RESOURCE: usize = 5; diff --git a/crates/bevy_ecs/src/component/info.rs b/crates/bevy_ecs/src/component/info.rs index 9ab6127b01293..a8423d9e67196 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 { @@ -362,7 +350,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 occur. pub(super) queued: bevy_platform::sync::RwLock, } @@ -407,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`. @@ -423,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`]. @@ -470,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())) @@ -492,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()) @@ -602,7 +587,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. @@ -680,11 +665,11 @@ 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) - .resources + .components .get(&type_id) .map(|queued| queued.id) }) @@ -728,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, @@ -738,7 +723,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/mod.rs b/crates/bevy_ecs/src/component/mod.rs index 395cb7db919e7..f8b821733a4d3 100644 --- a/crates/bevy_ecs/src/component/mod.rs +++ b/crates/bevy_ecs/src/component/mod.rs @@ -1,11 +1,13 @@ //! Types for declaring and storing [`Component`]s. mod clone; +mod constants; mod info; mod register; mod required; pub use clone::*; +pub use constants::*; pub use info::*; pub use register::*; pub use required::*; diff --git a/crates/bevy_ecs/src/component/register.rs b/crates/bevy_ecs/src/component/register.rs index 5d8ac3ee6e98b..3b60c5fa439c7 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 @@ -262,6 +247,12 @@ impl<'w> ComponentsRegistrator<'w> { /// If this method is called multiple times with identical descriptors, a distinct [`ComponentId`] /// will be created for each one. /// + /// # Warning + /// + /// When registering a custom resource be sure to add [`crate::resource::IsResource`] as a required component, + /// and [`crate::resource::resource_on_add_hook`] and [`crate::resource::resource_on_despawn_hook`] as component lifecycle hooks. + /// Otherwise it will not function as a resource. + /// /// # See also /// /// * [`Components::component_id()`] @@ -286,15 +277,9 @@ impl<'w> ComponentsRegistrator<'w> { /// # See also /// /// * [`Components::resource_id()`] - /// * [`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. @@ -304,24 +289,24 @@ 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, ) -> ComponentId { - if let Some(id) = self.resource_indices.get(&type_id) { + if let Some(id) = self.indices.get(&type_id) { return *id; } @@ -330,7 +315,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. @@ -342,12 +327,12 @@ 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 } - /// Registers a [`Resource`] described by `descriptor`. + /// Registers a non-send resource described by `descriptor`. /// /// # Note /// @@ -357,9 +342,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 +404,6 @@ impl QueuedRegistration { #[derive(Default)] pub struct QueuedComponents { pub(super) components: TypeIdMap, - pub(super) resources: TypeIdMap, pub(super) dynamic_registrations: Vec, } @@ -430,17 +414,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 +508,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. @@ -592,6 +574,12 @@ impl<'w> ComponentsQueuedRegistrator<'w> { /// /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. /// See type level docs for details. + /// + /// # Warning + /// + /// When registering a custom resource be sure to add [`crate::resource::IsResource`] as a required component, + /// and [`crate::resource::resource_on_add_hook`] and [`crate::resource::resource_on_despawn_hook`] as component lifecycle hooks. + /// Otherwise it will not function as a resource. #[inline] pub fn queue_register_component_with_descriptor( &self, @@ -633,7 +621,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { unsafe { registrator .components - .register_resource_unchecked(type_id, id, descriptor); + .register_non_send_unchecked(type_id, id, descriptor); } }, ) @@ -667,7 +655,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> { unsafe { registrator .components - .register_resource_unchecked(type_id, id, descriptor); + .register_non_send_unchecked(type_id, id, descriptor); } }, ) @@ -675,7 +663,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. /// @@ -684,7 +672,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/entity_disabling.rs b/crates/bevy_ecs/src/entity_disabling.rs index 10541d4ed9bc1..29c692f708289 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, }; @@ -166,7 +167,11 @@ pub struct Disabled; /// 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/lib.rs b/crates/bevy_ecs/src/lib.rs index b8835257a66dd..9a35e4d47dcd0 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -370,6 +370,7 @@ mod tests { #[test] fn spawning_with_manual_entity_allocation() { let mut world = World::new(); + let start = world.entities().count_spawned(); let e1 = world.entities_allocator_mut().alloc(); world.spawn_at(e1, (TableStored("abc"), A(123))).unwrap(); @@ -390,9 +391,9 @@ mod tests { .despawn_no_free(); world.spawn_at(e3, (TableStored("def"), A(456))).unwrap(); - assert_eq!(world.entities.count_spawned(), 2); + assert_eq!(world.entities.count_spawned(), start + 2); assert!(world.despawn(e1)); - assert_eq!(world.entities.count_spawned(), 1); + assert_eq!(world.entities.count_spawned(), start + 1); assert!(world.get::(e1).is_none()); assert!(world.get::(e1).is_none()); assert_eq!(world.get::(e3).unwrap().0, "def"); @@ -1252,7 +1253,6 @@ mod tests { #[derive(Resource, PartialEq, Debug)] struct BigNum(u64); - let mut world = World::default(); assert!(world.get_resource::().is_none()); assert!(!world.contains_resource::()); @@ -1419,30 +1419,30 @@ mod tests { } #[test] - fn non_send_resource() { + fn non_send() { let mut world = World::default(); - world.insert_non_send_resource(123i32); - world.insert_non_send_resource(456i64); - assert_eq!(*world.non_send_resource::(), 123); - assert_eq!(*world.non_send_resource_mut::(), 456); + world.insert_non_send(123i32); + world.insert_non_send(456i64); + assert_eq!(*world.non_send::(), 123); + assert_eq!(*world.non_send_mut::(), 456); } #[test] - fn non_send_resource_points_to_distinct_data() { + fn non_send_points_to_distinct_data() { let mut world = World::default(); world.insert_resource(ResA(123)); - world.insert_non_send_resource(ResA(456)); + world.insert_non_send(ResA(456)); assert_eq!(*world.resource::(), ResA(123)); - assert_eq!(*world.non_send_resource::(), ResA(456)); + assert_eq!(*world.non_send::(), ResA(456)); } #[test] #[should_panic] - fn non_send_resource_panic() { + fn non_send_panic() { let mut world = World::default(); - world.insert_non_send_resource(0i32); + world.insert_non_send(0i32); std::thread::spawn(move || { - let _ = world.non_send_resource_mut::(); + let _ = world.non_send_mut::(); }) .join() .unwrap(); @@ -1562,8 +1562,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] @@ -1590,9 +1590,9 @@ mod tests { #[test] #[should_panic] - fn non_send_resource_drop_from_different_thread() { + fn non_send_drop_from_different_thread() { let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); + world.insert_non_send(NonSendA::default()); let thread = std::thread::spawn(move || { // Dropping the non-send resource on a different thread @@ -1606,9 +1606,9 @@ mod tests { } #[test] - fn non_send_resource_drop_from_same_thread() { + fn non_send_drop_from_same_thread() { let mut world = World::default(); - world.insert_non_send_resource(NonSendA::default()); + world.insert_non_send(NonSendA::default()); drop(world); } diff --git a/crates/bevy_ecs/src/lifecycle.rs b/crates/bevy_ecs/src/lifecycle.rs index adfb660e8b38a..260d93c8e2e24 100644 --- a/crates/bevy_ecs/src/lifecycle.rs +++ b/crates/bevy_ecs/src/lifecycle.rs @@ -315,15 +315,15 @@ impl ComponentHooks { } /// [`EventKey`] for [`Add`] -pub const ADD: EventKey = EventKey(ComponentId::new(0)); +pub const ADD: EventKey = EventKey(ComponentId::new(crate::component::ADD)); /// [`EventKey`] for [`Insert`] -pub const INSERT: EventKey = EventKey(ComponentId::new(1)); +pub const INSERT: EventKey = EventKey(ComponentId::new(crate::component::INSERT)); /// [`EventKey`] for [`Replace`] -pub const REPLACE: EventKey = EventKey(ComponentId::new(2)); +pub const REPLACE: EventKey = EventKey(ComponentId::new(crate::component::REPLACE)); /// [`EventKey`] for [`Remove`] -pub const REMOVE: EventKey = EventKey(ComponentId::new(3)); +pub const REMOVE: EventKey = EventKey(ComponentId::new(crate::component::REMOVE)); /// [`EventKey`] for [`Despawn`] -pub const DESPAWN: EventKey = EventKey(ComponentId::new(4)); +pub const DESPAWN: EventKey = EventKey(ComponentId::new(crate::component::DESPAWN)); /// Trigger emitted when a component is inserted onto an entity that does not already have that /// component. Runs before `Insert`. 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/state.rs b/crates/bevy_ecs/src/query/state.rs index 79ecf09b20176..e46df776724cd 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -2111,11 +2111,11 @@ mod tests { #[test] fn transmute_to_or_filter() { let mut world = World::new(); - world.spawn(()); - world.spawn(A(0)); + world.spawn(D); + world.spawn((A(0), D)); let mut query = world - .query::>() + .query::<(&D, Option<&A>)>() .transmute_filtered::,)>>(&world); let iter = query.iter(&world); let len = iter.len(); @@ -2126,7 +2126,7 @@ mod tests { assert_eq!(count, len); let mut query = world - .query::>() + .query::<(&D, Option<&A>)>() .transmute_filtered::,)>>(&world); let iter = query.iter(&world); let count = iter.count(); 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..d461021e08fe9 100644 --- a/crates/bevy_ecs/src/reflect/resource.rs +++ b/crates/bevy_ecs/src/reflect/resource.rs @@ -4,253 +4,38 @@ //! //! See the module doc for [`reflect::component`](`crate::reflect::component`). -use crate::{ - change_detection::Mut, - component::ComponentId, - resource::Resource, - world::{ - error::ResourceFetchError, unsafe_world_cell::UnsafeWorldCell, FilteredResources, - FilteredResourcesMut, World, - }, -}; -use bevy_reflect::{FromReflect, FromType, PartialReflect, Reflect, TypePath, TypeRegistry}; +use crate::{reflect::ReflectComponent, resource::Resource}; +use bevy_reflect::{FromReflect, FromType, TypePath, TypeRegistration}; -use super::from_reflect_with_fallback; - -/// A struct used to operate on reflected [`Resource`] of a type. +/// A struct that marks a 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 struct does not provide any functionality. +/// It implies the existence of a reflected [`Component`] of the same type, +/// which is meant to be used instead. /// -/// This is used when creating custom implementations of [`ReflectResource`] with -/// [`ReflectResource::new()`]. +/// ``` +/// #[derive(Resource, Reflect)] +/// #[reflect(Resource)] +/// struct ResA; /// -/// > **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. +/// // is the same as: /// -/// Creating a custom [`ReflectResource`] may be useful if you need to create new resource types at -/// runtime, for example, for scripting implementations. +/// #[derive(Resource, Component, Reflect)] +/// #[reflect(Resource, Component)] +/// struct ResA; +/// ``` /// -/// 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. +/// A [`ReflectResource`] for type `T` can be obtained via +/// [`bevy_reflect::TypeRegistration::data`]. #[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 - } -} - -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 - } -} +pub struct ReflectResource; 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); - }, + ReflectResource + } - register_resource: |world: &mut World| -> ComponentId { - world.register_resource::() - }, - }) + fn insert_dependencies(type_registration: &mut TypeRegistration) { + type_registration.register_type_data::(); } } diff --git a/crates/bevy_ecs/src/resource.rs b/crates/bevy_ecs/src/resource.rs index 7da4f31113f4f..3ec2d5892f57e 100644 --- a/crates/bevy_ecs/src/resource.rs +++ b/crates/bevy_ecs/src/resource.rs @@ -1,7 +1,21 @@ //! 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}, + 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. /// @@ -72,4 +86,138 @@ 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 {} + +/// The `on_add` component hook that maintains the uniqueness property of a resource. +pub fn resource_on_add_hook(mut world: DeferredWorld, context: HookContext) { + if let Some(&original_entity) = world.resource_entities.get(context.component_id) { + if !world.entities().contains(original_entity) { + let name = world + .components() + .get_name(context.component_id) + .expect("resource is registered"); + panic!( + "Resource entity {} of {} has been despawned, when it's not supposed to be.", + original_entity, name + ); + } + + if original_entity != context.entity { + // the resource already exists and the new one should be removed + world + .commands() + .entity(context.entity) + .remove_by_id(context.component_id); + let name = world + .components() + .get_name(context.component_id) + .expect("resource is registered"); + warn!("Tried inserting the resource {} while one already exists. + Resources are unique components stored on a single entity. + Inserting on a different entity, when one already exists, causes the new value to be removed.", name); + } + } else { + // SAFETY: We have exclusive world access (as long as we don't make structural changes). + let cache = unsafe { 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. +pub fn resource_on_despawn_hook(mut 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 { 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)] +pub struct ResourceEntities(SyncUnsafeCell>); + +impl Deref for ResourceEntities { + 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 ResourceEntities { + fn deref_mut(&mut self) -> &mut Self::Target { + self.0.get_mut() + } +} + +/// A marker component for entities that have a Resource component. +#[cfg_attr( + feature = "bevy_reflect", + derive(Reflect), + reflect(Component, Default, Debug) +)] +#[derive(Component, Debug, Default)] +pub struct IsResource; + +/// [`ComponentId`] of the [`IsResource`] component. +pub const IS_RESOURCE: ComponentId = ComponentId::new(crate::component::IS_RESOURCE); + +#[cfg(test)] +mod tests { + use crate::change_detection::MaybeLocation; + use crate::ptr::OwningPtr; + use crate::resource::Resource; + use crate::world::World; + use bevy_platform::prelude::String; + + #[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().count_spawned(); + world.init_resource::(); + assert_eq!(world.entities().count_spawned(), start + 1); + world.insert_resource(TestResource2(String::from("Foo"))); + assert_eq!(world.entities().count_spawned(), 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().count_spawned(), start + 2); + 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().count_spawned(), start + 3); + assert!(world.remove_resource_by_id(id)); + // the entity is stable: removing the resource should only remove the component from the entity, not despawn the entity + assert_eq!(world.entities().count_spawned(), start + 3); + // again, the entity is stable: see previous explanation + world.remove_resource::(); + assert_eq!(world.entities().count_spawned(), start + 3); + // 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().count_spawned(), start + 3); + } +} diff --git a/crates/bevy_ecs/src/schedule/mod.rs b/crates/bevy_ecs/src/schedule/mod.rs index f3b687dfc5ba7..11ee228d11f56 100644 --- a/crates/bevy_ecs/src/schedule/mod.rs +++ b/crates/bevy_ecs/src/schedule/mod.rs @@ -35,6 +35,7 @@ mod tests { pub use crate::{ prelude::World, + resource::IsResource, resource::Resource, schedule::{Schedule, SystemSet}, system::{Res, ResMut}, @@ -988,6 +989,16 @@ mod tests { let _ = schedule.initialize(&mut world); + // this should fail, since resources are components + assert_eq!(schedule.graph().conflicting_systems().len(), 1); + + schedule = Schedule::default(); + schedule.add_systems(( + resmut_system, + |_query: Query>| {}, + )); + + // this should not fail, since the queries are disjoint assert_eq!(schedule.graph().conflicting_systems().len(), 0); } @@ -1001,6 +1012,17 @@ mod tests { let _ = schedule.initialize(&mut world); + // this should fail, since resources are components + assert_eq!(schedule.graph().conflicting_systems().len(), 1); + + schedule = Schedule::default(); + schedule.add_systems(( + res_system, + nonsend_system, + |_query: Query>| {}, + )); + + // this should not fail, since the queries are disjoint assert_eq!(schedule.graph().conflicting_systems().len(), 0); } diff --git a/crates/bevy_ecs/src/storage/mod.rs b/crates/bevy_ecs/src/storage/mod.rs index 0b163b1b4feca..ea8b0cdcc08d4 100644 --- a/crates/bevy_ecs/src/storage/mod.rs +++ b/crates/bevy_ecs/src/storage/mod.rs @@ -21,12 +21,12 @@ //! [`World::storages`]: crate::world::World::storages mod blob_array; -mod resource; +mod non_send; mod sparse_set; mod table; mod thin_array_ptr; -pub use resource::*; +pub use non_send::*; pub use sparse_set::*; pub use table::*; @@ -41,10 +41,8 @@ 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, + /// Backing storage for `!Send` data. + pub non_sends: NonSends, } impl Storages { diff --git a/crates/bevy_ecs/src/storage/resource.rs b/crates/bevy_ecs/src/storage/non_send.rs similarity index 56% rename from crates/bevy_ecs/src/storage/resource.rs rename to crates/bevy_ecs/src/storage/non_send.rs index d60bc16921cb3..44b2fc26f53ea 100644 --- a/crates/bevy_ecs/src/storage/resource.rs +++ b/crates/bevy_ecs/src/storage/non_send.rs @@ -1,8 +1,5 @@ use crate::{ - change_detection::{ - CheckChangeTicks, ComponentTickCells, ComponentTicks, ComponentTicksMut, MaybeLocation, - MutUntyped, Tick, - }, + change_detection::{CheckChangeTicks, ComponentTickCells, ComponentTicks, MaybeLocation, Tick}, component::{ComponentId, Components}, storage::{blob_array::BlobArray, SparseSet}, }; @@ -18,7 +15,7 @@ use std::thread::ThreadId; /// If `SEND` is false, values of this type will panic if dropped from a different thread. /// /// [`World`]: crate::world::World -pub struct ResourceData { +pub struct NonSendData { /// Capacity is 1, length is 1 if `is_present` and 0 otherwise. data: BlobArray, is_present: bool, @@ -34,12 +31,11 @@ pub struct ResourceData { changed_by: MaybeLocation>>, } -impl Drop for ResourceData { +impl Drop for NonSendData { fn drop(&mut self) { - // For Non Send resources we need to validate that correct thread - // is dropping the resource. This validation is not needed in case - // of SEND resources. Or if there is no data. - if !SEND && self.is_present() { + // We need to validate that correct thread is dropping the data. + // This validation is not needed if there is no data. + if self.is_present() { // If this thread is already panicking, panicking again will cause // the entire process to abort. In this case we choose to avoid // dropping or checking this altogether and just leak the column. @@ -49,8 +45,8 @@ impl Drop for ResourceData { } self.validate_access(); } - // SAFETY: Drop is only called once upon dropping the ResourceData - // and is inaccessible after this as the parent ResourceData has + // SAFETY: Drop is only called once upon dropping the NonSendData + // and is inaccessible after this as the parent NonSendData has // been dropped. The validate_access call above will check that the // data is dropped on the thread it was inserted from. unsafe { @@ -59,33 +55,31 @@ impl Drop for ResourceData { } } -impl ResourceData { +impl NonSendData { /// The only row in the underlying `BlobArray`. const ROW: usize = 0; - /// Validates the access to `!Send` resources is only done on the thread they were created from. + /// Validates that the access to `NonSendData` is only done on the thread they were created from. /// /// # Panics - /// If `SEND` is false, this will panic if called from a different thread than the one it was inserted from. + /// This will panic if called from a different thread than the one it was inserted from. #[inline] fn validate_access(&self) { - if !SEND { - #[cfg(feature = "std")] - if self.origin_thread_id != Some(std::thread::current().id()) { - // Panic in tests, as testing for aborting is nearly impossible - panic!( - "Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.", - self.type_name, - self.origin_thread_id, - std::thread::current().id() - ); - } - - // TODO: Handle no_std non-send. - // Currently, no_std is single-threaded only, so this is safe to ignore. - // To support no_std multithreading, an alternative will be required. - // Remove the #[expect] attribute above when this is addressed. + #[cfg(feature = "std")] + if self.origin_thread_id != Some(std::thread::current().id()) { + // Panic in tests, as testing for aborting is nearly impossible + panic!( + "Attempted to access or drop non-send resource {} from thread {:?} on a thread {:?}. This is not allowed. Aborting.", + self.type_name, + self.origin_thread_id, + std::thread::current().id() + ); } + + // TODO: Handle no_std non-send. + // Currently, no_std is single-threaded only, so this is safe to ignore. + // To support no_std multithreading, an alternative will be required. + // Remove the #[expect] attribute above when this is addressed. } /// Returns true if the resource is populated. @@ -97,8 +91,7 @@ impl ResourceData { /// Returns a reference to the resource, if it exists. /// /// # Panics - /// If `SEND` is false, this will panic if a value is present and is not accessed from the - /// original thread it was inserted from. + /// This will panic if a value is present and is not accessed from the original thread it was inserted from. #[inline] pub fn get_data(&self) -> Option> { self.is_present().then(|| { @@ -124,8 +117,7 @@ impl ResourceData { /// Returns references to the resource and its change ticks, if it exists. /// /// # 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. + /// This will panic if a value is present and is not accessed from the original thread it was inserted in. #[inline] pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, ComponentTickCells<'_>)> { self.is_present().then(|| { @@ -142,27 +134,11 @@ impl ResourceData { }) } - /// Returns a mutable reference to the resource, if it exists. - /// - /// # 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. - pub(crate) fn get_mut(&mut self, last_run: Tick, this_run: Tick) -> Option> { - let (ptr, ticks) = self.get_with_ticks()?; - Some(MutUntyped { - // SAFETY: We have exclusive access to the underlying storage. - value: unsafe { ptr.assert_unique() }, - // SAFETY: We have exclusive access to the underlying storage. - ticks: unsafe { ComponentTicksMut::from_tick_cells(ticks, last_run, this_run) }, - }) - } - /// Inserts a value into the resource. If a value is already present /// it will be replaced. /// /// # Panics - /// If `SEND` is false, this will panic if a value is present and is not replaced from - /// the original thread it was inserted in. + /// This will panic if a value is present and is not replaced from the original thread it was inserted in. /// /// # Safety /// - `value` must be valid for the underlying type for the resource. @@ -181,7 +157,7 @@ impl ResourceData { unsafe { self.data.replace_unchecked(Self::ROW, value) }; } else { #[cfg(feature = "std")] - if !SEND { + { self.origin_thread_id = Some(std::thread::current().id()); } // SAFETY: @@ -201,63 +177,17 @@ impl ResourceData { .assign(caller); } - /// Inserts a value into the resource with a pre-existing change tick. If a - /// value is already present it will be replaced. - /// - /// # Panics - /// If `SEND` is false, this will panic if a value is present and is not replaced from - /// the original thread it was inserted in. - /// - /// # Safety - /// - `value` must be valid for the underlying type for the resource. - #[inline] - pub(crate) unsafe fn insert_with_ticks( - &mut self, - value: OwningPtr<'_>, - change_ticks: ComponentTicks, - caller: MaybeLocation, - ) { - if self.is_present() { - self.validate_access(); - // SAFETY: The caller ensures that the provided value is valid for the underlying type and - // is properly initialized. We've ensured that a value is already present and previously - // initialized. - unsafe { self.data.replace_unchecked(Self::ROW, value) }; - } else { - #[cfg(feature = "std")] - if !SEND { - self.origin_thread_id = Some(std::thread::current().id()); - } - // SAFETY: - // - There is only one element, and it's always allocated. - // - The caller guarantees must be valid for the underlying type and thus its - // layout must be identical. - // - The value was previously not present and thus must not have been initialized. - unsafe { self.data.initialize_unchecked(Self::ROW, value) }; - self.is_present = true; - } - *self.added_ticks.deref_mut() = change_ticks.added; - *self.changed_ticks.deref_mut() = change_ticks.changed; - self.changed_by - .as_ref() - .map(|changed_by| changed_by.deref_mut()) - .assign(caller); - } - /// Removes a value from the resource, if present. /// /// # Panics - /// If `SEND` is false, this will panic if a value is present and is not removed from the - /// original thread it was inserted from. + /// This will panic if a value is present and is not removed from the original thread it was inserted from. #[inline] #[must_use = "The returned pointer to the removed component should be used or dropped"] pub(crate) fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks, MaybeLocation)> { if !self.is_present() { return None; } - if !SEND { - self.validate_access(); - } + self.validate_access(); self.is_present = false; @@ -290,8 +220,7 @@ impl ResourceData { /// Removes a value from the resource, if present, and drops it. /// /// # 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. + /// This will panic if a value is present and is not accessed from the original thread it was inserted in. #[inline] pub(crate) fn remove_and_drop(&mut self) { if self.is_present() { @@ -313,79 +242,67 @@ impl ResourceData { /// [`Resource`]: crate::resource::Resource /// [`World`]: crate::world::World #[derive(Default)] -pub struct Resources { - resources: SparseSet>, +pub struct NonSends { + non_sends: SparseSet, } -impl Resources { - /// The total number of resources stored in the [`World`] +impl NonSends { + /// The total amount of `!Send` data stored in the [`World`] /// /// [`World`]: crate::world::World #[inline] pub fn len(&self) -> usize { - self.resources.len() + self.non_sends.len() } /// Iterate over all resources that have been initialized, i.e. given a [`ComponentId`] - pub fn iter(&self) -> impl Iterator)> { - self.resources.iter().map(|(id, data)| (*id, data)) + pub fn iter(&self) -> impl Iterator { + self.non_sends.iter().map(|(id, data)| (*id, data)) } - /// Returns true if there are no resources stored in the [`World`], + /// Returns true if there is no `!Send` data stored in the [`World`], /// false otherwise. /// /// [`World`]: crate::world::World #[inline] pub fn is_empty(&self) -> bool { - self.resources.is_empty() + self.non_sends.is_empty() } - /// Gets read-only access to a resource, if it exists. + /// Gets read-only access to some `!Send` data, if it exists. #[inline] - pub fn get(&self, component_id: ComponentId) -> Option<&ResourceData> { - self.resources.get(component_id) + pub fn get(&self, component_id: ComponentId) -> Option<&NonSendData> { + self.non_sends.get(component_id) } /// Clears all resources. #[inline] pub fn clear(&mut self) { - self.resources.clear(); + self.non_sends.clear(); } - /// Gets mutable access to a resource, if it exists. + /// Gets mutable access to `!Send` data, if it exists. #[inline] - pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut ResourceData> { - self.resources.get_mut(component_id) + pub(crate) fn get_mut(&mut self, component_id: ComponentId) -> Option<&mut NonSendData> { + self.non_sends.get_mut(component_id) } - /// Fetches or initializes a new resource and returns back its underlying column. + /// Fetches or initializes new `!Send` data and returns back its underlying column. /// /// # Panics /// Will panic if `component_id` is not valid for the provided `components` - /// If `SEND` is true, this will panic if `component_id`'s `ComponentInfo` is not registered as being `Send` + `Sync`. pub(crate) fn initialize_with( &mut self, component_id: ComponentId, components: &Components, - ) -> &mut ResourceData { - self.resources.get_or_insert_with(component_id, || { + ) -> &mut NonSendData { + self.non_sends.get_or_insert_with(component_id, || { let component_info = components.get_info(component_id).unwrap(); - if SEND { - assert!( - component_info.is_send_and_sync(), - "Send + Sync resource {} initialized as non_send. It may have been inserted via World::insert_non_send_resource by accident. Try using World::insert_resource instead.", - component_info.name(), - ); - } // SAFETY: component_info.drop() is valid for the types that will be inserted. let data = unsafe { - BlobArray::with_capacity( - component_info.layout(), - component_info.drop(), - 1 - ) + BlobArray::with_capacity(component_info.layout(), component_info.drop(), 1) }; - ResourceData { + NonSendData { data, is_present: false, added_ticks: UnsafeCell::new(Tick::new(0)), @@ -399,7 +316,7 @@ impl Resources { } pub(crate) fn check_change_ticks(&mut self, check: CheckChangeTicks) { - for info in self.resources.values_mut() { + for info in self.non_sends.values_mut() { info.check_change_ticks(check); } } diff --git a/crates/bevy_ecs/src/system/builder.rs b/crates/bevy_ecs/src/system/builder.rs index 937911ca834c1..f63b39db6b8bd 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::*; @@ -1025,31 +1025,4 @@ mod tests { .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_ecs/src/system/mod.rs b/crates/bevy_ecs/src/system/mod.rs index eeecf4b7ac5db..50260b9ee6f9e 100644 --- a/crates/bevy_ecs/src/system/mod.rs +++ b/crates/bevy_ecs/src/system/mod.rs @@ -1006,7 +1006,7 @@ mod tests { // existence. struct NotSend1(alloc::rc::Rc); struct NotSend2(alloc::rc::Rc); - world.insert_non_send_resource(NotSend1(alloc::rc::Rc::new(0))); + world.insert_non_send(NotSend1(alloc::rc::Rc::new(0))); fn sys( op: Option>, @@ -1034,8 +1034,8 @@ mod tests { struct NotSend1(alloc::rc::Rc); struct NotSend2(alloc::rc::Rc); - world.insert_non_send_resource(NotSend1(alloc::rc::Rc::new(1))); - world.insert_non_send_resource(NotSend2(alloc::rc::Rc::new(2))); + world.insert_non_send(NotSend1(alloc::rc::Rc::new(1))); + world.insert_non_send(NotSend2(alloc::rc::Rc::new(2))); fn sys( _op: NonSend, diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index a69a1355a66e1..1ad8fb84b64ff 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -501,22 +501,22 @@ mod tests { #[test] fn command_processing() { let mut world = World::new(); - assert_eq!(world.entities.count_spawned(), 0); + assert_eq!(world.query::<&A>().query(&world).count(), 0); world.run_system_once(spawn_entity).unwrap(); - assert_eq!(world.entities.count_spawned(), 1); + assert_eq!(world.query::<&A>().query(&world).count(), 1); } #[test] - fn non_send_resources() { + fn non_send() { fn non_send_count_down(mut ns: NonSendMut) { ns.0 -= 1; } let mut world = World::new(); - world.insert_non_send_resource(Counter(10)); - assert_eq!(*world.non_send_resource::(), Counter(10)); + world.insert_non_send(Counter(10)); + assert_eq!(*world.non_send::(), Counter(10)); world.run_system_once(non_send_count_down).unwrap(); - assert_eq!(*world.non_send_resource::(), Counter(9)); + assert_eq!(*world.non_send::(), Counter(9)); } #[test] diff --git a/crates/bevy_ecs/src/system/system_param.rs b/crates/bevy_ecs/src/system/system_param.rs index 80d449017d2eb..1d97fe1b664cb 100644 --- a/crates/bevy_ecs/src/system/system_param.rs +++ b/crates/bevy_ecs/src/system/system_param.rs @@ -9,8 +9,8 @@ use crate::{ Access, FilteredAccess, FilteredAccessSet, QueryData, QueryFilter, QuerySingleError, QueryState, ReadOnlyQueryData, }, - resource::Resource, - storage::ResourceData, + resource::{Resource, IS_RESOURCE}, + storage::NonSendData, system::{Query, Single, SystemMeta}, world::{ unsafe_world_cell::UnsafeWorldCell, DeferredWorld, FilteredResources, FilteredResourcesMut, @@ -771,7 +771,20 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { system_meta.name, ); - component_access_set.add_unfiltered_resource_read(component_id); + let mut filter = FilteredAccess::default(); + filter.add_component_read(component_id); + filter.add_resource_read(component_id); + filter.and_with(IS_RESOURCE); + + assert!(component_access_set + .get_conflicts_single(&filter) + .is_empty(), + "error[B0002]: Res<{}> in system {} conflicts with a previous query. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", + DebugName::type_name::(), + system_meta.name + ); + + component_access_set.add(filter); } #[inline] @@ -780,11 +793,10 @@ unsafe impl<'a, T: Resource> SystemParam for Res<'a, T> { _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) + // SAFETY: Read-only access to the resource + if let Some(entity) = unsafe { world.resource_entities() }.get(component_id) + && let Ok(entity_ref) = world.get_entity(*entity) + && entity_ref.contains_id(component_id) { Ok(()) } else { @@ -849,7 +861,21 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { "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); + + let mut filter = FilteredAccess::default(); + filter.add_component_write(component_id); + filter.add_resource_write(component_id); + filter.and_with(IS_RESOURCE); + + assert!(component_access_set + .get_conflicts_single(&filter) + .is_empty(), + "error[B0002]: Res<{}> in system {} conflicts with a previous query. Consider removing the duplicate access. See: https://bevy.org/learn/errors/b0002", + DebugName::type_name::(), + system_meta.name + ); + + component_access_set.add(filter); } #[inline] @@ -858,11 +884,10 @@ unsafe impl<'a, T: Resource> SystemParam for ResMut<'a, T> { _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) + // SAFETY: Read-only access to the resource. + if let Some(entity) = unsafe { world.resource_entities() }.get(component_id) + && let Ok(entity_ref) = world.get_entity(*entity) + && entity_ref.contains_id(component_id) { Ok(()) } else { @@ -1430,11 +1455,11 @@ unsafe impl<'a, T: 'static> SystemParam for NonSend<'a, T> { _system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. + // SAFETY: Read-only access to non-send metadata. if unsafe { world.storages() } - .non_send_resources + .non_sends .get(component_id) - .is_some_and(ResourceData::is_present) + .is_some_and(NonSendData::is_present) { Ok(()) } else { @@ -1504,11 +1529,11 @@ unsafe impl<'a, T: 'static> SystemParam for NonSendMut<'a, T> { _system_meta: &SystemMeta, world: UnsafeWorldCell, ) -> Result<(), SystemParamValidationError> { - // SAFETY: Read-only access to resource metadata. + // SAFETY: Read-only access to non-send metadata. if unsafe { world.storages() } - .non_send_resources + .non_sends .get(component_id) - .is_some_and(ResourceData::is_present) + .is_some_and(NonSendData::is_present) { Ok(()) } else { @@ -2868,7 +2893,7 @@ mod tests { res1.0 += 1; } let mut world = World::new(); - world.insert_non_send_resource(A(42)); + world.insert_non_send(A(42)); let mut schedule = crate::schedule::Schedule::default(); schedule.add_systems(my_system); schedule.run(&mut world); @@ -3073,7 +3098,7 @@ mod tests { } let mut world = World::new(); - world.insert_non_send_resource(core::ptr::null_mut::()); + world.insert_non_send(core::ptr::null_mut::()); let mut schedule = crate::schedule::Schedule::default(); schedule.add_systems((non_send_param_set, non_send_param_set, non_send_param_set)); schedule.run(&mut world); @@ -3088,7 +3113,7 @@ mod tests { } let mut world = World::new(); - world.insert_non_send_resource(core::ptr::null_mut::()); + world.insert_non_send(core::ptr::null_mut::()); let mut schedule = crate::schedule::Schedule::default(); schedule.add_systems((non_send_param_set, non_send_param_set, non_send_param_set)); schedule.run(&mut world); diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index cb67888873b77..f70236888c80a 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -488,37 +488,37 @@ impl<'w> DeferredWorld<'w> { unsafe { self.world.get_resource_mut() } } - /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Gets a mutable reference to the non-send data of the given type, if it exists. /// /// # Panics /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. + /// Panics if the data does not exist. + /// Use [`get_non_send_mut`](World::get_non_send_mut) instead if you want to handle this case. /// - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. #[inline] #[track_caller] - pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { - match self.get_non_send_resource_mut() { + pub fn non_send_mut(&mut self) -> Mut<'_, R> { + match self.get_non_send_mut() { Some(x) => x, None => panic!( - "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? - Non-send resources can also be added by plugins.", + "Requested non-send data {} does not exist in the `World`. + Did you forget to add it using `app.insert_non_send` / `app.init_non_send`? + Non-send data can also be added by plugins.", DebugName::type_name::() ), } } - /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Gets a mutable reference to non-send data of the given type, if it exists. /// Otherwise returns `None`. /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. #[inline] - pub fn get_non_send_resource_mut(&mut self) -> Option> { - // SAFETY: &mut self ensure that there are no outstanding accesses to the resource - unsafe { self.world.get_non_send_resource_mut() } + pub fn get_non_send_mut(&mut self) -> Option> { + // SAFETY: &mut self ensure that there are no outstanding accesses to the data + unsafe { self.world.get_non_send_mut() } } /// Writes a [`Message`]. @@ -567,19 +567,19 @@ impl<'w> DeferredWorld<'w> { unsafe { self.world.get_resource_mut_by_id(component_id) } } - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// Gets mutable access to `!Send` data with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the data, as long as the mutable borrow /// of the [`World`] is still valid. /// - /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only - /// use this in cases where the actual types are not known at compile time.** + /// **You should prefer to use the typed API [`DeferredWorld::get_non_send_mut`] where possible + /// and only use this in cases where the actual types are not known at compile time.** /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { - // SAFETY: &mut self ensure that there are no outstanding accesses to the resource - unsafe { self.world.get_non_send_resource_mut_by_id(component_id) } + // SAFETY: &mut self ensure that there are no outstanding accesses to the data + unsafe { self.world.get_non_send_mut_by_id(component_id) } } /// Retrieves a mutable untyped reference to the given `entity`'s [`Component`] of the given [`ComponentId`]. 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 9978c775bc4b7..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,14 @@ 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::() } + } + /// 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/entity_access/mod.rs b/crates/bevy_ecs/src/world/entity_access/mod.rs index 187540f868995..d16e20a9474a9 100644 --- a/crates/bevy_ecs/src/world/entity_access/mod.rs +++ b/crates/bevy_ecs/src/world/entity_access/mod.rs @@ -27,6 +27,7 @@ mod tests { change_detection::{MaybeLocation, MutUntyped}, component::ComponentId, prelude::*, + resource::IsResource, system::{assert_is_system, RunSystemOnce as _}, world::{error::EntityComponentError, DeferredWorld, FilteredEntityMut, FilteredEntityRef}, }; @@ -666,12 +667,20 @@ mod tests { } #[test] - fn ref_compatible_with_resource_mut() { + #[should_panic] + fn ref_incompatible_with_resource_mut() { fn borrow_system(_: Query, _: ResMut) {} 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() { @@ -696,19 +705,35 @@ mod tests { } #[test] - fn mut_compatible_with_resource() { + #[should_panic] + fn mut_incompatible_with_resource() { fn borrow_mut_system(_: Res, _: Query) {} assert_is_system(borrow_mut_system); } #[test] - fn mut_compatible_with_resource_mut() { + #[should_panic] + fn mut_incompatible_with_resource_mut() { fn borrow_mut_system(_: ResMut, _: Query) {} 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() { 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 1ca4e24c75f5b..afa335ba7a78f 100644 --- a/crates/bevy_ecs/src/world/entity_access/world_mut.rs +++ b/crates/bevy_ecs/src/world/entity_access/world_mut.rs @@ -628,6 +628,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/filtered_resource.rs b/crates/bevy_ecs/src/world/filtered_resource.rs index 703d240964c96..279f3bc8abf42 100644 --- a/crates/bevy_ecs/src/world/filtered_resource.rs +++ b/crates/bevy_ecs/src/world/filtered_resource.rs @@ -558,6 +558,9 @@ impl<'w> FilteredResourcesBuilder<'w> { /// Add accesses required to read all resources. pub fn add_read_all(&mut self) -> &mut Self { self.access.read_all_resources(); + for &component_id in self.world.resource_entities().indices() { + self.access.add_component_read(component_id); + } self } @@ -570,6 +573,7 @@ impl<'w> FilteredResourcesBuilder<'w> { /// Add accesses required to read the resource with the given [`ComponentId`]. pub fn add_read_by_id(&mut self, component_id: ComponentId) -> &mut Self { self.access.add_resource_read(component_id); + self.access.add_component_read(component_id); self } @@ -604,6 +608,9 @@ impl<'w> FilteredResourcesMutBuilder<'w> { /// Add accesses required to read all resources. pub fn add_read_all(&mut self) -> &mut Self { self.access.read_all_resources(); + for &component_id in self.world.resource_entities().indices() { + self.access.add_component_read(component_id); + } self } @@ -616,12 +623,16 @@ impl<'w> FilteredResourcesMutBuilder<'w> { /// Add accesses required to read the resource with the given [`ComponentId`]. pub fn add_read_by_id(&mut self, component_id: ComponentId) -> &mut Self { self.access.add_resource_read(component_id); + self.access.add_component_read(component_id); self } /// Add accesses required to get mutable access to all resources. pub fn add_write_all(&mut self) -> &mut Self { self.access.write_all_resources(); + for &component_id in self.world.resource_entities().indices() { + self.access.add_component_write(component_id); + } self } @@ -634,6 +645,7 @@ impl<'w> FilteredResourcesMutBuilder<'w> { /// Add accesses required to get mutable access to the resource with the given [`ComponentId`]. pub fn add_write_by_id(&mut self, component_id: ComponentId) -> &mut Self { self.access.add_resource_write(component_id); + self.access.add_component_write(component_id); self } diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index a4f49c7460a35..2966bd6fb1147 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, @@ -49,12 +49,12 @@ use crate::{ lifecycle::{ComponentHooks, RemovedComponentMessages, ADD, DESPAWN, INSERT, REMOVE, REPLACE}, message::{Message, MessageId, Messages, WriteBatchIds}, observer::Observers, - prelude::{Add, Despawn, Insert, Remove, Replace}, + prelude::{Add, Despawn, DetectChangesMut, Insert, Remove, Replace, Without}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, relationship::RelationshipHookMode, - resource::Resource, + resource::{IsResource, Resource, ResourceEntities, IS_RESOURCE}, schedule::{Schedule, ScheduleLabel, Schedules}, - storage::{ResourceData, Storages}, + storage::{NonSendData, Storages}, system::Commands, world::{ command_queue::RawCommandQueue, @@ -86,8 +86,9 @@ use unsafe_world_cell::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, @@ -95,6 +96,7 @@ pub struct World { pub(crate) allocator: EntityAllocator, pub(crate) components: Components, pub(crate) component_ids: ComponentIds, + pub(crate) resource_entities: ResourceEntities, pub(crate) archetypes: Archetypes, pub(crate) storages: Storages, pub(crate) bundles: Bundles, @@ -114,6 +116,7 @@ impl Default for World { entities: Entities::new(), allocator: EntityAllocator::default(), components: Default::default(), + resource_entities: Default::default(), archetypes: Archetypes::new(), storages: Default::default(), bundles: Default::default(), @@ -167,6 +170,9 @@ impl World { let on_despawn = self.register_event_key::(); assert_eq!(DESPAWN, on_despawn); + let is_resource = self.register_component::(); + assert_eq!(IS_RESOURCE, is_resource); + // This sets up `Disabled` as a disabling component, via the FromWorld impl self.init_resource::(); } @@ -248,6 +254,12 @@ impl World { &self.components } + /// Retrieves this world's [`ResourceCache`]. + #[inline] + pub fn resource_entities(&self) -> &ResourceEntities { + &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. @@ -1793,21 +1805,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. @@ -1820,23 +1832,27 @@ impl World { #[inline] #[track_caller] pub fn init_resource(&mut self) -> ComponentId { + let resource_id = self.register_resource::(); 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); - } - }); + + if let Some(&entity) = self.resource_entities.get(resource_id) { + let entity_ref = self.get_entity(entity).expect("ResourceCache is in sync"); + if !entity_ref.contains_id(resource_id) { + let resource = R::from_world(self); + move_as_ptr!(resource); + self.entity_mut(entity).insert_with_caller( + resource, + InsertMode::Replace, + caller, + RelationshipHookMode::Run, + ); + } + } else { + let resource = R::from_world(self); + move_as_ptr!(resource); + self.spawn_with_caller(resource, caller); // ResourceCache is updated automatically } - component_id + resource_id } /// Inserts a new resource with the given `value`. @@ -1867,25 +1883,25 @@ impl World { }); } - /// Initializes a new non-send resource and returns the [`ComponentId`] created for it. + /// Initializes new non-send data and returns the [`ComponentId`] created for it. /// - /// If the resource already exists, nothing happens. + /// If the data already exists, nothing happens. /// /// The value given by the [`FromWorld::from_world`] method will be used. - /// Note that any resource with the `Default` trait automatically implements `FromWorld`, - /// and those default values will be here instead. + /// Note that any non-send data with the `Default` trait automatically implements + /// `FromWorld`, and those default values will be here instead. /// /// # Panics /// /// Panics if called from a thread other than the main thread. #[inline] #[track_caller] - pub fn init_non_send_resource(&mut self) -> ComponentId { + pub fn init_non_send(&mut self) -> ComponentId { let caller = MaybeLocation::caller(); let component_id = self.components_registrator().register_non_send::(); if self .storages - .non_send_resources + .non_sends .get(component_id) .is_none_or(|data| !data.is_present()) { @@ -1900,9 +1916,9 @@ impl World { component_id } - /// Inserts a new non-send resource with the given `value`. + /// Inserts new non-send data with the given `value`. /// - /// `NonSend` resources cannot be sent across threads, + /// `NonSend` data cannot be sent across threads, /// and do not need the `Send + Sync` bounds. /// Systems with `NonSend` resources are always scheduled on the main thread. /// @@ -1911,11 +1927,11 @@ impl World { /// from a different thread than where the original value was inserted from. #[inline] #[track_caller] - pub fn insert_non_send_resource(&mut self, value: R) { + pub fn insert_non_send(&mut self, value: R) { let caller = MaybeLocation::caller(); let component_id = self.components_registrator().register_non_send::(); OwningPtr::make(value, |ptr| { - // SAFETY: component_id was just initialized and corresponds to resource of type R. + // SAFETY: component_id was just initialized and corresponds to the data of type R. unsafe { self.insert_non_send_by_id(component_id, ptr, caller); } @@ -1925,17 +1941,20 @@ 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 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) } - /// Removes a `!Send` resource from the world and returns it, if present. + /// Removes `!Send` data from the world and returns it, if present. /// /// `NonSend` resources cannot be sent across threads, /// and do not need the `Send + Sync` bounds. - /// Systems with `NonSend` resources are always scheduled on the main thread. + /// Systems with `NonSend` data are always scheduled on the main thread. /// /// Returns `None` if a value was not previously present. /// @@ -1943,13 +1962,9 @@ impl World { /// If a value is present, this function will panic if called from a different /// thread than where the value was inserted from. #[inline] - pub fn remove_non_send_resource(&mut self) -> Option { + pub fn remove_non_send(&mut self) -> Option { let component_id = self.components.get_valid_resource_id(TypeId::of::())?; - let (ptr, _, _) = self - .storages - .non_send_resources - .get_mut(component_id)? - .remove()?; + let (ptr, _, _) = self.storages.non_sends.get_mut(component_id)?.remove()?; // SAFETY: `component_id` was gotten via looking up the `R` type unsafe { Some(ptr.read::()) } } @@ -1959,35 +1974,36 @@ 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.resource_entities.get(component_id) + && let Ok(entity_ref) = self.get_entity(*entity) + { + return entity_ref.contains_id(component_id) && entity_ref.contains::(); + } + false } - /// Returns `true` if a resource of type `R` exists. Otherwise returns `false`. + /// Returns `true` if `!Send` data of type `R` exists. Otherwise returns `false`. #[inline] pub fn contains_non_send(&self) -> bool { self.components .get_valid_resource_id(TypeId::of::()) - .and_then(|component_id| self.storages.non_send_resources.get(component_id)) - .is_some_and(ResourceData::is_present) + .and_then(|component_id| self.storages.non_sends.get(component_id)) + .is_some_and(NonSendData::is_present) } - /// Returns `true` if a resource with provided `component_id` exists. Otherwise returns `false`. + /// Returns `true` if `!Send` data with `component_id` exists. Otherwise returns `false`. #[inline] pub fn contains_non_send_by_id(&self, component_id: ComponentId) -> bool { self.storages - .non_send_resources + .non_sends .get(component_id) - .is_some_and(ResourceData::is_present) + .is_some_and(NonSendData::is_present) } /// Returns `true` if a resource of type `R` exists and was added since the world's @@ -2011,14 +2027,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 @@ -2042,14 +2052,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. @@ -2066,10 +2070,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 @@ -2192,28 +2195,10 @@ 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_with_caller(func(), MaybeLocation::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::() } + self.get_resource_mut::().unwrap() // must exist } /// Gets a mutable reference to the resource of type `T` if it exists, @@ -2250,110 +2235,81 @@ 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. + /// Gets an immutable reference to the non-send data of the given type, if it exists. /// /// # Panics /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource`](World::get_non_send_resource) instead if you want to handle this case. + /// Panics if the data does not exist. + /// Use [`get_non_send`](World::get_non_send) instead if you want to handle this case. /// /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] #[track_caller] - pub fn non_send_resource(&self) -> &R { - match self.get_non_send_resource() { + pub fn non_send(&self) -> &R { + match self.get_non_send() { Some(x) => x, None => panic!( "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? + Did you forget to add it using `app.insert_non_send` / `app.init_non_send`? Non-send resources can also be added by plugins.", DebugName::type_name::() ), } } - /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Gets a mutable reference to the non-send data of the given type, if it exists. /// /// # Panics /// - /// Panics if the resource does not exist. - /// Use [`get_non_send_resource_mut`](World::get_non_send_resource_mut) instead if you want to handle this case. + /// Panics if the data does not exist. + /// Use [`get_non_send_mut`](World::get_non_send_mut) instead if you want to handle this case. /// /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] #[track_caller] - pub fn non_send_resource_mut(&mut self) -> Mut<'_, R> { - match self.get_non_send_resource_mut() { + pub fn non_send_mut(&mut self) -> Mut<'_, R> { + match self.get_non_send_mut() { Some(x) => x, None => panic!( "Requested non-send resource {} does not exist in the `World`. - Did you forget to add it using `app.insert_non_send_resource` / `app.init_non_send_resource`? + Did you forget to add it using `app.insert_non_send` / `app.init_non_send`? Non-send resources can also be added by plugins.", DebugName::type_name::() ), } } - /// Gets a reference to the non-send resource of the given type, if it exists. + /// Gets a reference to the non-send data of the given type, if it exists. /// Otherwise returns `None`. /// /// # Panics /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] - pub fn get_non_send_resource(&self) -> Option<&R> { + pub fn get_non_send(&self) -> Option<&R> { // SAFETY: // - `as_unsafe_world_cell_readonly` gives permission to access the entire world immutably // - `&self` ensures that there are no mutable borrows of world data - unsafe { self.as_unsafe_world_cell_readonly().get_non_send_resource() } + unsafe { self.as_unsafe_world_cell_readonly().get_non_send() } } - /// Gets a mutable reference to the non-send resource of the given type, if it exists. + /// Gets a mutable reference to the non-send data of the given type, if it exists. /// Otherwise returns `None`. /// /// # Panics /// This function will panic if it isn't called from the same thread that the resource was inserted from. #[inline] - pub fn get_non_send_resource_mut(&mut self) -> Option> { + pub fn get_non_send_mut(&mut self) -> Option> { // SAFETY: // - `as_unsafe_world_cell` gives permission to access the entire world mutably // - `&mut self` ensures that there are no borrows of world data - unsafe { self.as_unsafe_world_cell().get_non_send_resource_mut() } + unsafe { self.as_unsafe_world_cell().get_non_send_mut() } } /// For a given batch of ([`Entity`], [`Bundle`]) pairs, @@ -2720,39 +2676,80 @@ 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::()?; + let value_mut = Mut { value: &mut value, ticks: ComponentTicksMut { added: &mut ticks.added, changed: &mut ticks.changed, - changed_by: caller.as_mut(), + changed_by: changed_by.as_mut(), last_run: last_change_tick, this_run: change_tick, }, }; + 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."); + + 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 { R::apply_effect(bundle, &mut entity_mut) }; + } Some(result) } @@ -2806,19 +2803,26 @@ 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); - } + // if the resource already exists, we replace it on the same entity + let mut entity_mut = if let Some(entity) = self.resource_entities.get(component_id) { + self.get_entity_mut(*entity) + .expect("ResourceCache is in sync") + } else { + self.spawn_empty() + }; + entity_mut.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 + /// Inserts new `!Send` data with the given `value`. Will replace the value if it already /// existed. /// - /// **You should prefer to use the typed API [`World::insert_non_send_resource`] where possible and only + /// **You should prefer to use the typed API [`World::insert_non_send`] where possible and only /// use this in cases where the actual types are not known at compile time.** /// /// # Panics @@ -2844,29 +2848,16 @@ 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] pub(crate) fn initialize_non_send_internal( &mut self, component_id: ComponentId, - ) -> &mut ResourceData { + ) -> &mut NonSendData { self.flush_components(); self.storages - .non_send_resources + .non_sends .initialize_with(component_id, &self.components) } @@ -3089,16 +3080,14 @@ impl World { let Storages { ref mut tables, ref mut sparse_sets, - ref mut resources, - ref mut non_send_resources, + ref mut non_sends, } = self.storages; #[cfg(feature = "trace")] 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); + non_sends.check_change_ticks(check); self.entities.check_change_ticks(check); if let Some(mut schedules) = self.get_resource_mut::() { @@ -3116,12 +3105,6 @@ 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(); @@ -3129,6 +3112,27 @@ impl World { self.allocator.restart(); } + /// Despawns all entities in this [`World`]. + /// + /// **Note:** This includes all resources, as they are stored as components. + /// Any resource fetch to this [`World`] will fail unless they are re-initialized, + /// including engine-internal resources that are only initialized on app/world construction. + /// + /// This can easily cause systems expecting certain resources to immediately start panicking. + /// Use with caution. + 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, @@ -3137,8 +3141,19 @@ 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(); - self.storages.non_send_resources.clear(); + let pairs: Vec<(ComponentId, Entity)> = self + .resource_entities() + .iter() + .map(|(id, entity)| (*id, *entity)) + .collect(); + for (component_id, entity) in pairs { + self.entity_mut(entity).remove_by_id(component_id); + } + } + + /// Clears all non-send data in this [`World`]. + pub fn clear_non_send(&mut self) { + self.storages.non_sends.clear(); } /// Registers all of the components in the given [`Bundle`] and returns both the component @@ -3331,17 +3346,13 @@ impl World { /// ``` #[inline] pub fn iter_resources(&self) -> impl Iterator)> { - self.storages - .resources + self.resource_entities + .indices() .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()?)) + .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)) }) } @@ -3410,52 +3421,43 @@ 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 + let unsafe_world = self.as_unsafe_world_cell(); + // SAFETY: exclusive world access to all resources + let resource_entities = unsafe { unsafe_world.resource_entities() }; + let components = unsafe_world.components(); + + resource_entities .iter() - .filter_map(|(component_id, data)| { + .map(|(component_id, entity)| (*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 (ptr, ticks) = 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 `ComponentTickCells` - // - We only hold one `ComponentTicksMut` at a time, and we let go of it before getting the next one - let ticks = unsafe { - ComponentTicksMut::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, - }; + // - 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)) }) } - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. + /// Gets a pointer to `!Send` data with the id [`ComponentId`] if it exists. /// The returned pointer must not be used to modify the resource, and must not be /// dereferenced after the immutable borrow of the [`World`] ends. /// - /// **You should prefer to use the typed API [`World::get_resource`] where possible and only + /// **You should prefer to use the typed API [`World::get_non_send`] where possible and only /// use this in cases where the actual types are not known at compile time.** /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. #[inline] pub fn get_non_send_by_id(&self, component_id: ComponentId) -> Option> { // SAFETY: @@ -3463,19 +3465,19 @@ impl World { // - `&self` ensures there are no mutable borrows on world data unsafe { self.as_unsafe_world_cell_readonly() - .get_non_send_resource_by_id(component_id) + .get_non_send_by_id(component_id) } } - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// Gets mutable access to `!Send` data with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the data, as long as the mutable borrow /// of the [`World`] is still valid. /// - /// **You should prefer to use the typed API [`World::get_resource_mut`] where possible and only + /// **You should prefer to use the typed API [`World::get_non_send_mut`] where possible and only /// use this in cases where the actual types are not known at compile time.** /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. #[inline] pub fn get_non_send_mut_by_id(&mut self, component_id: ComponentId) -> Option> { // SAFETY: @@ -3483,32 +3485,38 @@ impl World { // - `as_unsafe_world_cell` provides mutable permission to the whole world unsafe { self.as_unsafe_world_cell() - .get_non_send_resource_mut_by_id(component_id) + .get_non_send_mut_by_id(component_id) } } - /// Removes the resource of a given type, if it exists. Otherwise returns `None`. + /// Removes the resource of a given type, if it exists. + /// Returns `true` if the resource is successfully removed and `false` if + /// the entity does not exist. /// /// **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(()) + pub fn remove_resource_by_id(&mut self, component_id: ComponentId) -> bool { + if let Some(entity) = self.resource_entities.remove(component_id) + && let Ok(mut entity_mut) = self.get_entity_mut(entity) + && entity_mut.contains_id(component_id) + { + entity_mut.remove_by_id(component_id); + true + } else { + false + } } - /// Removes the resource of a given type, if it exists. Otherwise returns `None`. + /// Removes the non-send data of a given type, if it exists. Otherwise returns `None`. /// - /// **You should prefer to use the typed API [`World::remove_resource`] where possible and only + /// **You should prefer to use the typed API [`World::remove_non_send`] where possible and only /// use this in cases where the actual types are not known at compile time.** /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. pub fn remove_non_send_by_id(&mut self, component_id: ComponentId) -> Option<()> { self.storages - .non_send_resources + .non_sends .get_mut(component_id)? .remove_and_drop(); Some(()) @@ -3697,7 +3705,7 @@ impl fmt::Debug for World { .field("entity_count", &self.entities.count_spawned()) .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() } } @@ -3764,6 +3772,7 @@ mod tests { component::{ComponentCloneBehavior, ComponentDescriptor, ComponentInfo, StorageType}, entity::EntityHashSet, entity_disabling::{DefaultQueryFilters, Disabled}, + prelude::{Event, Mut, On, Res}, ptr::OwningPtr, resource::Resource, world::{error::EntityMutableFetchError, DeferredWorld}, @@ -4013,35 +4022,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(); @@ -4063,26 +4044,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); } @@ -4110,14 +4091,14 @@ mod tests { } #[test] - fn init_non_send_resource_does_not_overwrite() { + fn init_non_send_does_not_overwrite() { let mut world = World::new(); world.insert_resource(TestResource(0)); - world.init_non_send_resource::(); + world.init_non_send::(); world.insert_resource(TestResource(1)); - world.init_non_send_resource::(); + world.init_non_send::(); - let resource = world.non_send_resource::(); + let resource = world.non_send::(); assert_eq!(resource.0, 0); } @@ -4387,6 +4368,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)] diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 3b450e066e241..c5ccc88141a4c 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -17,12 +17,12 @@ use crate::{ observer::Observers, prelude::Component, query::{DebugCheckedUnwrap, ReleaseStateQueryData}, - resource::Resource, + resource::{Resource, ResourceEntities}, storage::{ComponentSparseSet, Storages, Table}, world::RawCommandQueue, }; use bevy_platform::sync::atomic::Ordering; -use bevy_ptr::Ptr; +use bevy_ptr::{Ptr, UnsafeCellDeref}; use core::{any::TypeId, cell::UnsafeCell, fmt::Debug, marker::PhantomData, ptr}; use thiserror::Error; @@ -288,6 +288,17 @@ impl<'w> UnsafeWorldCell<'w> { &unsafe { self.world_metadata() }.components } + /// 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 unsafe fn resource_entities(self) -> &'w ResourceEntities { + // 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: @@ -453,52 +464,50 @@ 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() + // 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()?; + entity_cell.get_by_id(component_id) } - /// Gets a reference to the non-send resource of the given type if it exists + /// Gets a reference to non-send data of the given type if it exists /// /// # Safety /// It is the caller's responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource - /// - no mutable reference to the resource exists at the same time + /// - the [`UnsafeWorldCell`] has permission to access the data + /// - no mutable reference to the data exists at the same time #[inline] - pub unsafe fn get_non_send_resource(self) -> Option<&'w R> { + pub unsafe fn get_non_send(self) -> Option<&'w R> { let component_id = self.components().get_valid_resource_id(TypeId::of::())?; // SAFETY: caller ensures that `self` has permission to access `R` // caller ensures that no mutable reference exists to `R` unsafe { - self.get_non_send_resource_by_id(component_id) + self.get_non_send_by_id(component_id) // SAFETY: `component_id` was obtained from `TypeId::of::()` .map(|ptr| ptr.deref::()) } } - /// 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 + /// Gets a pointer to `!Send` data with the id [`ComponentId`] if it exists. + /// The returned pointer must not be used to modify the data, and must not be /// dereferenced after the immutable borrow of the [`World`] ends. /// - /// **You should prefer to use the typed API [`UnsafeWorldCell::get_non_send_resource`] where possible and only + /// **You should prefer to use the typed API [`UnsafeWorldCell::get_non_send`] where possible and only /// use this in cases where the actual types are not known at compile time.** /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. /// /// # Safety /// It is the caller's responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource - /// - no mutable reference to the resource exists at the same time + /// - the [`UnsafeWorldCell`] has permission to access the data + /// - no mutable reference to the data exists at the same time #[inline] - pub unsafe fn get_non_send_resource_by_id(self, component_id: ComponentId) -> Option> { + pub unsafe fn get_non_send_by_id(self, component_id: ComponentId) -> Option> { // SAFETY: we only access data on world that the caller has ensured is unaliased and we have // permission to access. unsafe { self.storages() } - .non_send_resources + .non_sends .get(component_id)? .get_data() } @@ -540,65 +549,48 @@ 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) = 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 { - ComponentTicksMut::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: 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()?; + 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 + /// Gets a mutable reference to the non-send data of the given type if it exists /// /// # Safety /// It is the caller's responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource mutably - /// - no other references to the resource exist at the same time + /// - the [`UnsafeWorldCell`] has permission to access the data mutably + /// - no other references to the data exist at the same time #[inline] - pub unsafe fn get_non_send_resource_mut(self) -> Option> { + pub unsafe fn get_non_send_mut(self) -> Option> { self.assert_allows_mutable_access(); let component_id = self.components().get_valid_resource_id(TypeId::of::())?; // SAFETY: - // - caller ensures that `self` has permission to access the resource - // - caller ensures that the resource is unaliased + // - caller ensures that `self` has permission to access the data + // - caller ensures that the data is unaliased unsafe { - self.get_non_send_resource_mut_by_id(component_id) + self.get_non_send_mut_by_id(component_id) // SAFETY: `component_id` was gotten by `TypeId::of::()` .map(|ptr| ptr.with_type::()) } } - /// Gets a `!Send` resource to the resource with the id [`ComponentId`] if it exists. - /// The returned pointer may be used to modify the resource, as long as the mutable borrow + /// Gets mutable access to `!Send` data with the id [`ComponentId`] if it exists. + /// The returned pointer may be used to modify the data, as long as the mutable borrow /// of the [`World`] is still valid. /// - /// **You should prefer to use the typed API [`UnsafeWorldCell::get_non_send_resource_mut`] where possible and only + /// **You should prefer to use the typed API [`UnsafeWorldCell::get_non_send_mut`] where possible and only /// use this in cases where the actual types are not known at compile time.** /// /// # Panics - /// This function will panic if it isn't called from the same thread that the resource was inserted from. + /// This function will panic if it isn't called from the same thread that the data was inserted from. /// /// # Safety /// It is the caller's responsibility to ensure that - /// - the [`UnsafeWorldCell`] has permission to access the resource mutably - /// - no other references to the resource exist at the same time + /// - the [`UnsafeWorldCell`] has permission to access the data mutably + /// - no other references to the data exist at the same time #[inline] - pub unsafe fn get_non_send_resource_mut_by_id( + pub unsafe fn get_non_send_mut_by_id( self, component_id: ComponentId, ) -> Option> { @@ -607,7 +599,7 @@ impl<'w> UnsafeWorldCell<'w> { // SAFETY: we only access data that the caller has ensured is unaliased and `self` // has permission to access. let (ptr, ticks) = unsafe { self.storages() } - .non_send_resources + .non_sends .get(component_id)? .get_with_ticks()?; @@ -634,14 +626,16 @@ impl<'w> UnsafeWorldCell<'w> { self, component_id: ComponentId, ) -> Option<(Ptr<'w>, ComponentTickCells<'w>)> { + // 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: // - 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. @@ -662,7 +656,7 @@ impl<'w> UnsafeWorldCell<'w> { // - caller ensures there are no mutable borrows of this resource // - caller ensures that we have permission to access this resource unsafe { self.storages() } - .non_send_resources + .non_sends .get(component_id)? .get_with_ticks() } @@ -883,6 +877,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. /// @@ -1288,6 +1308,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_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index abe8881731fc0..1e0c8b2f538d6 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -514,10 +514,14 @@ pub fn process_remote_get_resources_request( let app_type_registry = world.resource::(); let type_registry = app_type_registry.read(); - let reflect_resource = - get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; + get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; + let reflect_component = + get_reflect_component(&type_registry, &resource_path).map_err(BrpError::component_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_component.reflect(entity_ref) else { return Err(BrpError::resource_not_present(&resource_path)); }; @@ -1032,9 +1036,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) } @@ -1118,18 +1133,21 @@ pub fn process_remote_mutate_resources_request( let type_registry = app_type_registry.read(); // Get the `ReflectResource` for the given resource path. - let reflect_resource = - get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; + get_reflect_resource(&type_registry, &resource_path).map_err(BrpError::resource_error)?; + let reflect_component = + get_reflect_component(&type_registry, &resource_path).map_err(BrpError::component_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))?; + let mut reflected_component = 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 .get_with_type_path( - reflected_resource + reflected_component .reflect_path(field_path.as_str()) .map_err(BrpError::resource_error)? .reflect_type_path(), @@ -1145,7 +1163,7 @@ pub fn process_remote_mutate_resources_request( .map_err(BrpError::resource_error)?; // Apply the value to the resource. - reflected_resource + reflected_component .reflect_path_mut(field_path.as_str()) .map_err(BrpError::resource_error)? .try_apply(&*deserialized_value) @@ -1195,9 +1213,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) } @@ -1606,6 +1624,24 @@ fn get_resource_type_registration<'r>( .ok_or_else(|| anyhow!("Unknown resource type: `{}`", resource_path)) } +fn get_resource_entity( + 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_remote/src/schemas/json_schema.rs b/crates/bevy_remote/src/schemas/json_schema.rs index 4e56625bc8eff..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 not be a component" - ); assert!( schema.reflect_types.contains(&"Resource".to_owned()), "Should be a resource" diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs index 9ac4d7e3cef7c..df84edb1529af 100644 --- a/crates/bevy_scene/src/dynamic_scene.rs +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -1,6 +1,6 @@ use crate::{DynamicSceneBuilder, Scene, SceneSpawnError}; use bevy_asset::Asset; -use bevy_ecs::reflect::{ReflectMapEntities, ReflectResource}; +use bevy_ecs::reflect::ReflectResource; use bevy_ecs::{ entity::{Entity, EntityHashMap, SceneEntityMapper}, reflect::{AppTypeRegistry, ReflectComponent}, @@ -8,7 +8,6 @@ use bevy_ecs::{ }; use bevy_reflect::{PartialReflect, TypePath}; -use crate::reflect_utils::clone_reflect_value; use bevy_ecs::component::ComponentCloneBehavior; use bevy_ecs::relationship::RelationshipHookMode; @@ -49,6 +48,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. @@ -57,7 +58,8 @@ impl DynamicScene { .archetypes() .iter() .flat_map(bevy_ecs::archetype::Archetype::entities) - .map(bevy_ecs::archetype::ArchetypeEntity::id), + .map(bevy_ecs::archetype::ArchetypeEntity::id) + .filter(|entity| !resource_entities.contains(entity)), ) .extract_resources() .build() @@ -151,30 +153,34 @@ impl DynamicScene { type_path: type_info.type_path().to_string(), } })?; - let reflect_resource = registration.data::().ok_or_else(|| { + registration.data::().ok_or_else(|| { SceneSpawnError::UnregisteredResource { type_path: type_info.type_path().to_string(), } })?; + // reflect_resource existing, implies that reflect_component also exists + let reflect_component = registration + .data::() + .expect("ReflectComponent is depended on ReflectResource"); - // 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() + let resource_id = reflect_component.register_component(world); + + // check if the resource already exists, if not spawn it, otherwise override the value + let entity = if let Some(entity) = world.resource_entities().get(resource_id) { + *entity } else { - resource.as_partial_reflect() + world.spawn_empty().id() }; - // 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); + SceneEntityMapper::world_scope(entity_map, world, |world, mapper| { + reflect_component.apply_or_insert_mapped( + &mut world.entity_mut(entity), + resource.as_partial_reflect(), + &type_registry, + mapper, + RelationshipHookMode::Skip, + ); + }); } Ok(()) diff --git a/crates/bevy_scene/src/dynamic_scene_builder.rs b/crates/bevy_scene/src/dynamic_scene_builder.rs index 83bf52524dd57..2fd6c0682b518 100644 --- a/crates/bevy_scene/src/dynamic_scene_builder.rs +++ b/crates/bevy_scene/src/dynamic_scene_builder.rs @@ -271,12 +271,22 @@ impl<'w> DynamicSceneBuilder<'w> { #[must_use] pub fn extract_entities(mut self, entities: impl Iterator) -> Self { let type_registry = self.original_world.resource::().read(); + let resource_entities: Vec = self + .original_world + .resource_entities() + .values() + .copied() + .collect(); for entity in entities { if self.extracted_scene.contains_key(&entity) { continue; } + if resource_entities.contains(&entity) { + continue; + } + let mut entry = DynamicEntity { entity, components: Vec::new(), @@ -354,15 +364,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.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); @@ -374,15 +384,15 @@ impl<'w> DynamicSceneBuilder<'w> { let type_registration = type_registry.get(type_id)?; - let resource = type_registration - .data::()? - .reflect(self.original_world) - .ok()?; + type_registration.data::()?; + let component = type_registration + .data::()? + .reflect(self.original_world.entity(*entity))?; - let resource = - clone_reflect_value(resource.as_partial_reflect(), type_registration); + let component = + clone_reflect_value(component.as_partial_reflect(), type_registration); - self.extracted_resources.insert(component_id, resource); + self.extracted_resources.insert(*component_id, component); Some(()) }; extract_and_push(); diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index 95233cbd150ac..0d2175fe44b07 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -156,6 +156,8 @@ mod tests { let mut app = App::new(); app.add_plugins((AssetPlugin::default(), ScenePlugin)) + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() @@ -282,6 +284,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.rs b/crates/bevy_scene/src/scene.rs index db92bdc468431..dbc46e5aec002 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -71,18 +71,22 @@ 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, source_entity) in self.world.resource_entities().iter() { + if Some(*component_id) == self_dqf_id { continue; } - if !resource_data.is_present() { + if !world + .get_entity(*source_entity) + .ok() + .is_some_and(|entity_ref| 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,26 +99,54 @@ impl Scene { .ok_or_else(|| SceneSpawnError::UnregisteredType { std_type_name: component_info.name(), })?; - let reflect_resource = registration.data::().ok_or_else(|| { + registration.data::().ok_or_else(|| { SceneSpawnError::UnregisteredResource { type_path: registration.type_info().type_path().to_string(), } })?; - reflect_resource.copy(&self.world, world, &type_registry); + // reflect_resource existing, implies that reflect_component also exists + let reflect_component = registration + .data::() + .expect("ReflectComponent is depended on ReflectResource"); + + // check if the resource already exists in the other world, if not spawn it + let destination_entity = + if let Some(entity) = world.resource_entities().get(*component_id) { + *entity + } else { + world.spawn_empty().id() + }; + + reflect_component.copy( + &self.world, + world, + *source_entity, + destination_entity, + &type_registry, + ); } + let resource_entities: Vec = + self.world.resource_entities().values().copied().collect(); + // 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()); + if !resource_entities.contains(&scene_entity.id()) { + entity_map + .entry(scene_entity.id()) + .or_insert_with(|| world.spawn_empty().id()); + } } } for archetype in self.world.archetypes().iter() { for scene_entity in archetype.entities() { + if resource_entities.contains(&scene_entity.id()) { + continue; + } + let entity = *entity_map .get(&scene_entity.id()) .expect("should have previously spawned an entity"); diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 11018fb4b0b4a..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}, }; @@ -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(); @@ -848,7 +849,8 @@ mod tests { #[reflect(Component)] struct ComponentF; - #[derive(Resource, Default)] + #[derive(Resource, Default, Reflect)] + #[reflect(Resource)] struct TriggerCount(u32); fn setup() -> App { @@ -1062,6 +1064,8 @@ mod tests { .add_plugins(AssetPlugin::default()) .add_plugins(ScenePlugin) .register_type::() + .register_type::() + .register_type::() .register_type::(); app.update(); diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 00a85ff13d292..2f5345b840133 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -638,20 +638,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), }, @@ -815,7 +815,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 +856,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 +899,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/app/log_layers_ecs.rs b/examples/app/log_layers_ecs.rs index 8dc663afc2148..93830b0caa72c 100644 --- a/examples/app/log_layers_ecs.rs +++ b/examples/app/log_layers_ecs.rs @@ -104,7 +104,7 @@ fn custom_layer(app: &mut App) -> Option { let layer = CaptureLayer { sender }; let resource = CapturedLogMessages(receiver); - app.insert_non_send_resource(resource); + app.insert_non_send(resource); app.add_message::(); app.add_systems(Update, transfer_log_messages); 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() { 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..7835463c435ec --- /dev/null +++ b/release-content/migration-guides/resources_as_components.md @@ -0,0 +1,72 @@ +--- +title: Resources as Components +pull_requests: [20934] +--- + +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 matching entities. + +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. +The same is true for relations, observers, and a host of other concepts that already exist for components. +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. + +The largest change is with regards to `ReflectResource`, which now shadows `ReflectComponent` exactly. When using `ReflectResource`, keep that in mind. The second largest change is that it's no longer possible to simultaneously derive `Component` and `Resource` on a struct. So + +```rust +// 0.17.0 +#[derive(Component, Resource)] +struct Dual +``` + +becomes + +```rust +// 0.18.0 +#[derive(Component)] +struct DualComp; + +#[derive(Resource)] +struct DualRes; +``` + +It's still possible to doubly derive `#[reflect(Component, Resource)]`, but since `ReflectResource` shadows `ReflectComponent` this isn't useful. + +Next, resource registration has been changed. `World::register_resource_with_descriptor` has been renamed to `World::register_non_send_with_descriptor` and is only supposed to be used for non-send resources. +Now, if one wants to dynamically register a resource, one must use `register_component_with_descriptor`. + +```rust +// 0.17 +world.register_resource_with_descriptor(descriptor); + +// 0.18 +use bevy::ecs::resource::{IsResource, resource_on_add_hook, resource_on_despawn_hook}; + +world.register_component_with_descriptor(descriptor); +world.register_component_hooks::().on_add(resource_on_add_hook); +world.register_component_hooks::().on_despawn(resource_on_despawn_hook); +world.register_required_resource::(); +``` + +Registering the component hooks and the required resource is obligatory, as it's key to how resources work internally. +Identically, `ComponentRegistrator::register_resource_with_descriptor`, `ComponentRegistrator::queue_register_resource_with_descriptor` have been renamed to `register_non_send_with_descriptor` and `queue_register_non_send_with_descriptor` respectively. + +We move on to `World::entities().len()`, which now gives more entities than you might expect. +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 recommend you instead use a query `world.query().query(&world).count()`. + +Lastly, since `MapEntities` is implemented by default for components, it's no longer necessary to add `derive(MapEntities)` to a resource. + +```rust +// 0.17.0 +#[derive(Resource, MapEntities)] +struct EntityStruct(#[entities] Entity); + +// 0.18.0 +#[derive(Resource)] +struct EntityStruct(#[entities] Entity); +``` diff --git a/release-content/release-notes/resources_as_components.md b/release-content/release-notes/resources_as_components.md new file mode 100644 index 0000000000000..1614344f0cfe8 --- /dev/null +++ b/release-content/release-notes/resources_as_components.md @@ -0,0 +1,30 @@ +--- +title: Resources as Components +pull_requests: [20934] +--- + +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 matching entities. + +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. +The same is true for relations, observers, and a host of other concepts that already exist for components. +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 first implementation is limited, only enabling observers for resources. + +```rust +#[derive(Resource)] +struct GlobalSetting; + +fn on_add_setting(add: On, query: Query<&LevelSetting>) { + // ... +} +``` + +The main drawbacks are twofold. First it's no longer to derive both `Component` and `Resource` for a struct. +Secondly `ReflectResource` has been gutted and now shadows `ReflectComponent`. +For more information, see the migration guide.