From 50f70ebb916162eecf394a5d6de3be92ee01afe7 Mon Sep 17 00:00:00 2001 From: HeartofPhos <43216176+HeartofPhos@users.noreply.github.com> Date: Tue, 27 May 2025 23:05:31 +0200 Subject: [PATCH] Fix custom relations panics with parent/child relations (#19341) # Objective Fixes #18905 ## Solution `world.commands().entity(target_entity).queue(command)` calls `commands.with_entity` without an error handler, instead queue on `Commands` with an error handler ## Testing Added unit test Co-authored-by: Heart <> --- crates/bevy_ecs/src/relationship/mod.rs | 81 ++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 9a2a2a2d5a..3522118fbc 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -158,19 +158,21 @@ pub trait Relationship: Component + Sized { { relationship_target.collection_mut_risky().remove(entity); if relationship_target.len() == 0 { - if let Ok(mut entity) = world.commands().get_entity(target_entity) { + let command = |mut entity: EntityWorldMut| { // this "remove" operation must check emptiness because in the event that an identical // relationship is inserted on top, this despawn would result in the removal of that identical // relationship ... not what we want! - entity.queue(|mut entity: EntityWorldMut| { - if entity - .get::() - .is_some_and(RelationshipTarget::is_empty) - { - entity.remove::(); - } - }); - } + if entity + .get::() + .is_some_and(RelationshipTarget::is_empty) + { + entity.remove::(); + } + }; + + world + .commands() + .queue(command.with_entity(target_entity).handle_error_with(ignore)); } } } @@ -424,4 +426,63 @@ mod tests { // No assert necessary, looking to make sure compilation works with the macros } + + #[test] + fn parent_child_relationship_with_custom_relationship() { + use crate::prelude::ChildOf; + + #[derive(Component)] + #[relationship(relationship_target = RelTarget)] + struct Rel(Entity); + + #[derive(Component)] + #[relationship_target(relationship = Rel)] + struct RelTarget(Entity); + + let mut world = World::new(); + + // Rel on Parent + // Despawn Parent + let mut commands = world.commands(); + let child = commands.spawn_empty().id(); + let parent = commands.spawn(Rel(child)).add_child(child).id(); + commands.entity(parent).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(world.get_entity(parent).is_err()); + + // Rel on Parent + // Despawn Child + let mut commands = world.commands(); + let child = commands.spawn_empty().id(); + let parent = commands.spawn(Rel(child)).add_child(child).id(); + commands.entity(child).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(!world.entity(parent).contains::()); + + // Rel on Child + // Despawn Parent + let mut commands = world.commands(); + let parent = commands.spawn_empty().id(); + let child = commands.spawn((ChildOf(parent), Rel(parent))).id(); + commands.entity(parent).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(world.get_entity(parent).is_err()); + + // Rel on Child + // Despawn Child + let mut commands = world.commands(); + let parent = commands.spawn_empty().id(); + let child = commands.spawn((ChildOf(parent), Rel(parent))).id(); + commands.entity(child).despawn(); + world.flush(); + + assert!(world.get_entity(child).is_err()); + assert!(!world.entity(parent).contains::()); + } }