Add World::try_resource_scope (#16707)

# Objective

Fixes #16706

## Solution 

- Added new method: `try_resource_scope` which returns `None` if the
requested resource doesn't exist.
- Changed the `resource_scope` test to use `try_resource_scope` as well
to test for the `None` case.

---

## Showcase

```rust
world.try_resource_scope::<MyResource, _>(|world, mut my_resource| {
    // do something with the resource if it exists
});
```
This commit is contained in:
poopy 2024-12-08 16:40:09 +01:00 committed by GitHub
parent 0707c0717b
commit 4aed2ca74c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 25 deletions

View file

@ -1484,6 +1484,7 @@ mod tests {
#[test] #[test]
fn resource_scope() { fn resource_scope() {
let mut world = World::default(); let mut world = World::default();
assert!(world.try_resource_scope::<A, _>(|_, _| {}).is_none());
world.insert_resource(A(0)); world.insert_resource(A(0));
world.resource_scope(|world: &mut World, mut value: Mut<A>| { world.resource_scope(|world: &mut World, mut value: Mut<A>| {
value.0 += 1; value.0 += 1;

View file

@ -2874,21 +2874,34 @@ impl World {
/// }); /// });
/// assert_eq!(world.get_resource::<A>().unwrap().0, 2); /// assert_eq!(world.get_resource::<A>().unwrap().0, 2);
/// ``` /// ```
///
/// See also [`try_resource_scope`](Self::try_resource_scope).
#[track_caller] #[track_caller]
pub fn resource_scope<R: Resource, U>(&mut self, f: impl FnOnce(&mut World, Mut<R>) -> U) -> U { pub fn resource_scope<R: Resource, U>(&mut self, f: impl FnOnce(&mut World, Mut<R>) -> U) -> U {
self.try_resource_scope(f)
.unwrap_or_else(|| panic!("resource does not exist: {}", core::any::type_name::<R>()))
}
/// Temporarily removes the requested resource from this [`World`] if it exists, runs custom user code,
/// then re-adds the resource before returning. Returns `None` if the resource does not exist in this [`World`].
///
/// This enables safe simultaneous mutable access to both a resource and the rest of the [`World`].
/// For more complex access patterns, consider using [`SystemState`](crate::system::SystemState).
///
/// See also [`resource_scope`](Self::resource_scope).
pub fn try_resource_scope<R: Resource, U>(
&mut self,
f: impl FnOnce(&mut World, Mut<R>) -> U,
) -> Option<U> {
let last_change_tick = self.last_change_tick(); let last_change_tick = self.last_change_tick();
let change_tick = self.change_tick(); let change_tick = self.change_tick();
let component_id = self let component_id = self.components.get_resource_id(TypeId::of::<R>())?;
.components
.get_resource_id(TypeId::of::<R>())
.unwrap_or_else(|| panic!("resource does not exist: {}", core::any::type_name::<R>()));
let (ptr, mut ticks, mut _caller) = self let (ptr, mut ticks, mut _caller) = self
.storages .storages
.resources .resources
.get_mut(component_id) .get_mut(component_id)
.and_then(ResourceData::remove) .and_then(ResourceData::remove)?;
.unwrap_or_else(|| panic!("resource does not exist: {}", core::any::type_name::<R>()));
// Read the value onto the stack to avoid potential mut aliasing. // Read the value onto the stack to avoid potential mut aliasing.
// SAFETY: `ptr` was obtained from the TypeId of `R`. // SAFETY: `ptr` was obtained from the TypeId of `R`.
let mut value = unsafe { ptr.read::<R>() }; let mut value = unsafe { ptr.read::<R>() };
@ -2912,27 +2925,18 @@ impl World {
OwningPtr::make(value, |ptr| { OwningPtr::make(value, |ptr| {
// SAFETY: pointer is of type R // SAFETY: pointer is of type R
unsafe { unsafe {
self.storages self.storages.resources.get_mut(component_id).map(|info| {
.resources info.insert_with_ticks(
.get_mut(component_id) ptr,
.map(|info| { ticks,
info.insert_with_ticks( #[cfg(feature = "track_change_detection")]
ptr, _caller,
ticks, );
#[cfg(feature = "track_change_detection")] })
_caller,
);
})
.unwrap_or_else(|| {
panic!(
"No resource of type {} exists in the World.",
core::any::type_name::<R>()
)
});
} }
}); })?;
result Some(result)
} }
/// Sends an [`Event`]. /// Sends an [`Event`].