Description
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.
bevy/crates/bevy_ecs/src/relationship/related_methods.rs
Lines 299 to 312 in f1eace6
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();
};
}
});
}