From 4aed2ca74c42b13b0f89e40971b85ee87de3acd7 Mon Sep 17 00:00:00 2001 From: poopy Date: Sun, 8 Dec 2024 16:40:09 +0100 Subject: [PATCH] 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::(|world, mut my_resource| { // do something with the resource if it exists }); ``` --- crates/bevy_ecs/src/lib.rs | 1 + crates/bevy_ecs/src/world/mod.rs | 54 +++++++++++++++++--------------- 2 files changed, 30 insertions(+), 25 deletions(-) diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index cfe4557d64..85ff83c0aa 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -1484,6 +1484,7 @@ mod tests { #[test] fn resource_scope() { let mut world = World::default(); + assert!(world.try_resource_scope::(|_, _| {}).is_none()); world.insert_resource(A(0)); world.resource_scope(|world: &mut World, mut value: Mut| { value.0 += 1; diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 491c3648dd..5152e549a8 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -2874,21 +2874,34 @@ impl World { /// }); /// assert_eq!(world.get_resource::().unwrap().0, 2); /// ``` + /// + /// See also [`try_resource_scope`](Self::try_resource_scope). #[track_caller] pub fn resource_scope(&mut self, f: impl FnOnce(&mut World, Mut) -> U) -> U { + self.try_resource_scope(f) + .unwrap_or_else(|| panic!("resource does not exist: {}", core::any::type_name::())) + } + + /// 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( + &mut self, + f: impl FnOnce(&mut World, Mut) -> U, + ) -> Option { let last_change_tick = self.last_change_tick(); let change_tick = self.change_tick(); - let component_id = self - .components - .get_resource_id(TypeId::of::()) - .unwrap_or_else(|| panic!("resource does not exist: {}", core::any::type_name::())); + let component_id = self.components.get_resource_id(TypeId::of::())?; let (ptr, mut ticks, mut _caller) = self .storages .resources .get_mut(component_id) - .and_then(ResourceData::remove) - .unwrap_or_else(|| panic!("resource does not exist: {}", core::any::type_name::())); + .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::() }; @@ -2912,27 +2925,18 @@ impl World { 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, - #[cfg(feature = "track_change_detection")] - _caller, - ); - }) - .unwrap_or_else(|| { - panic!( - "No resource of type {} exists in the World.", - core::any::type_name::() - ) - }); + self.storages.resources.get_mut(component_id).map(|info| { + info.insert_with_ticks( + ptr, + ticks, + #[cfg(feature = "track_change_detection")] + _caller, + ); + }) } - }); + })?; - result + Some(result) } /// Sends an [`Event`].