Panic on overlapping one-to-one relationships (#18833)

# Objective

One to one relationships (added in
https://github.com/bevyengine/bevy/pull/18087) can currently easily be
invalidated by having two entities relate to the same target.

Alternative to #18817 (removing one-to-one relationships)

## Solution

Panic if a RelationshipTarget is already targeted. Thanks @urben1680 for
the idea!

---------

Co-authored-by: François Mockers <mockersf@gmail.com>
This commit is contained in:
Carter Anderson 2025-04-14 18:25:37 -07:00 committed by François Mockers
parent fc7705c0a6
commit 15ac36f6d5

View File

@ -326,7 +326,7 @@ impl<const N: usize> RelationshipSourceCollection for SmallVec<[Entity; N]> {
} }
impl RelationshipSourceCollection for Entity { impl RelationshipSourceCollection for Entity {
type SourceIter<'a> = core::iter::Once<Entity>; type SourceIter<'a> = core::option::IntoIter<Entity>;
fn new() -> Self { fn new() -> Self {
Entity::PLACEHOLDER Entity::PLACEHOLDER
@ -339,6 +339,12 @@ impl RelationshipSourceCollection for Entity {
} }
fn add(&mut self, entity: Entity) -> bool { fn add(&mut self, entity: Entity) -> bool {
assert_eq!(
*self,
Entity::PLACEHOLDER,
"Entity {entity} attempted to target an entity with a one-to-one relationship, but it is already targeted by {}. You must remove the original relationship first.",
*self
);
*self = entity; *self = entity;
true true
@ -355,7 +361,11 @@ impl RelationshipSourceCollection for Entity {
} }
fn iter(&self) -> Self::SourceIter<'_> { fn iter(&self) -> Self::SourceIter<'_> {
core::iter::once(*self) if *self == Entity::PLACEHOLDER {
None.into_iter()
} else {
Some(*self).into_iter()
}
} }
fn len(&self) -> usize { fn len(&self) -> usize {
@ -372,7 +382,13 @@ impl RelationshipSourceCollection for Entity {
fn shrink_to_fit(&mut self) {} fn shrink_to_fit(&mut self) {}
fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) { fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
if let Some(entity) = entities.into_iter().last() { for entity in entities {
assert_eq!(
*self,
Entity::PLACEHOLDER,
"Entity {entity} attempted to target an entity with a one-to-one relationship, but it is already targeted by {}. You must remove the original relationship first.",
*self
);
*self = entity; *self = entity;
} }
} }
@ -530,4 +546,42 @@ mod tests {
assert!(world.get::<Below>(b).is_none()); assert!(world.get::<Below>(b).is_none());
assert_eq!(a, world.get::<Below>(c).unwrap().0); assert_eq!(a, world.get::<Below>(c).unwrap().0);
} }
#[test]
#[should_panic]
fn one_to_one_relationship_shared_target() {
#[derive(Component)]
#[relationship(relationship_target = Below)]
struct Above(Entity);
#[derive(Component)]
#[relationship_target(relationship = Above)]
struct Below(Entity);
let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn_empty().id();
let c = world.spawn_empty().id();
world.entity_mut(a).insert(Above(c));
world.entity_mut(b).insert(Above(c));
}
#[test]
fn one_to_one_relationship_reinsert() {
#[derive(Component)]
#[relationship(relationship_target = Below)]
struct Above(Entity);
#[derive(Component)]
#[relationship_target(relationship = Above)]
struct Below(Entity);
let mut world = World::new();
let a = world.spawn_empty().id();
let b = world.spawn_empty().id();
world.entity_mut(a).insert(Above(b));
world.entity_mut(a).insert(Above(b));
}
} }