Skip to content

Commit b0bd872

Browse files
committed
Fix unsound EntityMut::remove_children. Add EntityMut::world_scope (#6464)
`EntityMut::remove_children` does not call `self.update_location()` which is unsound. Verified by adding the following assertion, which fails when running the tests. ```rust let before = self.location(); self.update_location(); assert_eq!(before, self.location()); ``` I also removed incorrect messages like "parent entity is not modified" and the unhelpful "Inserting a bundle in the children entities may change the parent entity's location if they were of the same archetype" which might lead people to think that's the *only* thing that can change the entity's location. # Changelog Added `EntityMut::world_scope`. Co-authored-by: devil-ira <[email protected]>
1 parent 3a14ae4 commit b0bd872

File tree

3 files changed

+20
-31
lines changed

3 files changed

+20
-31
lines changed

crates/bevy_ecs/src/world/entity_ref.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,7 @@ impl<'w> EntityMut<'w> {
526526

527527
/// Returns this `EntityMut`'s world.
528528
///
529-
/// See [`EntityMut::into_world_mut`] for a safe alternative.
529+
/// See [`EntityMut::world_scope`] or [`EntityMut::into_world_mut`] for a safe alternative.
530530
///
531531
/// # Safety
532532
/// Caller must not modify the world in a way that changes the current entity's location
@@ -543,6 +543,12 @@ impl<'w> EntityMut<'w> {
543543
self.world
544544
}
545545

546+
/// Gives mutable access to this `EntityMut`'s [`World`] in a temporary scope.
547+
pub fn world_scope(&mut self, f: impl FnOnce(&mut World)) {
548+
f(self.world);
549+
self.update_location();
550+
}
551+
546552
/// Updates the internal entity location to match the current location in the internal
547553
/// [`World`]. This is only needed if the user called [`EntityMut::world`], which enables the
548554
/// location to change.

crates/bevy_hierarchy/src/child_builder.rs

+11-24
Original file line numberDiff line numberDiff line change
@@ -456,31 +456,23 @@ pub trait BuildWorldChildren {
456456

457457
impl<'w> BuildWorldChildren for EntityMut<'w> {
458458
fn with_children(&mut self, spawn_children: impl FnOnce(&mut WorldChildBuilder)) -> &mut Self {
459-
{
460-
let entity = self.id();
459+
let entity = self.id();
460+
self.world_scope(|world| {
461461
let mut builder = WorldChildBuilder {
462462
current_entity: None,
463463
parent_entities: vec![entity],
464-
// SAFETY: self.update_location() is called below. It is impossible to make EntityMut
465-
// function calls on `self` within the scope defined here
466-
world: unsafe { self.world_mut() },
464+
world,
467465
};
468-
469466
spawn_children(&mut builder);
470-
}
471-
self.update_location();
467+
});
472468
self
473469
}
474470

475471
fn push_children(&mut self, children: &[Entity]) -> &mut Self {
476472
let parent = self.id();
477-
{
478-
// SAFETY: parent entity is not modified and its location is updated manually
479-
let world = unsafe { self.world_mut() };
473+
self.world_scope(|world| {
480474
update_old_parents(world, parent, children);
481-
// Inserting a bundle in the children entities may change the parent entity's location if they were of the same archetype
482-
self.update_location();
483-
}
475+
});
484476
if let Some(mut children_component) = self.get_mut::<Children>() {
485477
children_component
486478
.0
@@ -494,14 +486,9 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
494486

495487
fn insert_children(&mut self, index: usize, children: &[Entity]) -> &mut Self {
496488
let parent = self.id();
497-
{
498-
// SAFETY: parent entity is not modified and its location is updated manually
499-
let world = unsafe { self.world_mut() };
489+
self.world_scope(|world| {
500490
update_old_parents(world, parent, children);
501-
// Inserting a bundle in the children entities may change the parent entity's location if they were of the same archetype
502-
self.update_location();
503-
}
504-
491+
});
505492
if let Some(mut children_component) = self.get_mut::<Children>() {
506493
children_component
507494
.0
@@ -515,9 +502,9 @@ impl<'w> BuildWorldChildren for EntityMut<'w> {
515502

516503
fn remove_children(&mut self, children: &[Entity]) -> &mut Self {
517504
let parent = self.id();
518-
// SAFETY: This doesn't change the parent's location
519-
let world = unsafe { self.world_mut() };
520-
remove_children(parent, children, world);
505+
self.world_scope(|world| {
506+
remove_children(parent, children, world);
507+
});
521508
self
522509
}
523510
}

crates/bevy_hierarchy/src/hierarchy.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ impl<'w, 's, 'a> DespawnRecursiveExt for EntityCommands<'w, 's, 'a> {
104104

105105
impl<'w> DespawnRecursiveExt for EntityMut<'w> {
106106
/// Despawns the provided entity and its children.
107-
fn despawn_recursive(mut self) {
107+
fn despawn_recursive(self) {
108108
let entity = self.id();
109109

110110
#[cfg(feature = "trace")]
@@ -114,11 +114,7 @@ impl<'w> DespawnRecursiveExt for EntityMut<'w> {
114114
)
115115
.entered();
116116

117-
// SAFETY: EntityMut is consumed so even though the location is no longer
118-
// valid, it cannot be accessed again with the invalid location.
119-
unsafe {
120-
despawn_with_children_recursive(self.world_mut(), entity);
121-
}
117+
despawn_with_children_recursive(self.into_world_mut(), entity);
122118
}
123119

124120
fn despawn_descendants(&mut self) {

0 commit comments

Comments
 (0)