From 0d2eb3df889f4d982ed95472bf930aa50d28640a Mon Sep 17 00:00:00 2001 From: Antony Date: Mon, 30 Sep 2024 19:12:11 +0100 Subject: [PATCH] Add `register_resource_with_descriptor` (#15501) # Objective - Fixes #15448. ## Solution - Add `World::register_resource_with_descriptor` and `Components::register_resource_with_descriptor`. ## Testing - Added a test `dynamic_resource`. --- crates/bevy_ecs/src/component.rs | 39 ++++++++++++++++++++---- crates/bevy_ecs/src/world/mod.rs | 52 +++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/src/component.rs b/crates/bevy_ecs/src/component.rs index c359984203..d461fa4ba7 100644 --- a/crates/bevy_ecs/src/component.rs +++ b/crates/bevy_ecs/src/component.rs @@ -836,7 +836,7 @@ pub struct Components { } impl Components { - /// Registers a component of type `T` with this instance. + /// Registers a [`Component`] of type `T` with this instance. /// If a component of this type has already been registered, this will return /// the ID of the pre-existing component. /// @@ -876,9 +876,9 @@ impl Components { /// Registers a component described by `descriptor`. /// - /// ## Note + /// # Note /// - /// If this method is called multiple times with identical descriptors, a distinct `ComponentId` + /// If this method is called multiple times with identical descriptors, a distinct [`ComponentId`] /// will be created for each one. /// /// # See also @@ -1034,6 +1034,7 @@ impl Components { /// # See also /// /// * [`Components::resource_id()`] + /// * [`Components::register_resource_with_descriptor()`] #[inline] pub fn register_resource(&mut self) -> ComponentId { // SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] @@ -1044,6 +1045,24 @@ impl Components { } } + /// Registers a [`Resource`] described by `descriptor`. + /// + /// # Note + /// + /// If this method is called multiple times with identical descriptors, a distinct [`ComponentId`] + /// will be created for each one. + /// + /// # See also + /// + /// * [`Components::resource_id()`] + /// * [`Components::register_resource()`] + pub fn register_resource_with_descriptor( + &mut self, + descriptor: ComponentDescriptor, + ) -> ComponentId { + Components::register_resource_inner(&mut self.components, descriptor) + } + /// Registers a [non-send resource](crate::system::NonSend) of type `T` with this instance. /// If a resource of this type has already been registered, this will return /// the ID of the pre-existing resource. @@ -1069,12 +1088,20 @@ impl Components { let components = &mut self.components; *self.resource_indices.entry(type_id).or_insert_with(|| { let descriptor = func(); - let component_id = ComponentId(components.len()); - components.push(ComponentInfo::new(component_id, descriptor)); - component_id + Components::register_resource_inner(components, descriptor) }) } + #[inline] + fn register_resource_inner( + components: &mut Vec, + descriptor: ComponentDescriptor, + ) -> ComponentId { + let component_id = ComponentId(components.len()); + components.push(ComponentInfo::new(component_id, descriptor)); + component_id + } + /// Gets an iterator over all components registered with this instance. pub fn iter(&self) -> impl Iterator + '_ { self.components.iter() diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 15e4c61464..a41628dacd 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1320,6 +1320,23 @@ impl World { .map(Into::into) } + /// Registers a new [`Resource`] type and returns the [`ComponentId`] created for it. + /// + /// This enables the dynamic registration of new [`Resource`] 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( + &mut self, + descriptor: ComponentDescriptor, + ) -> ComponentId { + self.components + .register_resource_with_descriptor(descriptor) + } + /// Initializes a new resource and returns the [`ComponentId`] created for it. /// /// If the resource already exists, nothing happens. @@ -3248,6 +3265,39 @@ 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, + #[cfg(feature = "track_change_detection")] + panic::Location::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() { static DROP_COUNT: AtomicU32 = AtomicU32::new(0); @@ -3268,7 +3318,7 @@ mod tests { ) }; - let component_id = world.register_component_with_descriptor(descriptor); + let component_id = world.register_resource_with_descriptor(descriptor); let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7]; OwningPtr::make(value, |ptr| {