diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index f95214262b548..74e74eb6f5b2d 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -16,7 +16,7 @@ use crate::{ entity::{ComponentCloneCtx, Entity, SourceComponent}, error::CommandWithEntity, lifecycle::HookContext, - world::{DeferredWorld, EntityWorldMut}, + world::{DeferredWorld, EntityWorldMut, World}, }; use log::warn; @@ -115,7 +115,7 @@ pub trait Relationship: Component + Sized { } } } - let target_entity = world.entity(entity).get::().unwrap().get(); + let target_entity = world.get::(entity).unwrap().get(); if target_entity == entity { warn!( "{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", @@ -126,27 +126,45 @@ pub trait Relationship: Component + Sized { world.commands().entity(entity).remove::(); return; } - if let Ok(mut entity_commands) = world.commands().get_entity(target_entity) { - // Deferring is necessary for batch mode - entity_commands - .entry::() - .and_modify(move |mut relationship_target| { - relationship_target.collection_mut_risky().add(entity); - }) - .or_insert_with(|| { - let mut target = Self::RelationshipTarget::with_capacity(1); - target.collection_mut_risky().add(entity); - target - }); - } else { - warn!( - "{}The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", - caller.map(|location|format!("{location}: ")).unwrap_or_default(), - DebugName::type_name::(), - DebugName::type_name::() - ); - world.commands().entity(entity).remove::(); - } + + // We defer the decision of inserting or modifying RelationshipTarget. Deciding now to defer + // either an insertion or a modification causes an issue when inserting in batch if the + // target doesn't have the component yet. In that case, since the hooks run with no flush in + // between, we end up deferring a batch of commands all inserting RelationshipTarget with a + // single entity, each command overwriting the previous one when applied, resulting in the + // target having an only child instead of the whole batch. + world.commands().queue(move |world: &mut World| { + if let Ok(mut target_entity_world) = world.get_entity_mut(target_entity) { + target_entity_world + .entry::() + .and_modify(move |mut relationship_target| { + relationship_target.collection_mut_risky().add(entity); + }) + .or_insert_with(|| { + let mut target = Self::RelationshipTarget::with_capacity(1); + target.collection_mut_risky().add(entity); + target + }); + } else if let Ok(mut entity_world) = world.get_entity_mut(entity) { + warn!( + "{}The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.", + caller + .map(|location| format!("{location}: ")) + .unwrap_or_default(), + DebugName::type_name::(), + DebugName::type_name::() + ); + entity_world.remove::(); + } else { + warn!( + "{}The {} relationship between entity {entity:?} and its target {target_entity:?} could not be set up because these entities don't exist.", + caller + .map(|location| format!("{location}: ")) + .unwrap_or_default(), + DebugName::type_name::(), + ); + } + }); } /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.