//! This module provides functionality to link entities to each other using specialized components called "relationships". See the [`Relationship`] trait for more info. mod related_methods; mod relationship_query; mod relationship_source_collection; use alloc::format; pub use related_methods::*; pub use relationship_query::*; pub use relationship_source_collection::*; use crate::{ component::{Component, HookContext, Mutable}, entity::{ComponentCloneCtx, Entity, SourceComponent}, error::{ignore, CommandWithEntity, HandleError}, system::entity_command::{self}, world::{DeferredWorld, EntityWorldMut}, }; use log::warn; /// A [`Component`] on a "source" [`Entity`] that references another target [`Entity`], creating a "relationship" between them. Every [`Relationship`] /// has a corresponding [`RelationshipTarget`] type (and vice-versa), which exists on the "target" entity of a relationship and contains the list of all /// "source" entities that relate to the given "target" /// /// The [`Relationship`] component is the "source of truth" and the [`RelationshipTarget`] component reflects that source of truth. When a [`Relationship`] /// component is inserted on an [`Entity`], the corresponding [`RelationshipTarget`] component is immediately inserted on the target component if it does /// not already exist, and the "source" entity is automatically added to the [`RelationshipTarget`] collection (this is done via "component hooks"). /// /// A common example of a [`Relationship`] is the parent / child relationship. Bevy ECS includes a canonical form of this via the [`ChildOf`](crate::hierarchy::ChildOf) /// [`Relationship`] and the [`Children`](crate::hierarchy::Children) [`RelationshipTarget`]. /// /// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. /// /// ## Derive /// /// [`Relationship`] and [`RelationshipTarget`] can only be derived for structs with a single unnamed field, single named field /// or for named structs where one field is annotated with `#[relationship]`. /// If there are additional fields, they must all implement [`Default`]. /// /// [`RelationshipTarget`] also requires that the relationship field is private to prevent direct mutation, /// ensuring the correctness of relationships. /// ``` /// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; /// #[derive(Component)] /// #[relationship(relationship_target = Children)] /// pub struct ChildOf { /// #[relationship] /// pub parent: Entity, /// internal: u8, /// }; /// /// #[derive(Component)] /// #[relationship_target(relationship = ChildOf)] /// pub struct Children(Vec); /// ``` /// /// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(linked_spawn)]` attribute to /// automatically despawn entities stored in an entity's [`RelationshipTarget`] when that entity is despawned: /// /// ``` /// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; /// #[derive(Component)] /// #[relationship(relationship_target = Children)] /// pub struct ChildOf(pub Entity); /// /// #[derive(Component)] /// #[relationship_target(relationship = ChildOf, linked_spawn)] /// pub struct Children(Vec); /// ``` pub trait Relationship: Component + Sized { /// The [`Component`] added to the "target" entities of this [`Relationship`], which contains the list of all "source" /// entities that relate to the "target". type RelationshipTarget: RelationshipTarget; /// Gets the [`Entity`] ID of the related entity. fn get(&self) -> Entity; /// Creates this [`Relationship`] from the given `entity`. fn from(entity: Entity) -> Self; /// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. fn on_insert( mut world: DeferredWorld, HookContext { entity, caller, relationship_hook_mode, .. }: HookContext, ) { match relationship_hook_mode { RelationshipHookMode::Run => {} RelationshipHookMode::Skip => return, RelationshipHookMode::RunIfNotLinked => { if ::LINKED_SPAWN { return; } } } let target_entity = world.entity(entity).get::().unwrap().get(); if target_entity == entity { warn!( "{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.", caller.map(|location|format!("{location}: ")).unwrap_or_default(), core::any::type_name::(), core::any::type_name::() ); world.commands().entity(entity).remove::(); return; } if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { if let Some(mut relationship_target) = target_entity_mut.get_mut::() { relationship_target.collection_mut_risky().add(entity); } else { let mut target = ::with_capacity(1); target.collection_mut_risky().add(entity); world.commands().entity(target_entity).insert(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(), core::any::type_name::(), core::any::type_name::() ); world.commands().entity(entity).remove::(); } } /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. // note: think of this as "on_drop" fn on_replace( mut world: DeferredWorld, HookContext { entity, relationship_hook_mode, .. }: HookContext, ) { match relationship_hook_mode { RelationshipHookMode::Run => {} RelationshipHookMode::Skip => return, RelationshipHookMode::RunIfNotLinked => { if ::LINKED_SPAWN { return; } } } let target_entity = world.entity(entity).get::().unwrap().get(); if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) { if let Some(mut relationship_target) = target_entity_mut.get_mut::() { relationship_target.collection_mut_risky().remove(entity); if relationship_target.len() == 0 { if let Ok(mut entity) = world.commands().get_entity(target_entity) { // 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::(); } }); } } } } } } /// The iterator type for the source entities in a [`RelationshipTarget`] collection, /// as defined in the [`RelationshipSourceCollection`] trait. pub type SourceIter<'w, R> = <::Collection as RelationshipSourceCollection>::SourceIter<'w>; /// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type. /// See the [`Relationship`] documentation for more information. pub trait RelationshipTarget: Component + Sized { /// If this is true, when despawning or cloning (when [linked cloning is enabled](crate::entity::EntityClonerBuilder::linked_cloning)), the related entities targeting this entity will also be despawned or cloned. /// /// For example, this is set to `true` for Bevy's built-in parent-child relation, defined by [`ChildOf`](crate::prelude::ChildOf) and [`Children`](crate::prelude::Children). /// This means that when a parent is despawned, any children targeting that parent are also despawned (and the same applies to cloning). /// /// To get around this behavior, you can first break the relationship between entities, and *then* despawn or clone. /// This defaults to false when derived. const LINKED_SPAWN: bool; /// The [`Relationship`] that populates this [`RelationshipTarget`] collection. type Relationship: Relationship; /// The collection type that stores the "source" entities for this [`RelationshipTarget`] component. /// /// Check the list of types which implement [`RelationshipSourceCollection`] for the data structures that can be used inside of your component. /// If you need a new collection type, you can implement the [`RelationshipSourceCollection`] trait /// for a type you own which wraps the collection you want to use (to avoid the orphan rule), /// or open an issue on the Bevy repository to request first-party support for your collection type. type Collection: RelationshipSourceCollection; /// Returns a reference to the stored [`RelationshipTarget::Collection`]. fn collection(&self) -> &Self::Collection; /// Returns a mutable reference to the stored [`RelationshipTarget::Collection`]. /// /// # Warning /// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship. fn collection_mut_risky(&mut self) -> &mut Self::Collection; /// Creates a new [`RelationshipTarget`] from the given [`RelationshipTarget::Collection`]. /// /// # Warning /// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship. fn from_collection_risky(collection: Self::Collection) -> Self; /// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection. // note: think of this as "on_drop" fn on_replace(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { let (entities, mut commands) = world.entities_and_commands(); let relationship_target = entities.get(entity).unwrap().get::().unwrap(); for source_entity in relationship_target.iter() { if entities.get(source_entity).is_ok() { commands.queue( entity_command::remove::() .with_entity(source_entity) .handle_error_with(ignore), ); } else { warn!( "{}Tried to despawn non-existent entity {}", caller .map(|location| format!("{location}: ")) .unwrap_or_default(), source_entity ); } } } /// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when /// that entity is despawned. // note: think of this as "on_drop" fn on_despawn(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) { let (entities, mut commands) = world.entities_and_commands(); let relationship_target = entities.get(entity).unwrap().get::().unwrap(); for source_entity in relationship_target.iter() { if entities.get(source_entity).is_ok() { commands.queue( entity_command::despawn() .with_entity(source_entity) .handle_error_with(ignore), ); } else { warn!( "{}Tried to despawn non-existent entity {}", caller .map(|location| format!("{location}: ")) .unwrap_or_default(), source_entity ); } } } /// Creates this [`RelationshipTarget`] with the given pre-allocated entity capacity. fn with_capacity(capacity: usize) -> Self { let collection = ::with_capacity(capacity); Self::from_collection_risky(collection) } /// Iterates the entities stored in this collection. #[inline] fn iter(&self) -> SourceIter<'_, Self> { self.collection().iter() } /// Returns the number of entities in this collection. #[inline] fn len(&self) -> usize { self.collection().len() } /// Returns true if this entity collection is empty. #[inline] fn is_empty(&self) -> bool { self.collection().is_empty() } } /// The "clone behavior" for [`RelationshipTarget`]. This actually creates an empty /// [`RelationshipTarget`] instance with space reserved for the number of targets in the /// original instance. The [`RelationshipTarget`] will then be populated with the proper components /// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities /// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that! /// /// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured /// to spawn recursively. pub fn clone_relationship_target( source: &SourceComponent, context: &mut ComponentCloneCtx, ) { if let Some(component) = source.read::() { let mut cloned = T::with_capacity(component.len()); if context.linked_cloning() && T::LINKED_SPAWN { let collection = cloned.collection_mut_risky(); for entity in component.iter() { collection.add(entity); context.queue_entity_clone(entity); } } context.write_target_component(cloned); } } /// Configures the conditions under which the Relationship insert/replace hooks will be run. #[derive(Copy, Clone, Debug)] pub enum RelationshipHookMode { /// Relationship insert/replace hooks will always run Run, /// Relationship insert/replace hooks will run if [`RelationshipTarget::LINKED_SPAWN`] is false RunIfNotLinked, /// Relationship insert/replace hooks will always be skipped Skip, } #[cfg(test)] mod tests { use crate::world::World; use crate::{component::Component, entity::Entity}; use alloc::vec::Vec; #[test] fn custom_relationship() { #[derive(Component)] #[relationship(relationship_target = LikedBy)] struct Likes(pub Entity); #[derive(Component)] #[relationship_target(relationship = Likes)] struct LikedBy(Vec); let mut world = World::new(); let a = world.spawn_empty().id(); let b = world.spawn(Likes(a)).id(); let c = world.spawn(Likes(a)).id(); assert_eq!(world.entity(a).get::().unwrap().0, &[b, c]); } #[test] fn self_relationship_fails() { #[derive(Component)] #[relationship(relationship_target = RelTarget)] struct Rel(Entity); #[derive(Component)] #[relationship_target(relationship = Rel)] struct RelTarget(Vec); let mut world = World::new(); let a = world.spawn_empty().id(); world.entity_mut(a).insert(Rel(a)); assert!(!world.entity(a).contains::()); assert!(!world.entity(a).contains::()); } #[test] fn relationship_with_missing_target_fails() { #[derive(Component)] #[relationship(relationship_target = RelTarget)] struct Rel(Entity); #[derive(Component)] #[relationship_target(relationship = Rel)] struct RelTarget(Vec); let mut world = World::new(); let a = world.spawn_empty().id(); world.despawn(a); let b = world.spawn(Rel(a)).id(); assert!(!world.entity(b).contains::()); assert!(!world.entity(b).contains::()); } #[test] fn relationship_with_multiple_non_target_fields_compiles() { #[derive(Component)] #[relationship(relationship_target=Target)] #[expect(dead_code, reason = "test struct")] struct Source { #[relationship] target: Entity, foo: u8, bar: u8, } #[derive(Component)] #[relationship_target(relationship=Source)] struct Target(Vec); // No assert necessary, looking to make sure compilation works with the macros } #[test] fn relationship_target_with_multiple_non_target_fields_compiles() { #[derive(Component)] #[relationship(relationship_target=Target)] struct Source(Entity); #[derive(Component)] #[relationship_target(relationship=Source)] #[expect(dead_code, reason = "test struct")] struct Target { #[relationship] target: Vec, foo: u8, bar: u8, } // No assert necessary, looking to make sure compilation works with the macros } }