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`.
This commit is contained in:
Antony 2024-09-30 19:12:11 +01:00 committed by GitHub
parent fc93e13c36
commit 0d2eb3df88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 7 deletions

View file

@ -836,7 +836,7 @@ pub struct Components {
} }
impl 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 /// If a component of this type has already been registered, this will return
/// the ID of the pre-existing component. /// the ID of the pre-existing component.
/// ///
@ -876,9 +876,9 @@ impl Components {
/// Registers a component described by `descriptor`. /// 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. /// will be created for each one.
/// ///
/// # See also /// # See also
@ -1034,6 +1034,7 @@ impl Components {
/// # See also /// # See also
/// ///
/// * [`Components::resource_id()`] /// * [`Components::resource_id()`]
/// * [`Components::register_resource_with_descriptor()`]
#[inline] #[inline]
pub fn register_resource<T: Resource>(&mut self) -> ComponentId { pub fn register_resource<T: Resource>(&mut self) -> ComponentId {
// SAFETY: The [`ComponentDescriptor`] matches the [`TypeId`] // 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. /// 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 /// If a resource of this type has already been registered, this will return
/// the ID of the pre-existing resource. /// the ID of the pre-existing resource.
@ -1069,12 +1088,20 @@ impl Components {
let components = &mut self.components; let components = &mut self.components;
*self.resource_indices.entry(type_id).or_insert_with(|| { *self.resource_indices.entry(type_id).or_insert_with(|| {
let descriptor = func(); let descriptor = func();
let component_id = ComponentId(components.len()); Components::register_resource_inner(components, descriptor)
components.push(ComponentInfo::new(component_id, descriptor));
component_id
}) })
} }
#[inline]
fn register_resource_inner(
components: &mut Vec<ComponentInfo>,
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. /// Gets an iterator over all components registered with this instance.
pub fn iter(&self) -> impl Iterator<Item = &ComponentInfo> + '_ { pub fn iter(&self) -> impl Iterator<Item = &ComponentInfo> + '_ {
self.components.iter() self.components.iter()

View file

@ -1320,6 +1320,23 @@ impl World {
.map(Into::into) .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. /// Initializes a new resource and returns the [`ComponentId`] created for it.
/// ///
/// If the resource already exists, nothing happens. /// 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::<TestResource>();
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::<TestResource>()
};
assert_eq!(resource.0, 0);
assert!(world.remove_resource_by_id(component_id).is_some());
}
#[test] #[test]
fn custom_resource_with_layout() { fn custom_resource_with_layout() {
static DROP_COUNT: AtomicU32 = AtomicU32::new(0); 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]; let value: [u8; 8] = [0, 1, 2, 3, 4, 5, 6, 7];
OwningPtr::make(value, |ptr| { OwningPtr::make(value, |ptr| {