
# Objective fixes #17896 ## Solution Change ChildOf ( Entity ) to ChildOf { parent: Entity } by doing this we also allow users to use named structs for relationship derives, When you have more than 1 field in a struct with named fields the macro will look for a field with the attribute #[relationship] and all of the other fields should implement the Default trait. Unnamed fields are still supported. When u have a unnamed struct with more than one field the macro will fail. Do we want to support something like this ? ```rust #[derive(Component)] #[relationship_target(relationship = ChildOf)] pub struct Children (#[relationship] Entity, u8); ``` I could add this, it but doesn't seem nice. ## Testing crates/bevy_ecs - cargo test ## Showcase ```rust 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 { children: Vec<Entity> }; ``` --------- Co-authored-by: Tim Overbeek <oorbecktim@Tims-MacBook-Pro.local> Co-authored-by: Tim Overbeek <oorbecktim@c-001-001-042.client.nl.eduvpn.org> Co-authored-by: Tim Overbeek <oorbecktim@c-001-001-059.client.nl.eduvpn.org> Co-authored-by: Tim Overbeek <oorbecktim@c-001-001-054.client.nl.eduvpn.org> Co-authored-by: Tim Overbeek <oorbecktim@c-001-001-027.client.nl.eduvpn.org>
357 lines
16 KiB
Rust
357 lines
16 KiB
Rust
//! 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},
|
|
system::{
|
|
command::HandleError,
|
|
entity_command::{self, CommandWithEntity},
|
|
error_handler, Commands,
|
|
},
|
|
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 child: Entity,
|
|
/// internal: u8,
|
|
/// };
|
|
///
|
|
/// #[derive(Component)]
|
|
/// #[relationship_target(relationship = ChildOf)]
|
|
/// pub struct Children(Vec<Entity>);
|
|
/// ```
|
|
///
|
|
/// 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<Entity>);
|
|
/// ```
|
|
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<Relationship = Self>;
|
|
|
|
/// 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, .. }: HookContext) {
|
|
let target_entity = world.entity(entity).get::<Self>().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::<Self>(),
|
|
core::any::type_name::<Self>()
|
|
);
|
|
world.commands().entity(entity).remove::<Self>();
|
|
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::<Self::RelationshipTarget>()
|
|
{
|
|
relationship_target.collection_mut_risky().add(entity);
|
|
} else {
|
|
let mut target = <Self::RelationshipTarget as RelationshipTarget>::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::<Self>(),
|
|
core::any::type_name::<Self>()
|
|
);
|
|
world.commands().entity(entity).remove::<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, .. }: HookContext) {
|
|
let target_entity = world.entity(entity).get::<Self>().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::<Self::RelationshipTarget>()
|
|
{
|
|
relationship_target.collection_mut_risky().remove(entity);
|
|
if relationship_target.len() == 0 {
|
|
if let Some(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::<Self::RelationshipTarget>()
|
|
.is_some_and(RelationshipTarget::is_empty)
|
|
{
|
|
entity.remove::<Self::RelationshipTarget>();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The iterator type for the source entities in a [`RelationshipTarget`] collection,
|
|
/// as defined in the [`RelationshipSourceCollection`] trait.
|
|
pub type SourceIter<'w, R> =
|
|
<<R as RelationshipTarget>::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<Mutability = Mutable> + Sized {
|
|
/// If this is true, when despawning or cloning (when [recursion is enabled](crate::entity::EntityClonerBuilder::recursive)), 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<RelationshipTarget = Self>;
|
|
/// 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) {
|
|
// NOTE: this unsafe code is an optimization. We could make this safe, but it would require
|
|
// copying the RelationshipTarget collection
|
|
// SAFETY: This only reads the Self component and queues Remove commands
|
|
unsafe {
|
|
let world = world.as_unsafe_world_cell();
|
|
let relationship_target = world.get_entity(entity).unwrap().get::<Self>().unwrap();
|
|
let mut commands = world.get_raw_command_queue();
|
|
for source_entity in relationship_target.iter() {
|
|
if world.get_entity(source_entity).is_ok() {
|
|
commands.push(
|
|
entity_command::remove::<Self::Relationship>()
|
|
.with_entity(source_entity)
|
|
.handle_error_with(error_handler::silent()),
|
|
);
|
|
} 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) {
|
|
// NOTE: this unsafe code is an optimization. We could make this safe, but it would require
|
|
// copying the RelationshipTarget collection
|
|
// SAFETY: This only reads the Self component and queues despawn commands
|
|
unsafe {
|
|
let world = world.as_unsafe_world_cell();
|
|
let relationship_target = world.get_entity(entity).unwrap().get::<Self>().unwrap();
|
|
let mut commands = world.get_raw_command_queue();
|
|
for source_entity in relationship_target.iter() {
|
|
if world.get_entity(source_entity).is_ok() {
|
|
commands.push(
|
|
entity_command::despawn()
|
|
.with_entity(source_entity)
|
|
.handle_error_with(error_handler::silent()),
|
|
);
|
|
} 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 =
|
|
<Self::Collection as RelationshipSourceCollection>::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<T: RelationshipTarget>(
|
|
_commands: &mut Commands,
|
|
context: &mut ComponentCloneCtx,
|
|
) {
|
|
if let Some(component) = context.read_source_component::<T>() {
|
|
if context.is_recursive() && T::LINKED_SPAWN {
|
|
for entity in component.iter() {
|
|
context.queue_entity_clone(entity);
|
|
}
|
|
}
|
|
context.write_target_component(T::with_capacity(component.len()));
|
|
}
|
|
}
|
|
|
|
#[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<Entity>);
|
|
|
|
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::<LikedBy>().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<Entity>);
|
|
|
|
let mut world = World::new();
|
|
let a = world.spawn_empty().id();
|
|
world.entity_mut(a).insert(Rel(a));
|
|
assert!(!world.entity(a).contains::<Rel>());
|
|
assert!(!world.entity(a).contains::<RelTarget>());
|
|
}
|
|
|
|
#[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<Entity>);
|
|
|
|
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::<Rel>());
|
|
assert!(!world.entity(b).contains::<RelTarget>());
|
|
}
|
|
}
|