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 {
type SourceIter<'a> = core::iter::Once<Entity>;
type SourceIter<'a> = core::option::IntoIter<Entity>;
fn new() -> Self {
Entity::PLACEHOLDER
@ -339,6 +339,12 @@ impl RelationshipSourceCollection for Entity {
}
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;
true
@ -355,7 +361,11 @@ impl RelationshipSourceCollection for Entity {
}
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 {
@ -372,7 +382,13 @@ impl RelationshipSourceCollection for Entity {
fn shrink_to_fit(&mut self) {}
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;
}
}
@ -530,4 +546,42 @@ mod tests {
assert!(world.get::<Below>(b).is_none());
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));
}
}