
# Objective
The existing `RelationshipSourceCollection` uses `Vec` as the only
possible backing for our relationships. While a reasonable choice,
benchmarking use cases might reveal that a different data type is better
or faster.
For example:
- Not all relationships require a stable ordering between the
relationship sources (i.e. children). In cases where we a) have many
such relations and b) don't care about the ordering between them, a hash
set is likely a better datastructure than a `Vec`.
- The number of children-like entities may be small on average, and a
`smallvec` may be faster
## Solution
- Implement `RelationshipSourceCollection` for `EntityHashSet`, our
custom entity-optimized `HashSet`.
-~~Implement `DoubleEndedIterator` for `EntityHashSet` to make things
compile.~~
- This implementation was cursed and very surprising.
- Instead, by moving the iterator type on `RelationshipSourceCollection`
from an erased RPTIT to an explicit associated type we can add a trait
bound on the offending methods!
- Implement `RelationshipSourceCollection` for `SmallVec`
## Testing
I've added a pair of new tests to make sure this pattern compiles
successfully in practice!
## Migration Guide
`EntityHashSet` and `EntityHashMap` are no longer re-exported in
`bevy_ecs::entity` directly. If you were not using `bevy_ecs` / `bevy`'s
`prelude`, you can access them through their now-public modules,
`hash_set` and `hash_map` instead.
## Notes to reviewers
The `EntityHashSet::Iter` type needs to be public for this impl to be
allowed. I initially renamed it to something that wasn't ambiguous and
re-exported it, but as @Victoronz pointed out, that was somewhat
unidiomatic.
In
1a8564898f
,
I instead made the `entity_hash_set` public (and its `entity_hash_set`)
sister public, and removed the re-export. I prefer this design (give me
module docs please), but it leads to a lot of churn in this PR.
Let me know which you'd prefer, and if you'd like me to split that
change out into its own micro PR.
264 lines
12 KiB
Rust
264 lines
12 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;
|
|
|
|
pub use related_methods::*;
|
|
pub use relationship_query::*;
|
|
pub use relationship_source_collection::*;
|
|
|
|
use crate::{
|
|
component::{Component, ComponentId, Mutable},
|
|
entity::Entity,
|
|
system::{
|
|
command::HandleError,
|
|
entity_command::{self, CommandWithEntity},
|
|
error_handler,
|
|
},
|
|
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 [`Parent`](crate::hierarchy::Parent)
|
|
/// [`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.
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::component::Component;
|
|
/// # use bevy_ecs::entity::Entity;
|
|
/// #[derive(Component)]
|
|
/// #[relationship(relationship_target = Children)]
|
|
/// pub struct Parent(pub Entity);
|
|
///
|
|
/// #[derive(Component)]
|
|
/// #[relationship_target(relationship = Parent)]
|
|
/// pub struct Children(Vec<Entity>);
|
|
/// ```
|
|
///
|
|
/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(despawn_descendants)]` 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 Parent(pub Entity);
|
|
///
|
|
/// #[derive(Component)]
|
|
/// #[relationship_target(relationship = Parent, despawn_descendants)]
|
|
/// 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, entity: Entity, _: ComponentId) {
|
|
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.",
|
|
core::any::type_name::<Self>(),
|
|
core::any::type_name::<Self>()
|
|
);
|
|
world.commands().entity(entity).remove::<Self>();
|
|
}
|
|
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.",
|
|
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, entity: Entity, _: ComponentId) {
|
|
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 {
|
|
/// 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, entity: Entity, _: ComponentId) {
|
|
// 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_some() {
|
|
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 {}", 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, entity: Entity, _: ComponentId) {
|
|
// 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_some() {
|
|
commands.push(
|
|
entity_command::despawn()
|
|
.with_entity(source_entity)
|
|
.handle_error_with(error_handler::silent()),
|
|
);
|
|
} else {
|
|
warn!("Tried to despawn non-existent entity {}", 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()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate as bevy_ecs;
|
|
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]);
|
|
}
|
|
}
|