mod related_methods;
mod relationship_query;
mod relationship_source_collection;
use alloc::boxed::Box;
use bevy_platform::sync::Arc;
use bevy_ptr::Ptr;
use core::{any::TypeId, marker::PhantomData};
use alloc::format;
use bevy_utils::prelude::DebugName;
pub use related_methods::*;
pub use relationship_query::*;
pub use relationship_source_collection::*;
use crate::{
component::{Component, ComponentCloneBehavior, ComponentId, Components, Mutable},
entity::{ComponentCloneCtx, Entity},
lifecycle::HookContext,
system::EntityCommand,
world::{DeferredWorld, EntityWorldMut},
};
use log::warn;
pub trait Relationship: Component + Sized {
type RelationshipTarget: RelationshipTarget<Relationship = Self>;
const ALLOW_SELF_REFERENTIAL: bool = false;
fn get(&self) -> Entity;
fn from(entity: Entity) -> Self;
fn set_risky(&mut self, entity: Entity);
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 <Self::RelationshipTarget as RelationshipTarget>::LINKED_SPAWN {
return;
}
}
}
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
if !Self::ALLOW_SELF_REFERENTIAL && target_entity == entity {
warn!(
"{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.\nIf this is intended behavior self-referential relations can be enabled with the allow_self_referential attribute: #[relationship(allow_self_referential)]",
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
DebugName::type_name::<Self>(),
DebugName::type_name::<Self>()
);
world.commands().entity(entity).remove::<Self>();
return;
}
let current_source_to_remove = world
.get_entity(target_entity)
.ok()
.and_then(|target_entity_ref| target_entity_ref.get::<Self::RelationshipTarget>())
.and_then(|relationship_target| {
relationship_target
.collection()
.source_to_remove_before_add()
});
if let Some(current_source) = current_source_to_remove {
world.commands().entity(current_source).try_remove::<Self>();
}
if let Ok(mut entity_commands) = world.commands().get_entity(target_entity) {
entity_commands
.entry::<Self::RelationshipTarget>()
.and_modify(move |mut relationship_target| {
relationship_target.collection_mut_risky().add(entity);
})
.or_insert_with(move || {
let mut target = Self::RelationshipTarget::with_capacity(1);
target.collection_mut_risky().add(entity);
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(),
DebugName::type_name::<Self>(),
DebugName::type_name::<Self>()
);
world.commands().entity(entity).remove::<Self>();
}
}
fn on_discard(
mut world: DeferredWorld,
HookContext {
entity,
relationship_hook_mode,
..
}: HookContext,
) {
match relationship_hook_mode {
RelationshipHookMode::Run => {}
RelationshipHookMode::Skip => return,
RelationshipHookMode::RunIfNotLinked => {
if <Self::RelationshipTarget as RelationshipTarget>::LINKED_SPAWN {
return;
}
}
}
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity)
&& let Some(mut relationship_target) =
target_entity_mut.get_mut::<Self::RelationshipTarget>()
{
relationship_target.collection_mut_risky().remove(entity);
if relationship_target.len() == 0 {
let command = |mut entity: EntityWorldMut| {
if entity
.get::<Self::RelationshipTarget>()
.is_some_and(RelationshipTarget::is_empty)
{
entity.remove::<Self::RelationshipTarget>();
}
};
world
.commands()
.queue_silenced(command.with_entity(target_entity));
}
}
}
}
pub type SourceIter<'w, R> =
<<R as RelationshipTarget>::Collection as RelationshipSourceCollection>::SourceIter<'w>;
pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
const LINKED_SPAWN: bool;
type Relationship: Relationship<RelationshipTarget = Self>;
type Collection: RelationshipSourceCollection;
fn collection(&self) -> &Self::Collection;
fn collection_mut_risky(&mut self) -> &mut Self::Collection;
fn from_collection_risky(collection: Self::Collection) -> Self;
fn on_discard(
mut world: DeferredWorld,
HookContext {
entity,
relationship_hook_mode,
..
}: HookContext,
) {
match relationship_hook_mode {
RelationshipHookMode::Run => {}
RelationshipHookMode::Skip | RelationshipHookMode::RunIfNotLinked => return,
}
let (entities, mut commands) = world.entities_and_commands();
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
for source_entity in relationship_target.iter() {
commands
.entity(source_entity)
.try_remove::<Self::Relationship>();
}
}
fn on_despawn(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let (entities, mut commands) = world.entities_and_commands();
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
for source_entity in relationship_target.iter() {
commands.entity(source_entity).try_despawn();
}
}
fn with_capacity(capacity: usize) -> Self {
let collection =
<Self::Collection as RelationshipSourceCollection>::with_capacity(capacity);
Self::from_collection_risky(collection)
}
#[inline]
fn iter(&self) -> SourceIter<'_, Self> {
self.collection().iter()
}
#[inline]
fn len(&self) -> usize {
self.collection().len()
}
#[inline]
fn is_empty(&self) -> bool {
self.collection().is_empty()
}
}
pub fn clone_relationship_target<T: RelationshipTarget>(
component: &T,
cloned: &mut T,
context: &mut ComponentCloneCtx,
) {
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);
}
} else if context.moving() {
let target = context.target();
let collection = cloned.collection_mut_risky();
for entity in component.iter() {
collection.add(entity);
context.queue_deferred(move |world, _mapper| {
_ = DeferredWorld::from(world)
.modify_component_with_relationship_hook_mode::<T::Relationship, ()>(
entity,
RelationshipHookMode::Skip,
|r| r.set_risky(target),
);
});
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum RelationshipHookMode {
Run,
RunIfNotLinked,
Skip,
}
#[doc(hidden)]
pub struct RelationshipCloneBehaviorSpecialization<T>(PhantomData<T>);
impl<T> Default for RelationshipCloneBehaviorSpecialization<T> {
fn default() -> Self {
Self(PhantomData)
}
}
#[doc(hidden)]
pub trait RelationshipCloneBehaviorBase {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C> RelationshipCloneBehaviorBase for RelationshipCloneBehaviorSpecialization<C> {
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Ignore
}
}
#[doc(hidden)]
pub trait RelationshipCloneBehaviorViaReflect {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
#[cfg(feature = "bevy_reflect")]
impl<C: Relationship + bevy_reflect::Reflect> RelationshipCloneBehaviorViaReflect
for &RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::reflect()
}
}
#[doc(hidden)]
pub trait RelationshipCloneBehaviorViaClone {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C: Relationship + Clone> RelationshipCloneBehaviorViaClone
for &&RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::clone::<C>()
}
}
#[doc(hidden)]
pub trait RelationshipTargetCloneBehaviorViaReflect {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
#[cfg(feature = "bevy_reflect")]
impl<C: RelationshipTarget + bevy_reflect::Reflect + bevy_reflect::TypePath>
RelationshipTargetCloneBehaviorViaReflect for &&&RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Custom(|source, context| {
if let Some(component) = source.read::<C>()
&& let Ok(mut cloned) = component.reflect_clone_and_take::<C>()
{
cloned.collection_mut_risky().clear();
clone_relationship_target(component, &mut cloned, context);
context.write_target_component(cloned);
}
})
}
}
#[doc(hidden)]
pub trait RelationshipTargetCloneBehaviorViaClone {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl<C: RelationshipTarget + Clone> RelationshipTargetCloneBehaviorViaClone
for &&&&RelationshipCloneBehaviorSpecialization<C>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Custom(|source, context| {
if let Some(component) = source.read::<C>() {
let mut cloned = component.clone();
cloned.collection_mut_risky().clear();
clone_relationship_target(component, &mut cloned, context);
context.write_target_component(cloned);
}
})
}
}
#[doc(hidden)]
pub trait RelationshipTargetCloneBehaviorHierarchy {
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
}
impl RelationshipTargetCloneBehaviorHierarchy
for &&&&&RelationshipCloneBehaviorSpecialization<crate::hierarchy::Children>
{
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
ComponentCloneBehavior::Custom(|source, context| {
if let Some(component) = source.read::<crate::hierarchy::Children>() {
let mut cloned = crate::hierarchy::Children::with_capacity(component.len());
clone_relationship_target(component, &mut cloned, context);
context.write_target_component(cloned);
}
})
}
}
#[derive(Clone)]
pub enum RelationshipAccessorInitializer {
Relationship {
entity_field_offset: usize,
linked_spawn: bool,
allow_self_referential: bool,
relationship_target_getter: Arc<dyn Fn(&Components) -> Option<ComponentId>>,
},
RelationshipTarget {
iter: for<'a> unsafe fn(Ptr<'a>) -> Box<dyn Iterator<Item = Entity> + 'a>,
linked_spawn: bool,
allow_self_referential: bool,
relationship_getter: Arc<dyn Fn(&Components) -> Option<ComponentId>>,
},
}
impl core::fmt::Debug for RelationshipAccessorInitializer {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Relationship {
entity_field_offset,
linked_spawn,
allow_self_referential,
relationship_target_getter: _,
} => f
.debug_struct("Relationship")
.field("entity_field_offset", entity_field_offset)
.field("linked_spawn", linked_spawn)
.field("allow_self_referential", allow_self_referential)
.finish(),
Self::RelationshipTarget {
iter,
linked_spawn,
allow_self_referential,
relationship_getter: _,
} => f
.debug_struct("RelationshipTarget")
.field("iter", iter)
.field("linked_spawn", linked_spawn)
.field("allow_self_referential", allow_self_referential)
.finish(),
}
}
}
#[derive(Clone, Debug, Default)]
pub(crate) enum MaybeRelationshipAccessor {
#[default]
NoAccessor,
Initializer(Box<RelationshipAccessorInitializer>),
Accessor(RelationshipAccessor),
}
impl MaybeRelationshipAccessor {
pub fn accessor(&self) -> Option<&RelationshipAccessor> {
match self {
MaybeRelationshipAccessor::Accessor(relationship_accessor) => {
Some(relationship_accessor)
}
_ => None,
}
}
pub fn initialize(&mut self, id: ComponentId, components: &mut Components) {
_ = (|| {
let accessor = self.initializer_to_accessor(|getter| getter(components))?;
let counterpart_id = accessor.counterpart_id();
let counterpart_slot = components.get_relationship_accessor_mut(counterpart_id)?;
let counterpart_accessor = counterpart_slot.initializer_to_accessor(|_| Some(id))?;
*counterpart_slot = MaybeRelationshipAccessor::Accessor(counterpart_accessor);
*self = MaybeRelationshipAccessor::Accessor(accessor);
Some(())
})();
}
fn initializer_to_accessor(
&self,
mapper: impl FnOnce(&dyn Fn(&Components) -> Option<ComponentId>) -> Option<ComponentId>,
) -> Option<RelationshipAccessor> {
let MaybeRelationshipAccessor::Initializer(initializer) = self else {
return None;
};
Some(match *initializer.as_ref() {
RelationshipAccessorInitializer::Relationship {
entity_field_offset,
linked_spawn,
allow_self_referential,
ref relationship_target_getter,
} => {
let relationship_target = mapper(relationship_target_getter.as_ref())?;
RelationshipAccessor::Relationship {
entity_field_offset,
linked_spawn,
allow_self_referential,
relationship_target,
}
}
RelationshipAccessorInitializer::RelationshipTarget {
iter,
linked_spawn,
allow_self_referential,
ref relationship_getter,
} => {
let relationship = mapper(relationship_getter.as_ref())?;
RelationshipAccessor::RelationshipTarget {
iter,
linked_spawn,
allow_self_referential,
relationship,
}
}
})
}
}
impl From<Option<RelationshipAccessorInitializer>> for MaybeRelationshipAccessor {
fn from(value: Option<RelationshipAccessorInitializer>) -> Self {
value
.map(|v| MaybeRelationshipAccessor::Initializer(Box::new(v)))
.unwrap_or(MaybeRelationshipAccessor::NoAccessor)
}
}
#[derive(Debug, Clone, Copy)]
pub enum RelationshipAccessor {
Relationship {
entity_field_offset: usize,
linked_spawn: bool,
allow_self_referential: bool,
relationship_target: ComponentId,
},
RelationshipTarget {
iter: for<'a> unsafe fn(Ptr<'a>) -> Box<dyn Iterator<Item = Entity> + 'a>,
linked_spawn: bool,
allow_self_referential: bool,
relationship: ComponentId,
},
}
impl RelationshipAccessor {
pub fn counterpart_id(&self) -> ComponentId {
match self {
RelationshipAccessor::Relationship {
relationship_target,
..
} => *relationship_target,
RelationshipAccessor::RelationshipTarget { relationship, .. } => *relationship,
}
}
pub fn linked_spawn(&self) -> bool {
match self {
RelationshipAccessor::Relationship { linked_spawn, .. }
| RelationshipAccessor::RelationshipTarget { linked_spawn, .. } => *linked_spawn,
}
}
pub fn allow_self_referential(&self) -> bool {
match self {
RelationshipAccessor::Relationship {
allow_self_referential,
..
}
| RelationshipAccessor::RelationshipTarget {
allow_self_referential,
..
} => *allow_self_referential,
}
}
}
pub struct ComponentRelationshipAccessor<C: ?Sized> {
pub(crate) initializer: RelationshipAccessorInitializer,
phantom: PhantomData<C>,
}
impl<C> ComponentRelationshipAccessor<C> {
pub unsafe fn relationship(entity_field_offset: usize) -> Self
where
C: Relationship,
{
let getter: Box<dyn Fn(&Components) -> Option<ComponentId>> =
Box::new(|components| components.get_id(TypeId::of::<C::RelationshipTarget>()));
Self {
initializer: RelationshipAccessorInitializer::Relationship {
entity_field_offset,
linked_spawn: C::RelationshipTarget::LINKED_SPAWN,
allow_self_referential: C::ALLOW_SELF_REFERENTIAL,
relationship_target_getter: Arc::from(getter),
},
phantom: Default::default(),
}
}
pub fn relationship_target() -> Self
where
C: RelationshipTarget,
{
let getter: Box<dyn Fn(&Components) -> Option<ComponentId>> =
Box::new(|components| components.get_id(TypeId::of::<C::Relationship>()));
Self {
initializer: RelationshipAccessorInitializer::RelationshipTarget {
iter: |ptr| unsafe { Box::new(RelationshipTarget::iter(ptr.deref::<C>())) },
linked_spawn: C::LINKED_SPAWN,
allow_self_referential: C::Relationship::ALLOW_SELF_REFERENTIAL,
relationship_getter: Arc::from(getter),
},
phantom: Default::default(),
}
}
}
#[cfg(test)]
mod tests {
use core::marker::PhantomData;
use core::sync::atomic::AtomicBool;
use crate::lifecycle::HookContext;
use crate::prelude::{ChildOf, Children};
use crate::relationship::{Relationship, RelationshipAccessor};
use crate::world::{DeferredWorld, 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_by_default() {
#[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 self_relationship_succeeds_with_allow_self_referential() {
#[derive(Component)]
#[relationship(relationship_target = RelTarget, allow_self_referential)]
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>());
assert_eq!(world.entity(a).get::<Rel>().unwrap().get(), a);
assert_eq!(&*world.entity(a).get::<RelTarget>().unwrap().0, &[a]);
}
#[test]
fn self_relationship_removal_with_allow_self_referential() {
#[derive(Component)]
#[relationship(relationship_target = RelTarget, allow_self_referential)]
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>());
world.entity_mut(a).remove::<Rel>();
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>());
}
#[test]
fn relationship_with_multiple_non_target_fields_compiles() {
#[expect(
dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)]
#[derive(Component)]
#[relationship(relationship_target=Target)]
struct Source {
#[relationship]
target: Entity,
foo: u8,
bar: u8,
}
#[expect(
dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)]
#[derive(Component)]
#[relationship_target(relationship=Source)]
struct Target(Vec<Entity>);
}
#[test]
fn relationship_target_with_multiple_non_target_fields_compiles() {
#[expect(
dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)]
#[derive(Component)]
#[relationship(relationship_target=Target)]
struct Source(Entity);
#[expect(
dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)]
#[derive(Component)]
#[relationship_target(relationship=Source)]
struct Target {
#[relationship]
target: Vec<Entity>,
foo: u8,
bar: u8,
}
}
#[test]
fn relationship_with_multiple_unnamed_non_target_fields_compiles() {
#[expect(
dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)]
#[derive(Component)]
#[relationship(relationship_target=Target<T>)]
struct Source<T: Send + Sync + 'static>(#[relationship] Entity, PhantomData<T>);
#[expect(
dead_code,
reason = "This struct is used as a compilation test to test the derive macros, and as such is intentionally never constructed."
)]
#[derive(Component)]
#[relationship_target(relationship=Source<T>)]
struct Target<T: Send + Sync + 'static>(#[relationship] Vec<Entity>, PhantomData<T>);
}
#[test]
fn parent_child_relationship_with_custom_relationship() {
#[derive(Component)]
#[relationship(relationship_target = RelTarget)]
struct Rel(Entity);
#[derive(Component)]
#[relationship_target(relationship = Rel)]
struct RelTarget(Entity);
let mut world = World::new();
let mut commands = world.commands();
let child = commands.spawn_empty().id();
let parent = commands.spawn(Rel(child)).add_child(child).id();
commands.entity(parent).despawn();
world.flush();
assert!(world.get_entity(child).is_err());
assert!(world.get_entity(parent).is_err());
let mut commands = world.commands();
let child = commands.spawn_empty().id();
let parent = commands.spawn(Rel(child)).add_child(child).id();
commands.entity(child).despawn();
world.flush();
assert!(world.get_entity(child).is_err());
assert!(!world.entity(parent).contains::<Rel>());
let mut commands = world.commands();
let parent = commands.spawn_empty().id();
let child = commands.spawn((ChildOf(parent), Rel(parent))).id();
commands.entity(parent).despawn();
world.flush();
assert!(world.get_entity(child).is_err());
assert!(world.get_entity(parent).is_err());
let mut commands = world.commands();
let parent = commands.spawn_empty().id();
let child = commands.spawn((ChildOf(parent), Rel(parent))).id();
commands.entity(child).despawn();
world.flush();
assert!(world.get_entity(child).is_err());
assert!(!world.entity(parent).contains::<RelTarget>());
}
#[test]
fn spawn_batch_with_relationship() {
let mut world = World::new();
let parent = world.spawn_empty().id();
let children = world
.spawn_batch((0..10).map(|_| ChildOf(parent)))
.collect::<Vec<_>>();
for &child in &children {
assert!(world
.get::<ChildOf>(child)
.is_some_and(|child_of| child_of.parent() == parent));
}
assert!(world
.get::<Children>(parent)
.is_some_and(|children| children.len() == 10));
}
#[test]
fn insert_batch_with_relationship() {
let mut world = World::new();
let parent = world.spawn_empty().id();
let child = world.spawn_empty().id();
world.insert_batch([(child, ChildOf(parent))]);
world.flush();
assert!(world.get::<ChildOf>(child).is_some());
assert!(world.get::<Children>(parent).is_some());
}
#[test]
fn dynamically_traverse_hierarchy() {
let mut world = World::new();
let child_of_id = world.register_component::<ChildOf>();
let children_id = world.register_component::<Children>();
let parent = world.spawn_empty().id();
let child = world.spawn_empty().id();
world.entity_mut(child).insert(ChildOf(parent));
world.flush();
let children_ptr = world.get_by_id(parent, children_id).unwrap();
let RelationshipAccessor::RelationshipTarget { iter, .. } = world
.components()
.get_info(children_id)
.unwrap()
.relationship_accessor()
.unwrap()
else {
unreachable!()
};
let children: Vec<_> = unsafe { iter(children_ptr).collect() };
assert_eq!(children, alloc::vec![child]);
let child_of_ptr = world.get_by_id(child, child_of_id).unwrap();
let RelationshipAccessor::Relationship {
entity_field_offset,
..
} = world
.components()
.get_info(child_of_id)
.unwrap()
.relationship_accessor()
.unwrap()
else {
unreachable!()
};
let child_of_entity: Entity =
unsafe { *child_of_ptr.byte_add(*entity_field_offset).deref() };
assert_eq!(child_of_entity, parent);
}
#[test]
fn relationship_accessor() {
#[derive(Component)]
#[relationship(relationship_target = LikedBy)]
struct Likes {
_a: u16,
#[relationship]
e: Entity,
_b: (i8, u8),
}
#[derive(Component)]
#[relationship_target(relationship = Likes)]
struct LikedBy(Vec<Entity>);
let mut world = World::new();
let likes_id = world.register_component::<Likes>();
let liked_by_id = world.register_component::<LikedBy>();
let likes_accessor = world
.components()
.get_info(likes_id)
.unwrap()
.relationship_accessor()
.unwrap();
match *likes_accessor {
RelationshipAccessor::Relationship {
entity_field_offset,
linked_spawn,
allow_self_referential,
relationship_target,
} => {
assert_eq!(entity_field_offset, core::mem::offset_of!(Likes, e));
assert!(!linked_spawn);
assert!(!allow_self_referential);
assert_eq!(relationship_target, liked_by_id);
}
_ => {
panic!("Not a Relationship")
}
}
let liked_by_accessor = world
.components()
.get_info(liked_by_id)
.unwrap()
.relationship_accessor()
.unwrap();
match *liked_by_accessor {
RelationshipAccessor::RelationshipTarget {
iter,
linked_spawn,
allow_self_referential,
relationship,
} => {
let liked_by = LikedBy(alloc::vec![
world.spawn_empty().id(),
world.spawn_empty().id(),
world.spawn_empty().id()
]);
unsafe {
assert_eq!(iter((&liked_by).into()).collect::<Vec<_>>(), liked_by.0);
}
assert!(!linked_spawn);
assert!(!allow_self_referential);
assert_eq!(relationship, likes_id);
}
_ => {
panic!("Not a RelationshipTarget")
}
}
#[derive(Component)]
#[relationship(relationship_target = RelTarget, allow_self_referential)]
struct Rel(Entity);
#[derive(Component)]
#[relationship_target(relationship = Rel, linked_spawn)]
struct RelTarget(Vec<Entity>);
let rel_id = world.register_component::<Rel>();
let rel_target_id = world.register_component::<RelTarget>();
let rel_accessor = world
.components()
.get_info(rel_id)
.unwrap()
.relationship_accessor()
.unwrap();
assert!(rel_accessor.linked_spawn());
assert!(rel_accessor.allow_self_referential());
let rel_target_accessor = world
.components()
.get_info(rel_target_id)
.unwrap()
.relationship_accessor()
.unwrap();
assert!(rel_target_accessor.linked_spawn());
assert!(rel_target_accessor.allow_self_referential());
}
#[test]
pub fn component_hooks_compatibility() {
static ADD_CALLED: AtomicBool = AtomicBool::new(false);
static INSERT_CALLED: AtomicBool = AtomicBool::new(false);
static DISCARD_CALLED: AtomicBool = AtomicBool::new(false);
static REMOVE_CALLED: AtomicBool = AtomicBool::new(false);
static DESPAWN_CALLED: AtomicBool = AtomicBool::new(false);
#[derive(Component)]
#[relationship(relationship_target = RelTarget)]
#[component(on_add, on_insert, on_discard, on_remove, on_despawn)]
struct Rel(Entity);
#[derive(Component)]
#[relationship_target(relationship = Rel)]
struct RelTarget(Entity);
impl Rel {
fn on_add(world: DeferredWorld, context: HookContext) {
let &Rel(target) = world.get(context.entity).unwrap();
assert!(!world.entity(target).contains::<RelTarget>());
ADD_CALLED.store(true, core::sync::atomic::Ordering::Relaxed);
}
fn on_insert(world: DeferredWorld, context: HookContext) {
let &Rel(target) = world.get(context.entity).unwrap();
assert!(!world.entity(target).contains::<RelTarget>());
INSERT_CALLED.store(true, core::sync::atomic::Ordering::Relaxed);
}
fn on_discard(world: DeferredWorld, context: HookContext) {
let &Rel(target) = world.get(context.entity).unwrap();
assert!(world.entity(target).contains::<RelTarget>());
DISCARD_CALLED.store(true, core::sync::atomic::Ordering::Relaxed);
}
fn on_remove(world: DeferredWorld, context: HookContext) {
let &Rel(target) = world.get(context.entity).unwrap();
assert!(world.entity(target).contains::<RelTarget>());
REMOVE_CALLED.store(true, core::sync::atomic::Ordering::Relaxed);
}
fn on_despawn(world: DeferredWorld, context: HookContext) {
let &Rel(target) = world.get(context.entity).unwrap();
assert!(world.entity(target).contains::<RelTarget>());
DESPAWN_CALLED.store(true, core::sync::atomic::Ordering::Relaxed);
}
}
let mut world = World::new();
let target = world.spawn_empty().id();
let source = world.spawn(Rel(target)).id();
assert!(world.entity(target).contains::<RelTarget>());
assert!(ADD_CALLED.load(core::sync::atomic::Ordering::Relaxed));
assert!(INSERT_CALLED.load(core::sync::atomic::Ordering::Relaxed));
world.despawn(source);
assert!(DISCARD_CALLED.load(core::sync::atomic::Ordering::Relaxed));
assert!(REMOVE_CALLED.load(core::sync::atomic::Ordering::Relaxed));
assert!(DESPAWN_CALLED.load(core::sync::atomic::Ordering::Relaxed));
}
}