Skip to content

Commit f3f6cad

Browse files
cartmockersf
andauthored
Panic on overlapping one-to-one relationships (#18833)
# Objective One to one relationships (added in #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 <[email protected]>
1 parent 56784de commit f3f6cad

File tree

1 file changed

+57
-3
lines changed

1 file changed

+57
-3
lines changed

crates/bevy_ecs/src/relationship/relationship_source_collection.rs

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ impl<const N: usize> RelationshipSourceCollection for SmallVec<[Entity; N]> {
326326
}
327327

328328
impl RelationshipSourceCollection for Entity {
329-
type SourceIter<'a> = core::iter::Once<Entity>;
329+
type SourceIter<'a> = core::option::IntoIter<Entity>;
330330

331331
fn new() -> Self {
332332
Entity::PLACEHOLDER
@@ -339,6 +339,12 @@ impl RelationshipSourceCollection for Entity {
339339
}
340340

341341
fn add(&mut self, entity: Entity) -> bool {
342+
assert_eq!(
343+
*self,
344+
Entity::PLACEHOLDER,
345+
"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.",
346+
*self
347+
);
342348
*self = entity;
343349

344350
true
@@ -355,7 +361,11 @@ impl RelationshipSourceCollection for Entity {
355361
}
356362

357363
fn iter(&self) -> Self::SourceIter<'_> {
358-
core::iter::once(*self)
364+
if *self == Entity::PLACEHOLDER {
365+
None.into_iter()
366+
} else {
367+
Some(*self).into_iter()
368+
}
359369
}
360370

361371
fn len(&self) -> usize {
@@ -372,7 +382,13 @@ impl RelationshipSourceCollection for Entity {
372382
fn shrink_to_fit(&mut self) {}
373383

374384
fn extend_from_iter(&mut self, entities: impl IntoIterator<Item = Entity>) {
375-
if let Some(entity) = entities.into_iter().last() {
385+
for entity in entities {
386+
assert_eq!(
387+
*self,
388+
Entity::PLACEHOLDER,
389+
"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.",
390+
*self
391+
);
376392
*self = entity;
377393
}
378394
}
@@ -530,4 +546,42 @@ mod tests {
530546
assert!(world.get::<Below>(b).is_none());
531547
assert_eq!(a, world.get::<Below>(c).unwrap().0);
532548
}
549+
550+
#[test]
551+
#[should_panic]
552+
fn one_to_one_relationship_shared_target() {
553+
#[derive(Component)]
554+
#[relationship(relationship_target = Below)]
555+
struct Above(Entity);
556+
557+
#[derive(Component)]
558+
#[relationship_target(relationship = Above)]
559+
struct Below(Entity);
560+
561+
let mut world = World::new();
562+
let a = world.spawn_empty().id();
563+
let b = world.spawn_empty().id();
564+
let c = world.spawn_empty().id();
565+
566+
world.entity_mut(a).insert(Above(c));
567+
world.entity_mut(b).insert(Above(c));
568+
}
569+
570+
#[test]
571+
fn one_to_one_relationship_reinsert() {
572+
#[derive(Component)]
573+
#[relationship(relationship_target = Below)]
574+
struct Above(Entity);
575+
576+
#[derive(Component)]
577+
#[relationship_target(relationship = Above)]
578+
struct Below(Entity);
579+
580+
let mut world = World::new();
581+
let a = world.spawn_empty().id();
582+
let b = world.spawn_empty().id();
583+
584+
world.entity_mut(a).insert(Above(b));
585+
world.entity_mut(a).insert(Above(b));
586+
}
533587
}

0 commit comments

Comments
 (0)