Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/relationship/mod.rs
6600 views
1
//! This module provides functionality to link entities to each other using specialized components called "relationships". See the [`Relationship`] trait for more info.
2
3
mod related_methods;
4
mod relationship_query;
5
mod relationship_source_collection;
6
7
use core::marker::PhantomData;
8
9
use alloc::format;
10
11
use bevy_utils::prelude::DebugName;
12
pub use related_methods::*;
13
pub use relationship_query::*;
14
pub use relationship_source_collection::*;
15
16
use crate::{
17
component::{Component, ComponentCloneBehavior, Mutable},
18
entity::{ComponentCloneCtx, Entity},
19
error::CommandWithEntity,
20
lifecycle::HookContext,
21
world::{DeferredWorld, EntityWorldMut},
22
};
23
use log::warn;
24
25
/// A [`Component`] on a "source" [`Entity`] that references another target [`Entity`], creating a "relationship" between them. Every [`Relationship`]
26
/// has a corresponding [`RelationshipTarget`] type (and vice-versa), which exists on the "target" entity of a relationship and contains the list of all
27
/// "source" entities that relate to the given "target"
28
///
29
/// The [`Relationship`] component is the "source of truth" and the [`RelationshipTarget`] component reflects that source of truth. When a [`Relationship`]
30
/// component is inserted on an [`Entity`], the corresponding [`RelationshipTarget`] component is immediately inserted on the target component if it does
31
/// not already exist, and the "source" entity is automatically added to the [`RelationshipTarget`] collection (this is done via "component hooks").
32
///
33
/// 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)
34
/// [`Relationship`] and the [`Children`](crate::hierarchy::Children) [`RelationshipTarget`].
35
///
36
/// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly.
37
///
38
/// ## Derive
39
///
40
/// [`Relationship`] and [`RelationshipTarget`] can only be derived for structs with a single unnamed field, single named field
41
/// or for named structs where one field is annotated with `#[relationship]`.
42
/// If there are additional fields, they must all implement [`Default`].
43
///
44
/// [`RelationshipTarget`] also requires that the relationship field is private to prevent direct mutation,
45
/// ensuring the correctness of relationships.
46
/// ```
47
/// # use bevy_ecs::component::Component;
48
/// # use bevy_ecs::entity::Entity;
49
/// #[derive(Component)]
50
/// #[relationship(relationship_target = Children)]
51
/// pub struct ChildOf {
52
/// #[relationship]
53
/// pub parent: Entity,
54
/// internal: u8,
55
/// };
56
///
57
/// #[derive(Component)]
58
/// #[relationship_target(relationship = ChildOf)]
59
/// pub struct Children(Vec<Entity>);
60
/// ```
61
///
62
/// When deriving [`RelationshipTarget`] you can specify the `#[relationship_target(linked_spawn)]` attribute to
63
/// automatically despawn entities stored in an entity's [`RelationshipTarget`] when that entity is despawned:
64
///
65
/// ```
66
/// # use bevy_ecs::component::Component;
67
/// # use bevy_ecs::entity::Entity;
68
/// #[derive(Component)]
69
/// #[relationship(relationship_target = Children)]
70
/// pub struct ChildOf(pub Entity);
71
///
72
/// #[derive(Component)]
73
/// #[relationship_target(relationship = ChildOf, linked_spawn)]
74
/// pub struct Children(Vec<Entity>);
75
/// ```
76
pub trait Relationship: Component + Sized {
77
/// The [`Component`] added to the "target" entities of this [`Relationship`], which contains the list of all "source"
78
/// entities that relate to the "target".
79
type RelationshipTarget: RelationshipTarget<Relationship = Self>;
80
81
/// Gets the [`Entity`] ID of the related entity.
82
fn get(&self) -> Entity;
83
84
/// Creates this [`Relationship`] from the given `entity`.
85
fn from(entity: Entity) -> Self;
86
87
/// Changes the current [`Entity`] ID of the entity containing the [`RelationshipTarget`] to another one.
88
///
89
/// This is useful for updating the relationship without overwriting other fields stored in `Self`.
90
///
91
/// # Warning
92
///
93
/// This should generally not be called by user code, as modifying the related entity could invalidate the
94
/// relationship. If this method is used, then the hooks [`on_replace`](Relationship::on_replace) have to
95
/// run before and [`on_insert`](Relationship::on_insert) after it.
96
/// This happens automatically when this method is called with [`EntityWorldMut::modify_component`].
97
///
98
/// Prefer to use regular means of insertions when possible.
99
fn set_risky(&mut self, entity: Entity);
100
101
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
102
fn on_insert(
103
mut world: DeferredWorld,
104
HookContext {
105
entity,
106
caller,
107
relationship_hook_mode,
108
..
109
}: HookContext,
110
) {
111
match relationship_hook_mode {
112
RelationshipHookMode::Run => {}
113
RelationshipHookMode::Skip => return,
114
RelationshipHookMode::RunIfNotLinked => {
115
if <Self::RelationshipTarget as RelationshipTarget>::LINKED_SPAWN {
116
return;
117
}
118
}
119
}
120
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
121
if target_entity == entity {
122
warn!(
123
"{}The {}({target_entity:?}) relationship on entity {entity:?} points to itself. The invalid {} relationship has been removed.",
124
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
125
DebugName::type_name::<Self>(),
126
DebugName::type_name::<Self>()
127
);
128
world.commands().entity(entity).remove::<Self>();
129
return;
130
}
131
// For one-to-one relationships, remove existing relationship before adding new one
132
let current_source_to_remove = world
133
.get_entity(target_entity)
134
.ok()
135
.and_then(|target_entity_ref| target_entity_ref.get::<Self::RelationshipTarget>())
136
.and_then(|relationship_target| {
137
relationship_target
138
.collection()
139
.source_to_remove_before_add()
140
});
141
142
if let Some(current_source) = current_source_to_remove {
143
world.commands().entity(current_source).try_remove::<Self>();
144
}
145
146
if let Ok(mut entity_commands) = world.commands().get_entity(target_entity) {
147
// Deferring is necessary for batch mode
148
entity_commands
149
.entry::<Self::RelationshipTarget>()
150
.and_modify(move |mut relationship_target| {
151
relationship_target.collection_mut_risky().add(entity);
152
})
153
.or_insert_with(move || {
154
let mut target = Self::RelationshipTarget::with_capacity(1);
155
target.collection_mut_risky().add(entity);
156
target
157
});
158
} else {
159
warn!(
160
"{}The {}({target_entity:?}) relationship on entity {entity:?} relates to an entity that does not exist. The invalid {} relationship has been removed.",
161
caller.map(|location|format!("{location}: ")).unwrap_or_default(),
162
DebugName::type_name::<Self>(),
163
DebugName::type_name::<Self>()
164
);
165
world.commands().entity(entity).remove::<Self>();
166
}
167
}
168
169
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
170
// note: think of this as "on_drop"
171
fn on_replace(
172
mut world: DeferredWorld,
173
HookContext {
174
entity,
175
relationship_hook_mode,
176
..
177
}: HookContext,
178
) {
179
match relationship_hook_mode {
180
RelationshipHookMode::Run => {}
181
RelationshipHookMode::Skip => return,
182
RelationshipHookMode::RunIfNotLinked => {
183
if <Self::RelationshipTarget as RelationshipTarget>::LINKED_SPAWN {
184
return;
185
}
186
}
187
}
188
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
189
if let Ok(mut target_entity_mut) = world.get_entity_mut(target_entity) {
190
if let Some(mut relationship_target) =
191
target_entity_mut.get_mut::<Self::RelationshipTarget>()
192
{
193
relationship_target.collection_mut_risky().remove(entity);
194
if relationship_target.len() == 0 {
195
let command = |mut entity: EntityWorldMut| {
196
// this "remove" operation must check emptiness because in the event that an identical
197
// relationship is inserted on top, this despawn would result in the removal of that identical
198
// relationship ... not what we want!
199
if entity
200
.get::<Self::RelationshipTarget>()
201
.is_some_and(RelationshipTarget::is_empty)
202
{
203
entity.remove::<Self::RelationshipTarget>();
204
}
205
};
206
207
world
208
.commands()
209
.queue_silenced(command.with_entity(target_entity));
210
}
211
}
212
}
213
}
214
}
215
216
/// The iterator type for the source entities in a [`RelationshipTarget`] collection,
217
/// as defined in the [`RelationshipSourceCollection`] trait.
218
pub type SourceIter<'w, R> =
219
<<R as RelationshipTarget>::Collection as RelationshipSourceCollection>::SourceIter<'w>;
220
221
/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type.
222
/// See the [`Relationship`] documentation for more information.
223
pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
224
/// 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.
225
///
226
/// 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).
227
/// This means that when a parent is despawned, any children targeting that parent are also despawned (and the same applies to cloning).
228
///
229
/// To get around this behavior, you can first break the relationship between entities, and *then* despawn or clone.
230
/// This defaults to false when derived.
231
const LINKED_SPAWN: bool;
232
/// The [`Relationship`] that populates this [`RelationshipTarget`] collection.
233
type Relationship: Relationship<RelationshipTarget = Self>;
234
/// The collection type that stores the "source" entities for this [`RelationshipTarget`] component.
235
///
236
/// Check the list of types which implement [`RelationshipSourceCollection`] for the data structures that can be used inside of your component.
237
/// If you need a new collection type, you can implement the [`RelationshipSourceCollection`] trait
238
/// for a type you own which wraps the collection you want to use (to avoid the orphan rule),
239
/// or open an issue on the Bevy repository to request first-party support for your collection type.
240
type Collection: RelationshipSourceCollection;
241
242
/// Returns a reference to the stored [`RelationshipTarget::Collection`].
243
fn collection(&self) -> &Self::Collection;
244
/// Returns a mutable reference to the stored [`RelationshipTarget::Collection`].
245
///
246
/// # Warning
247
/// This should generally not be called by user code, as modifying the internal collection could invalidate the relationship.
248
/// The collection should not contain duplicates.
249
fn collection_mut_risky(&mut self) -> &mut Self::Collection;
250
251
/// Creates a new [`RelationshipTarget`] from the given [`RelationshipTarget::Collection`].
252
///
253
/// # Warning
254
/// This should generally not be called by user code, as constructing the internal collection could invalidate the relationship.
255
/// The collection should not contain duplicates.
256
fn from_collection_risky(collection: Self::Collection) -> Self;
257
258
/// The `on_replace` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
259
// note: think of this as "on_drop"
260
fn on_replace(
261
mut world: DeferredWorld,
262
HookContext {
263
entity,
264
relationship_hook_mode,
265
..
266
}: HookContext,
267
) {
268
match relationship_hook_mode {
269
RelationshipHookMode::Run => {}
270
// For RelationshipTarget we don't want to run this hook even if it isn't linked, but for Relationship we do.
271
RelationshipHookMode::Skip | RelationshipHookMode::RunIfNotLinked => return,
272
}
273
let (entities, mut commands) = world.entities_and_commands();
274
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
275
for source_entity in relationship_target.iter() {
276
commands
277
.entity(source_entity)
278
.try_remove::<Self::Relationship>();
279
}
280
}
281
282
/// The `on_despawn` component hook that despawns entities stored in an entity's [`RelationshipTarget`] when
283
/// that entity is despawned.
284
// note: think of this as "on_drop"
285
fn on_despawn(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
286
let (entities, mut commands) = world.entities_and_commands();
287
let relationship_target = entities.get(entity).unwrap().get::<Self>().unwrap();
288
for source_entity in relationship_target.iter() {
289
commands.entity(source_entity).try_despawn();
290
}
291
}
292
293
/// Creates this [`RelationshipTarget`] with the given pre-allocated entity capacity.
294
fn with_capacity(capacity: usize) -> Self {
295
let collection =
296
<Self::Collection as RelationshipSourceCollection>::with_capacity(capacity);
297
Self::from_collection_risky(collection)
298
}
299
300
/// Iterates the entities stored in this collection.
301
#[inline]
302
fn iter(&self) -> SourceIter<'_, Self> {
303
self.collection().iter()
304
}
305
306
/// Returns the number of entities in this collection.
307
#[inline]
308
fn len(&self) -> usize {
309
self.collection().len()
310
}
311
312
/// Returns true if this entity collection is empty.
313
#[inline]
314
fn is_empty(&self) -> bool {
315
self.collection().is_empty()
316
}
317
}
318
319
/// The "clone behavior" for [`RelationshipTarget`]. The [`RelationshipTarget`] will be populated with the proper components
320
/// when the corresponding [`Relationship`] sources of truth are inserted. Cloning the actual entities
321
/// in the original [`RelationshipTarget`] would result in duplicates, so we don't do that!
322
///
323
/// This will also queue up clones of the relationship sources if the [`EntityCloner`](crate::entity::EntityCloner) is configured
324
/// to spawn recursively.
325
pub fn clone_relationship_target<T: RelationshipTarget>(
326
component: &T,
327
cloned: &mut T,
328
context: &mut ComponentCloneCtx,
329
) {
330
if context.linked_cloning() && T::LINKED_SPAWN {
331
let collection = cloned.collection_mut_risky();
332
for entity in component.iter() {
333
collection.add(entity);
334
context.queue_entity_clone(entity);
335
}
336
} else if context.moving() {
337
let target = context.target();
338
let collection = cloned.collection_mut_risky();
339
for entity in component.iter() {
340
collection.add(entity);
341
context.queue_deferred(move |world, _mapper| {
342
// We don't want relationships hooks to run because we are manually constructing the collection here
343
_ = DeferredWorld::from(world)
344
.modify_component_with_relationship_hook_mode::<T::Relationship, ()>(
345
entity,
346
RelationshipHookMode::Skip,
347
|r| r.set_risky(target),
348
);
349
});
350
}
351
}
352
}
353
354
/// Configures the conditions under which the Relationship insert/replace hooks will be run.
355
#[derive(Copy, Clone, Debug)]
356
pub enum RelationshipHookMode {
357
/// Relationship insert/replace hooks will always run
358
Run,
359
/// Relationship insert/replace hooks will run if [`RelationshipTarget::LINKED_SPAWN`] is false
360
RunIfNotLinked,
361
/// Relationship insert/replace hooks will always be skipped
362
Skip,
363
}
364
365
/// Wrapper for components clone specialization using autoderef.
366
#[doc(hidden)]
367
pub struct RelationshipCloneBehaviorSpecialization<T>(PhantomData<T>);
368
369
impl<T> Default for RelationshipCloneBehaviorSpecialization<T> {
370
fn default() -> Self {
371
Self(PhantomData)
372
}
373
}
374
375
/// Base trait for relationship clone specialization using autoderef.
376
#[doc(hidden)]
377
pub trait RelationshipCloneBehaviorBase {
378
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
379
}
380
381
impl<C> RelationshipCloneBehaviorBase for RelationshipCloneBehaviorSpecialization<C> {
382
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
383
// Relationships currently must have `Clone`/`Reflect`-based handler for cloning/moving logic to properly work.
384
ComponentCloneBehavior::Ignore
385
}
386
}
387
388
/// Specialized trait for relationship clone specialization using autoderef.
389
#[doc(hidden)]
390
pub trait RelationshipCloneBehaviorViaReflect {
391
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
392
}
393
394
#[cfg(feature = "bevy_reflect")]
395
impl<C: Relationship + bevy_reflect::Reflect> RelationshipCloneBehaviorViaReflect
396
for &RelationshipCloneBehaviorSpecialization<C>
397
{
398
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
399
ComponentCloneBehavior::reflect()
400
}
401
}
402
403
/// Specialized trait for relationship clone specialization using autoderef.
404
#[doc(hidden)]
405
pub trait RelationshipCloneBehaviorViaClone {
406
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
407
}
408
409
impl<C: Relationship + Clone> RelationshipCloneBehaviorViaClone
410
for &&RelationshipCloneBehaviorSpecialization<C>
411
{
412
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
413
ComponentCloneBehavior::clone::<C>()
414
}
415
}
416
417
/// Specialized trait for relationship target clone specialization using autoderef.
418
#[doc(hidden)]
419
pub trait RelationshipTargetCloneBehaviorViaReflect {
420
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
421
}
422
423
#[cfg(feature = "bevy_reflect")]
424
impl<C: RelationshipTarget + bevy_reflect::Reflect + bevy_reflect::TypePath>
425
RelationshipTargetCloneBehaviorViaReflect for &&&RelationshipCloneBehaviorSpecialization<C>
426
{
427
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
428
ComponentCloneBehavior::Custom(|source, context| {
429
if let Some(component) = source.read::<C>()
430
&& let Ok(mut cloned) = component.reflect_clone_and_take::<C>()
431
{
432
cloned.collection_mut_risky().clear();
433
clone_relationship_target(component, &mut cloned, context);
434
context.write_target_component(cloned);
435
}
436
})
437
}
438
}
439
440
/// Specialized trait for relationship target clone specialization using autoderef.
441
#[doc(hidden)]
442
pub trait RelationshipTargetCloneBehaviorViaClone {
443
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
444
}
445
446
impl<C: RelationshipTarget + Clone> RelationshipTargetCloneBehaviorViaClone
447
for &&&&RelationshipCloneBehaviorSpecialization<C>
448
{
449
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
450
ComponentCloneBehavior::Custom(|source, context| {
451
if let Some(component) = source.read::<C>() {
452
let mut cloned = component.clone();
453
cloned.collection_mut_risky().clear();
454
clone_relationship_target(component, &mut cloned, context);
455
context.write_target_component(cloned);
456
}
457
})
458
}
459
}
460
461
/// We know there's no additional data on Children, so this handler is an optimization to avoid cloning the entire Collection.
462
#[doc(hidden)]
463
pub trait RelationshipTargetCloneBehaviorHierarchy {
464
fn default_clone_behavior(&self) -> ComponentCloneBehavior;
465
}
466
467
impl RelationshipTargetCloneBehaviorHierarchy
468
for &&&&&RelationshipCloneBehaviorSpecialization<crate::hierarchy::Children>
469
{
470
fn default_clone_behavior(&self) -> ComponentCloneBehavior {
471
ComponentCloneBehavior::Custom(|source, context| {
472
if let Some(component) = source.read::<crate::hierarchy::Children>() {
473
let mut cloned = crate::hierarchy::Children::with_capacity(component.len());
474
clone_relationship_target(component, &mut cloned, context);
475
context.write_target_component(cloned);
476
}
477
})
478
}
479
}
480
481
#[cfg(test)]
482
mod tests {
483
use core::marker::PhantomData;
484
485
use crate::prelude::{ChildOf, Children};
486
use crate::world::World;
487
use crate::{component::Component, entity::Entity};
488
use alloc::vec::Vec;
489
490
#[test]
491
fn custom_relationship() {
492
#[derive(Component)]
493
#[relationship(relationship_target = LikedBy)]
494
struct Likes(pub Entity);
495
496
#[derive(Component)]
497
#[relationship_target(relationship = Likes)]
498
struct LikedBy(Vec<Entity>);
499
500
let mut world = World::new();
501
let a = world.spawn_empty().id();
502
let b = world.spawn(Likes(a)).id();
503
let c = world.spawn(Likes(a)).id();
504
assert_eq!(world.entity(a).get::<LikedBy>().unwrap().0, &[b, c]);
505
}
506
507
#[test]
508
fn self_relationship_fails() {
509
#[derive(Component)]
510
#[relationship(relationship_target = RelTarget)]
511
struct Rel(Entity);
512
513
#[derive(Component)]
514
#[relationship_target(relationship = Rel)]
515
struct RelTarget(Vec<Entity>);
516
517
let mut world = World::new();
518
let a = world.spawn_empty().id();
519
world.entity_mut(a).insert(Rel(a));
520
assert!(!world.entity(a).contains::<Rel>());
521
assert!(!world.entity(a).contains::<RelTarget>());
522
}
523
524
#[test]
525
fn relationship_with_missing_target_fails() {
526
#[derive(Component)]
527
#[relationship(relationship_target = RelTarget)]
528
struct Rel(Entity);
529
530
#[derive(Component)]
531
#[relationship_target(relationship = Rel)]
532
struct RelTarget(Vec<Entity>);
533
534
let mut world = World::new();
535
let a = world.spawn_empty().id();
536
world.despawn(a);
537
let b = world.spawn(Rel(a)).id();
538
assert!(!world.entity(b).contains::<Rel>());
539
assert!(!world.entity(b).contains::<RelTarget>());
540
}
541
542
#[test]
543
fn relationship_with_multiple_non_target_fields_compiles() {
544
#[derive(Component)]
545
#[relationship(relationship_target=Target)]
546
#[expect(dead_code, reason = "test struct")]
547
struct Source {
548
#[relationship]
549
target: Entity,
550
foo: u8,
551
bar: u8,
552
}
553
554
#[derive(Component)]
555
#[relationship_target(relationship=Source)]
556
struct Target(Vec<Entity>);
557
558
// No assert necessary, looking to make sure compilation works with the macros
559
}
560
#[test]
561
fn relationship_target_with_multiple_non_target_fields_compiles() {
562
#[derive(Component)]
563
#[relationship(relationship_target=Target)]
564
struct Source(Entity);
565
566
#[derive(Component)]
567
#[relationship_target(relationship=Source)]
568
#[expect(dead_code, reason = "test struct")]
569
struct Target {
570
#[relationship]
571
target: Vec<Entity>,
572
foo: u8,
573
bar: u8,
574
}
575
576
// No assert necessary, looking to make sure compilation works with the macros
577
}
578
579
#[test]
580
fn relationship_with_multiple_unnamed_non_target_fields_compiles() {
581
#[derive(Component)]
582
#[relationship(relationship_target=Target<T>)]
583
struct Source<T: Send + Sync + 'static>(#[relationship] Entity, PhantomData<T>);
584
585
#[derive(Component)]
586
#[relationship_target(relationship=Source<T>)]
587
struct Target<T: Send + Sync + 'static>(#[relationship] Vec<Entity>, PhantomData<T>);
588
589
// No assert necessary, looking to make sure compilation works with the macros
590
}
591
592
#[test]
593
fn parent_child_relationship_with_custom_relationship() {
594
#[derive(Component)]
595
#[relationship(relationship_target = RelTarget)]
596
struct Rel(Entity);
597
598
#[derive(Component)]
599
#[relationship_target(relationship = Rel)]
600
struct RelTarget(Entity);
601
602
let mut world = World::new();
603
604
// Rel on Parent
605
// Despawn Parent
606
let mut commands = world.commands();
607
let child = commands.spawn_empty().id();
608
let parent = commands.spawn(Rel(child)).add_child(child).id();
609
commands.entity(parent).despawn();
610
world.flush();
611
612
assert!(world.get_entity(child).is_err());
613
assert!(world.get_entity(parent).is_err());
614
615
// Rel on Parent
616
// Despawn Child
617
let mut commands = world.commands();
618
let child = commands.spawn_empty().id();
619
let parent = commands.spawn(Rel(child)).add_child(child).id();
620
commands.entity(child).despawn();
621
world.flush();
622
623
assert!(world.get_entity(child).is_err());
624
assert!(!world.entity(parent).contains::<Rel>());
625
626
// Rel on Child
627
// Despawn Parent
628
let mut commands = world.commands();
629
let parent = commands.spawn_empty().id();
630
let child = commands.spawn((ChildOf(parent), Rel(parent))).id();
631
commands.entity(parent).despawn();
632
world.flush();
633
634
assert!(world.get_entity(child).is_err());
635
assert!(world.get_entity(parent).is_err());
636
637
// Rel on Child
638
// Despawn Child
639
let mut commands = world.commands();
640
let parent = commands.spawn_empty().id();
641
let child = commands.spawn((ChildOf(parent), Rel(parent))).id();
642
commands.entity(child).despawn();
643
world.flush();
644
645
assert!(world.get_entity(child).is_err());
646
assert!(!world.entity(parent).contains::<RelTarget>());
647
}
648
649
#[test]
650
fn spawn_batch_with_relationship() {
651
let mut world = World::new();
652
let parent = world.spawn_empty().id();
653
let children = world
654
.spawn_batch((0..10).map(|_| ChildOf(parent)))
655
.collect::<Vec<_>>();
656
657
for &child in &children {
658
assert!(world
659
.get::<ChildOf>(child)
660
.is_some_and(|child_of| child_of.parent() == parent));
661
}
662
assert!(world
663
.get::<Children>(parent)
664
.is_some_and(|children| children.len() == 10));
665
}
666
667
#[test]
668
fn insert_batch_with_relationship() {
669
let mut world = World::new();
670
let parent = world.spawn_empty().id();
671
let child = world.spawn_empty().id();
672
world.insert_batch([(child, ChildOf(parent))]);
673
world.flush();
674
675
assert!(world.get::<ChildOf>(child).is_some());
676
assert!(world.get::<Children>(parent).is_some());
677
}
678
}
679
680