Skip to content

despawn_related removes relationship before calling observers and hooks #20106

Open
@Dampfwalze

Description

@Dampfwalze

Bevy version

0.16.1 release

What you did

In my application I have a tree structure, built with relationships. When nodes in the tree get dropped, I need to do some cleanup, involving moving up the tree.

What went wrong

The problem is, when running the OnReplace observer, the Relationship component is already gone.

Reproduction example

App::new()
    .add_systems(Startup, test)
    .add_observer(on_my_component_replace)
    .run();

#[derive(Component)]
struct MyComponent;

fn test(mut commands: Commands) {
    let root = commands.spawn(children![MyComponent]).id();

    commands.entity(root).despawn_related::<Children>();
}

fn on_my_component_replace(
    trigger: Trigger<OnReplace, MyComponent>,
    has_relationship: Query<Has<ChildOf>>,
) {
    assert!(
        has_relationship.get(trigger.target()).unwrap(),
        "Entity should have a ChildOf relationship"
    );
}

Additional information

The problem is that despawn_related calls EntityWorldMut::take<S> to get the RelationshipTarget component, which removes the component. This triggers the component hooks on the RelationshipTarget, which then remove the relationship from all children. Only after all that, the children are actually despawned and the observers and hooks associated to their components are called.

/// Despawns entities that relate to this one via the given [`RelationshipTarget`].
/// This entity will not be despawned.
pub fn despawn_related<S: RelationshipTarget>(&mut self) -> &mut Self {
if let Some(sources) = self.take::<S>() {
self.world_scope(|world| {
for entity in sources.iter() {
if let Ok(entity_mut) = world.get_entity_mut(entity) {
entity_mut.despawn();
}
}
});
}
self
}

The simplest solution is to replace take with get and copying all children into a temporary vector:

if let Some(sources) = self.get::<S>() {
    let sources = sources.iter().collect::<Vec<_>>();
    self.world_scope(|world| {
        for entity in sources {
            if let Ok(entity_mut) = world.get_entity_mut(entity) {
                entity_mut.despawn();
            };
        }
    });
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-BugAn unexpected or incorrect behaviorS-Needs-TriageThis issue needs to be labelled

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions