From ce4a1924d4a6277384eff535d8b4c72ca4a303b3 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 13:50:46 -0500 Subject: [PATCH 01/16] add a test case for `EntityMut` unsoundness --- crates/bevy_ecs/src/world/entity_ref.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 7eff73711881a..3b18ea3c87eb7 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -923,4 +923,23 @@ mod tests { assert!(entity.get_by_id(invalid_component_id).is_none()); assert!(entity.get_mut_by_id(invalid_component_id).is_none()); } + + #[test] + fn entity_mut_world_scope_panic() { + let mut world = World::new(); + + let mut entity = world.spawn_empty(); + let id = entity.id(); + entity.world_scope(|w| { + // Change the entity's `EnityLocation`, which invalidates the original `EntityMut`. + // This will get updated at the end of the scope. + w.entity_mut(id).insert(TestComponent(0)); + + // Ensure that the entity location still gets updated even in case of a panic. + panic!() + }); + + // Despawn the entity. If the `EntityLocation` has not been updated, this will cause UB. + entity.despawn(); + } } From 3412c828023e7b5948cf61daaf7cf8237d89d50f Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:00:44 -0500 Subject: [PATCH 02/16] catch the panic in the test --- crates/bevy_ecs/src/world/entity_ref.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 3b18ea3c87eb7..0b3695f810cde 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -832,6 +832,8 @@ pub(crate) unsafe fn take_component<'a>( #[cfg(test)] mod tests { + use std::panic::AssertUnwindSafe; + use crate as bevy_ecs; use crate::component::ComponentId; use crate::prelude::*; // for the `#[derive(Component)]` @@ -930,14 +932,17 @@ mod tests { let mut entity = world.spawn_empty(); let id = entity.id(); - entity.world_scope(|w| { - // Change the entity's `EnityLocation`, which invalidates the original `EntityMut`. - // This will get updated at the end of the scope. - w.entity_mut(id).insert(TestComponent(0)); - - // Ensure that the entity location still gets updated even in case of a panic. - panic!() - }); + let res = std::panic::catch_unwind(AssertUnwindSafe(|| { + entity.world_scope(|w| { + // Change the entity's `EnityLocation`, which invalidates the original `EntityMut`. + // This will get updated at the end of the scope. + w.entity_mut(id).insert(TestComponent(0)); + + // Ensure that the entity location still gets updated even in case of a panic. + panic!() + }); + })); + assert!(res.is_err()); // Despawn the entity. If the `EntityLocation` has not been updated, this will cause UB. entity.despawn(); From f134b43d675c8acfccef79507a587307c53bb228 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:01:54 -0500 Subject: [PATCH 03/16] use archetype moves instead of despawning --- crates/bevy_ecs/src/world/entity_ref.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 0b3695f810cde..65c10ef44e71f 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -944,7 +944,7 @@ mod tests { })); assert!(res.is_err()); - // Despawn the entity. If the `EntityLocation` has not been updated, this will cause UB. - entity.despawn(); + // If the `EntityLocation` has not been updated, this will cause UB. + entity.insert(TestComponent(0)); } } From bb59e54f0ad14d708998ff877b6a1cf319ff64e2 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:23:59 -0500 Subject: [PATCH 04/16] fix unsoundness --- crates/bevy_ecs/src/world/entity_ref.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 65c10ef44e71f..1b9cb8e2d1930 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -541,8 +541,22 @@ impl<'w> EntityMut<'w> { /// Gives mutable access to this `EntityMut`'s [`World`] in a temporary scope. pub fn world_scope(&mut self, f: impl FnOnce(&mut World)) { + // Erase the lifetime of self, so we can get around the borrow checker. + let this_ptr = self as *mut Self; + + // Update the stored `EntityLocation` for this instance. + // This will get called at the end of this scope, even if the closure `f` unwinds. + let _on_drop = bevy_utils::OnDrop::new(|| { + // SAFETY: + // - This will only be invoked at the end of the outer scope, at which point + // `self` will no longer be borrowed, which ensures the references don't alias. + // - The pointer must otherwise be safe to dereference, since it was obtained + // from a mutable reference. + let this = unsafe { &mut *this_ptr }; + this.update_location(); + }); + f(self.world); - self.update_location(); } /// Updates the internal entity location to match the current location in the internal From b85453ede7b9cabab440bf490deca8a10dd6c5f1 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:50:21 -0500 Subject: [PATCH 05/16] rename `on_drop` -> `cleanup_guard` --- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1b9cb8e2d1930..e58f75d36b0fb 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -546,7 +546,7 @@ impl<'w> EntityMut<'w> { // Update the stored `EntityLocation` for this instance. // This will get called at the end of this scope, even if the closure `f` unwinds. - let _on_drop = bevy_utils::OnDrop::new(|| { + let _cleanup_guard = bevy_utils::OnDrop::new(move || { // SAFETY: // - This will only be invoked at the end of the outer scope, at which point // `self` will no longer be borrowed, which ensures the references don't alias. From 0aff619bd7d31d05d8c8e555ef8cca7aefdf7511 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 14:59:47 -0500 Subject: [PATCH 06/16] be overly cautious --- crates/bevy_ecs/src/world/entity_ref.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index e58f75d36b0fb..20832219e01a2 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -544,19 +544,37 @@ impl<'w> EntityMut<'w> { // Erase the lifetime of self, so we can get around the borrow checker. let this_ptr = self as *mut Self; + // Make sure `self` cannot get observed in this scope. + // NOTE: This is probably unnecessary, but we are excercising extreme caution here. + #[allow(clippy::forget_ref)] + std::mem::forget(self); + // Update the stored `EntityLocation` for this instance. // This will get called at the end of this scope, even if the closure `f` unwinds. let _cleanup_guard = bevy_utils::OnDrop::new(move || { // SAFETY: - // - This will only be invoked at the end of the outer scope, at which point - // `self` will no longer be borrowed, which ensures the references don't alias. + // - When this closure gets called at the end of the outer scope, + // then any other mutable references to `this_ptr` will have been released, + // which ensures that this will not alias. // - The pointer must otherwise be safe to dereference, since it was obtained // from a mutable reference. let this = unsafe { &mut *this_ptr }; this.update_location(); }); - f(self.world); + { + // SAFETY: + // - `this_ptr` does not have any current aliased mutable references. + // `this` will get released at the end of the current scope, which ensures + // that it won't alias when `_cleanup_guard` gets called. + // - The pointer must otherwise be safe to dereference, since it was obtained + // from a mutable reference. + let this = unsafe { &mut *this_ptr }; + f(this.world); + + // `this` will get released at the end of this scope, + // which allows `_cleanup_guard` to safely take a reference to + } } /// Updates the internal entity location to match the current location in the internal From f8ff83a89a7851d7c21205f4eae0fcd58054ebc1 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:15:58 -0500 Subject: [PATCH 07/16] fix a stacked borrows failure --- crates/bevy_ecs/src/world/entity_ref.rs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 20832219e01a2..9bb4f5c7e48e5 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -544,11 +544,6 @@ impl<'w> EntityMut<'w> { // Erase the lifetime of self, so we can get around the borrow checker. let this_ptr = self as *mut Self; - // Make sure `self` cannot get observed in this scope. - // NOTE: This is probably unnecessary, but we are excercising extreme caution here. - #[allow(clippy::forget_ref)] - std::mem::forget(self); - // Update the stored `EntityLocation` for this instance. // This will get called at the end of this scope, even if the closure `f` unwinds. let _cleanup_guard = bevy_utils::OnDrop::new(move || { @@ -562,19 +557,7 @@ impl<'w> EntityMut<'w> { this.update_location(); }); - { - // SAFETY: - // - `this_ptr` does not have any current aliased mutable references. - // `this` will get released at the end of the current scope, which ensures - // that it won't alias when `_cleanup_guard` gets called. - // - The pointer must otherwise be safe to dereference, since it was obtained - // from a mutable reference. - let this = unsafe { &mut *this_ptr }; - f(this.world); - - // `this` will get released at the end of this scope, - // which allows `_cleanup_guard` to safely take a reference to - } + f(self.world); } /// Updates the internal entity location to match the current location in the internal From ecb90f7b0120e5f08219096949790f33a5dab9cb Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:26:52 -0500 Subject: [PATCH 08/16] update a comment --- crates/bevy_ecs/src/world/entity_ref.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 268edb454c236..95f2a13a3aab3 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -572,8 +572,7 @@ impl<'w> EntityMut<'w> { let _cleanup_guard = bevy_utils::OnDrop::new(move || { // SAFETY: // - When this closure gets called at the end of the outer scope, - // then any other mutable references to `this_ptr` will have been released, - // which ensures that this will not alias. + // the reference to `self` will be inactive, which ensures that this will not alias. // - The pointer must otherwise be safe to dereference, since it was obtained // from a mutable reference. let this = unsafe { &mut *this_ptr }; From dfb99347700ae0be26e06fc6ab04c6ada8a5718b Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:27:43 -0500 Subject: [PATCH 09/16] add a message to a test panic --- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 95f2a13a3aab3..483d4ae60b075 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -976,7 +976,7 @@ mod tests { w.entity_mut(id).insert(TestComponent(0)); // Ensure that the entity location still gets updated even in case of a panic. - panic!() + panic!("this should get caught by the outer scope") }); })); assert!(res.is_err()); From 3cb2ab0bba83f7c6cb284c145e7653b39434d9bf Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 15:57:53 -0500 Subject: [PATCH 10/16] Add a link to this PR on the regression test Co-authored-by: James Liu --- crates/bevy_ecs/src/world/entity_ref.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 483d4ae60b075..d11d2b828fa59 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -963,6 +963,7 @@ mod tests { assert!(entity.get_mut_by_id(invalid_component_id).is_none()); } + // regression test for https://github.com/bevyengine/bevy/pull/7387 #[test] fn entity_mut_world_scope_panic() { let mut world = World::new(); From 41369f3d67bd845cdcfcb8c4478b492d4c7447a6 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 17:33:22 -0500 Subject: [PATCH 11/16] catch panics using only safe code --- crates/bevy_ecs/src/world/entity_ref.rs | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d11d2b828fa59..6612d4698251d 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -11,7 +11,7 @@ use crate::{ }; use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::debug; -use std::any::TypeId; +use std::{any::TypeId, panic::AssertUnwindSafe}; use super::unsafe_world_cell::UnsafeWorldCellEntityRef; @@ -564,22 +564,9 @@ impl<'w> EntityMut<'w> { /// # assert_eq!(new_r.0, 1); /// ``` pub fn world_scope(&mut self, f: impl FnOnce(&mut World) -> U) -> U { - // Erase the lifetime of self, so we can get around the borrow checker. - let this_ptr = self as *mut Self; - - // Update the stored `EntityLocation` for this instance. - // This will get called at the end of this scope, even if the closure `f` unwinds. - let _cleanup_guard = bevy_utils::OnDrop::new(move || { - // SAFETY: - // - When this closure gets called at the end of the outer scope, - // the reference to `self` will be inactive, which ensures that this will not alias. - // - The pointer must otherwise be safe to dereference, since it was obtained - // from a mutable reference. - let this = unsafe { &mut *this_ptr }; - this.update_location(); - }); - - f(self.world) + let value = std::panic::catch_unwind(AssertUnwindSafe(|| f(self.world))); + self.update_location(); + value.unwrap_or_else(|e| std::panic::panic_any(e)) } /// Updates the internal entity location to match the current location in the internal From feecc665403096dda91baea1e1ae8ec460e899d2 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 20:38:20 -0500 Subject: [PATCH 12/16] Revert "catch panics using only safe code" This reverts commit 41369f3d67bd845cdcfcb8c4478b492d4c7447a6. --- crates/bevy_ecs/src/world/entity_ref.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 6612d4698251d..d11d2b828fa59 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -11,7 +11,7 @@ use crate::{ }; use bevy_ptr::{OwningPtr, Ptr}; use bevy_utils::tracing::debug; -use std::{any::TypeId, panic::AssertUnwindSafe}; +use std::any::TypeId; use super::unsafe_world_cell::UnsafeWorldCellEntityRef; @@ -564,9 +564,22 @@ impl<'w> EntityMut<'w> { /// # assert_eq!(new_r.0, 1); /// ``` pub fn world_scope(&mut self, f: impl FnOnce(&mut World) -> U) -> U { - let value = std::panic::catch_unwind(AssertUnwindSafe(|| f(self.world))); - self.update_location(); - value.unwrap_or_else(|e| std::panic::panic_any(e)) + // Erase the lifetime of self, so we can get around the borrow checker. + let this_ptr = self as *mut Self; + + // Update the stored `EntityLocation` for this instance. + // This will get called at the end of this scope, even if the closure `f` unwinds. + let _cleanup_guard = bevy_utils::OnDrop::new(move || { + // SAFETY: + // - When this closure gets called at the end of the outer scope, + // the reference to `self` will be inactive, which ensures that this will not alias. + // - The pointer must otherwise be safe to dereference, since it was obtained + // from a mutable reference. + let this = unsafe { &mut *this_ptr }; + this.update_location(); + }); + + f(self.world) } /// Updates the internal entity location to match the current location in the internal From ed2bc62fdc49ef2396a8d65e32e04d48793be21e Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Fri, 27 Jan 2023 20:50:09 -0500 Subject: [PATCH 13/16] use a custom guard type to to perform the cleanup --- crates/bevy_ecs/src/world/entity_ref.rs | 30 ++++++++++++------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index d11d2b828fa59..dbc0adb68bbc1 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -564,22 +564,22 @@ impl<'w> EntityMut<'w> { /// # assert_eq!(new_r.0, 1); /// ``` pub fn world_scope(&mut self, f: impl FnOnce(&mut World) -> U) -> U { - // Erase the lifetime of self, so we can get around the borrow checker. - let this_ptr = self as *mut Self; + struct Guard<'w, 'a> { + entity_mut: &'a mut EntityMut<'w>, + } - // Update the stored `EntityLocation` for this instance. - // This will get called at the end of this scope, even if the closure `f` unwinds. - let _cleanup_guard = bevy_utils::OnDrop::new(move || { - // SAFETY: - // - When this closure gets called at the end of the outer scope, - // the reference to `self` will be inactive, which ensures that this will not alias. - // - The pointer must otherwise be safe to dereference, since it was obtained - // from a mutable reference. - let this = unsafe { &mut *this_ptr }; - this.update_location(); - }); - - f(self.world) + impl Drop for Guard<'_, '_> { + #[inline] + fn drop(&mut self) { + self.entity_mut.update_location(); + } + } + + // When `guard` is dropped at the end of this scope, + // it will update the cached `EntityLocation` for this instance. + // This will run even in case the closure `f` unwinds. + let guard = Guard { entity_mut: self }; + f(guard.entity_mut.world) } /// Updates the internal entity location to match the current location in the internal From 708abccb8227dad911edd177796e0aea3828ce63 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 28 Jan 2023 08:15:53 -0500 Subject: [PATCH 14/16] fix a typo Co-authored-by: Jerome Humbert --- crates/bevy_ecs/src/world/entity_ref.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index dbc0adb68bbc1..165fbead31396 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -972,7 +972,7 @@ mod tests { let id = entity.id(); let res = std::panic::catch_unwind(AssertUnwindSafe(|| { entity.world_scope(|w| { - // Change the entity's `EnityLocation`, which invalidates the original `EntityMut`. + // Change the entity's `EntityLocation`, which invalidates the original `EntityMut`. // This will get updated at the end of the scope. w.entity_mut(id).insert(TestComponent(0)); From c08200150bb0481615195c0702c6342d91117131 Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 28 Jan 2023 08:18:58 -0500 Subject: [PATCH 15/16] make the regression test more rigorous --- crates/bevy_ecs/src/world/entity_ref.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 165fbead31396..1286f2cc4628a 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -969,6 +969,7 @@ mod tests { let mut world = World::new(); let mut entity = world.spawn_empty(); + let old_location = entity.location(); let id = entity.id(); let res = std::panic::catch_unwind(AssertUnwindSafe(|| { entity.world_scope(|w| { @@ -982,7 +983,7 @@ mod tests { })); assert!(res.is_err()); - // If the `EntityLocation` has not been updated, this will cause UB. - entity.insert(TestComponent(0)); + // Ensure that the location has been properly updated. + assert!(entity.location() != old_location); } } From 75f507f1cb17b9192fe94eba33ff42a6e8457fad Mon Sep 17 00:00:00 2001 From: JoJoJet <21144246+JoJoJet@users.noreply.github.com> Date: Sat, 28 Jan 2023 10:44:40 -0500 Subject: [PATCH 16/16] minor correction to the docs for `EntityMut::update_location` --- crates/bevy_ecs/src/world/entity_ref.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 1286f2cc4628a..7ab064500ff3a 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -583,8 +583,10 @@ impl<'w> EntityMut<'w> { } /// Updates the internal entity location to match the current location in the internal - /// [`World`]. This is only needed if the user called [`EntityMut::world`], which enables the - /// location to change. + /// [`World`]. + /// + /// This is *only* required when using the unsafe function [`EntityMut::world_mut`], + /// which enables the location to change. pub fn update_location(&mut self) { self.location = self.world.entities().get(self.entity).unwrap(); }