
Fixes #17535 Bevy's approach to handling "entity mapping" during spawning and cloning needs some work. The addition of [Relations](https://github.com/bevyengine/bevy/pull/17398) both [introduced a new "duplicate entities" bug when spawning scenes in the scene system](#17535) and made the weaknesses of the current mapping system exceedingly clear: 1. Entity mapping requires _a ton_ of boilerplate (implement or derive VisitEntities and VisitEntitesMut, then register / reflect MapEntities). Knowing the incantation is challenging and if you forget to do it in part or in whole, spawning subtly breaks. 2. Entity mapping a spawned component in scenes incurs unnecessary overhead: look up ReflectMapEntities, create a _brand new temporary instance_ of the component using FromReflect, map the entities in that instance, and then apply that on top of the actual component using reflection. We can do much better. Additionally, while our new [Entity cloning system](https://github.com/bevyengine/bevy/pull/16132) is already pretty great, it has some areas we can make better: * It doesn't expose semantic info about the clone (ex: ignore or "clone empty"), meaning we can't key off of that in places where it would be useful, such as scene spawning. Rather than duplicating this info across contexts, I think it makes more sense to add that info to the clone system, especially given that we'd like to use cloning code in some of our spawning scenarios. * EntityCloner is currently built in a way that prioritizes a single entity clone * EntityCloner's recursive cloning is built to be done "inside out" in a parallel context (queue commands that each have a clone of EntityCloner). By making EntityCloner the orchestrator of the clone we can remove internal arcs, improve the clarity of the code, make EntityCloner mutable again, and simplify the builder code. * EntityCloner does not currently take into account entity mapping. This is necessary to do true "bullet proof" cloning, would allow us to unify the per-component scene spawning and cloning UX, and ultimately would allow us to use EntityCloner in place of raw reflection for scenes like `Scene(World)` (which would give us a nice performance boost: fewer archetype moves, less reflection overhead). ## Solution ### Improved Entity Mapping First, components now have first-class "entity visiting and mapping" behavior: ```rust #[derive(Component, Reflect)] #[reflect(Component)] struct Inventory { size: usize, #[entities] items: Vec<Entity>, } ``` Any field with the `#[entities]` annotation will be viewable and mappable when cloning and spawning scenes. Compare that to what was required before! ```rust #[derive(Component, Reflect, VisitEntities, VisitEntitiesMut)] #[reflect(Component, MapEntities)] struct Inventory { #[visit_entities(ignore)] size: usize, items: Vec<Entity>, } ``` Additionally, for relationships `#[entities]` is implied, meaning this "just works" in scenes and cloning: ```rust #[derive(Component, Reflect)] #[relationship(relationship_target = Children)] #[reflect(Component)] struct ChildOf(pub Entity); ``` Note that Component _does not_ implement `VisitEntities` directly. Instead, it has `Component::visit_entities` and `Component::visit_entities_mut` methods. This is for a few reasons: 1. We cannot implement `VisitEntities for C: Component` because that would conflict with our impl of VisitEntities for anything that implements `IntoIterator<Item=Entity>`. Preserving that impl is more important from a UX perspective. 2. We should not implement `Component: VisitEntities` VisitEntities in the Component derive, as that would increase the burden of manual Component trait implementors. 3. Making VisitEntitiesMut directly callable for components would make it easy to invalidate invariants defined by a component author. By putting it in the `Component` impl, we can make it harder to call naturally / unavailable to autocomplete using `fn visit_entities_mut(this: &mut Self, ...)`. `ReflectComponent::apply_or_insert` is now `ReflectComponent::apply_or_insert_mapped`. By moving mapping inside this impl, we remove the need to go through the reflection system to do entity mapping, meaning we no longer need to create a clone of the target component, map the entities in that component, and patch those values on top. This will make spawning mapped entities _much_ faster (The default `Component::visit_entities_mut` impl is an inlined empty function, so it will incur no overhead for unmapped entities). ### The Bug Fix To solve #17535, spawning code now skips entities with the new `ComponentCloneBehavior::Ignore` and `ComponentCloneBehavior::RelationshipTarget` variants (note RelationshipTarget is a temporary "workaround" variant that allows scenes to skip these components. This is a temporary workaround that can be removed as these cases should _really_ be using EntityCloner logic, which should be done in a followup PR. When that is done, `ComponentCloneBehavior::RelationshipTarget` can be merged into the normal `ComponentCloneBehavior::Custom`). ### Improved Cloning * `Option<ComponentCloneHandler>` has been replaced by `ComponentCloneBehavior`, which encodes additional intent and context (ex: `Default`, `Ignore`, `Custom`, `RelationshipTarget` (this last one is temporary)). * Global per-world entity cloning configuration has been removed. This felt overly complicated, increased our API surface, and felt too generic. Each clone context can have different requirements (ex: what a user wants in a specific system, what a scene spawner wants, etc). I'd prefer to see how far context-specific EntityCloners get us first. * EntityCloner's internals have been reworked to remove Arcs and make it mutable. * EntityCloner is now directly stored on EntityClonerBuilder, simplifying the code somewhat * EntityCloner's "bundle scratch" pattern has been moved into the new BundleScratch type, improving its usability and making it usable in other contexts (such as future cross-world cloning code). Currently this is still private, but with some higher level safe APIs it could be used externally for making dynamic bundles * EntityCloner's recursive cloning behavior has been "externalized". It is now responsible for orchestrating recursive clones, meaning it no longer needs to be sharable/clone-able across threads / read-only. * EntityCloner now does entity mapping during clones, like scenes do. This gives behavior parity and also makes it more generically useful. * `RelatonshipTarget::RECURSIVE_SPAWN` is now `RelationshipTarget::LINKED_SPAWN`, and this field is used when cloning relationship targets to determine if cloning should happen recursively. The new `LINKED_SPAWN` term was picked to make it more generically applicable across spawning and cloning scenarios. ## Next Steps * I think we should adapt EntityCloner to support cross world cloning. I think this PR helps set the stage for that by making the internals slightly more generalized. We could have a CrossWorldEntityCloner that reuses a lot of this infrastructure. * Once we support cross world cloning, we should use EntityCloner to spawn `Scene(World)` scenes. This would yield significant performance benefits (no archetype moves, less reflection overhead). --------- Co-authored-by: eugineerd <70062110+eugineerd@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
440 lines
15 KiB
Rust
440 lines
15 KiB
Rust
//! The canonical "parent-child" [`Relationship`] for entities, driven by
|
|
//! the [`ChildOf`] [`Relationship`] and the [`Children`] [`RelationshipTarget`].
|
|
//!
|
|
//! See [`ChildOf`] for a full description of the relationship and how to use it.
|
|
//!
|
|
//! [`Relationship`]: crate::relationship::Relationship
|
|
//! [`RelationshipTarget`]: crate::relationship::RelationshipTarget
|
|
|
|
#[cfg(feature = "bevy_reflect")]
|
|
use crate::reflect::{ReflectComponent, ReflectFromWorld};
|
|
use crate::{
|
|
self as bevy_ecs,
|
|
bundle::Bundle,
|
|
component::{Component, HookContext},
|
|
entity::Entity,
|
|
relationship::{RelatedSpawner, RelatedSpawnerCommands},
|
|
system::EntityCommands,
|
|
world::{DeferredWorld, EntityWorldMut, FromWorld, World},
|
|
};
|
|
use alloc::{format, string::String, vec::Vec};
|
|
use core::ops::Deref;
|
|
use core::slice;
|
|
use disqualified::ShortName;
|
|
use log::warn;
|
|
|
|
/// A [`Relationship`](crate::relationship::Relationship) component that creates the canonical
|
|
/// "parent / child" hierarchy. This is the "source of truth" component, and it pairs with
|
|
/// the [`Children`] [`RelationshipTarget`](crate::relationship::RelationshipTarget).
|
|
///
|
|
/// This relationship should be used for things like:
|
|
///
|
|
/// 1. Organizing entities in a scene
|
|
/// 2. Propagating configuration or data inherited from a parent, such as "visibility" or "world-space global transforms".
|
|
/// 3. Ensuring a hierarchy is despawned when an entity is despawned.
|
|
/// 4.
|
|
///
|
|
/// [`ChildOf`] contains a single "target" [`Entity`]. When [`ChildOf`] is inserted on a "source" entity,
|
|
/// the "target" entity will automatically (and immediately, via a component hook) have a [`Children`]
|
|
/// component inserted, and the "source" entity will be added to that [`Children`] instance.
|
|
///
|
|
/// If the [`ChildOf`] component is replaced with a different "target" entity, the old target's [`Children`]
|
|
/// will be automatically (and immediately, via a component hook) be updated to reflect that change.
|
|
///
|
|
/// Likewise, when the [`ChildOf`] component is removed, the "source" entity will be removed from the old
|
|
/// target's [`Children`]. If this results in [`Children`] being empty, [`Children`] will be automatically removed.
|
|
///
|
|
/// When a parent is despawned, all children (and their descendants) will _also_ be despawned.
|
|
///
|
|
/// You can create parent-child relationships in a variety of ways. The most direct way is to insert a [`ChildOf`] component:
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # let mut world = World::new();
|
|
/// let root = world.spawn_empty().id();
|
|
/// let child1 = world.spawn(ChildOf(root)).id();
|
|
/// let child2 = world.spawn(ChildOf(root)).id();
|
|
/// let grandchild = world.spawn(ChildOf(child1)).id();
|
|
///
|
|
/// assert_eq!(&**world.entity(root).get::<Children>().unwrap(), &[child1, child2]);
|
|
/// assert_eq!(&**world.entity(child1).get::<Children>().unwrap(), &[grandchild]);
|
|
///
|
|
/// world.entity_mut(child2).remove::<ChildOf>();
|
|
/// assert_eq!(&**world.entity(root).get::<Children>().unwrap(), &[child1]);
|
|
///
|
|
/// world.entity_mut(root).despawn();
|
|
/// assert!(world.get_entity(root).is_err());
|
|
/// assert!(world.get_entity(child1).is_err());
|
|
/// assert!(world.get_entity(grandchild).is_err());
|
|
/// ```
|
|
///
|
|
/// However if you are spawning many children, you might want to use the [`EntityWorldMut::with_children`] helper instead:
|
|
///
|
|
/// ```
|
|
/// # use bevy_ecs::prelude::*;
|
|
/// # let mut world = World::new();
|
|
/// let mut child1 = Entity::PLACEHOLDER;
|
|
/// let mut child2 = Entity::PLACEHOLDER;
|
|
/// let mut grandchild = Entity::PLACEHOLDER;
|
|
/// let root = world.spawn_empty().with_children(|p| {
|
|
/// child1 = p.spawn_empty().with_children(|p| {
|
|
/// grandchild = p.spawn_empty().id();
|
|
/// }).id();
|
|
/// child2 = p.spawn_empty().id();
|
|
/// }).id();
|
|
///
|
|
/// assert_eq!(&**world.entity(root).get::<Children>().unwrap(), &[child1, child2]);
|
|
/// assert_eq!(&**world.entity(child1).get::<Children>().unwrap(), &[grandchild]);
|
|
/// ```
|
|
#[derive(Component, Clone, PartialEq, Eq, Debug)]
|
|
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
|
#[cfg_attr(
|
|
feature = "bevy_reflect",
|
|
reflect(Component, PartialEq, Debug, FromWorld)
|
|
)]
|
|
#[relationship(relationship_target = Children)]
|
|
pub struct ChildOf(pub Entity);
|
|
|
|
impl ChildOf {
|
|
/// Returns the "target" entity.
|
|
pub fn get(&self) -> Entity {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Deref for ChildOf {
|
|
type Target = Entity;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
// TODO: We need to impl either FromWorld or Default so ChildOf can be registered as Reflect.
|
|
// This is because Reflect deserialize by creating an instance and apply a patch on top.
|
|
// However ChildOf should only ever be set with a real user-defined entity. Its worth looking into
|
|
// better ways to handle cases like this.
|
|
impl FromWorld for ChildOf {
|
|
#[inline(always)]
|
|
fn from_world(_world: &mut World) -> Self {
|
|
ChildOf(Entity::PLACEHOLDER)
|
|
}
|
|
}
|
|
|
|
/// A [`RelationshipTarget`](crate::relationship::RelationshipTarget) collection component that is populated
|
|
/// with entities that "target" this entity with the [`ChildOf`] [`Relationship`](crate::relationship::Relationship) component.
|
|
///
|
|
/// Together, these components form the "canonical parent-child hierarchy". See the [`ChildOf`] component for all full
|
|
/// description of this relationship and instructions on how to use it.
|
|
#[derive(Component, Default, Debug, PartialEq, Eq)]
|
|
#[relationship_target(relationship = ChildOf, linked_spawn)]
|
|
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
|
|
#[cfg_attr(feature = "bevy_reflect", reflect(Component, FromWorld))]
|
|
pub struct Children(Vec<Entity>);
|
|
|
|
impl<'a> IntoIterator for &'a Children {
|
|
type Item = <Self::IntoIter as Iterator>::Item;
|
|
|
|
type IntoIter = slice::Iter<'a, Entity>;
|
|
|
|
#[inline(always)]
|
|
fn into_iter(self) -> Self::IntoIter {
|
|
self.0.iter()
|
|
}
|
|
}
|
|
|
|
impl Deref for Children {
|
|
type Target = [Entity];
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
/// A type alias over [`RelatedSpawner`] used to spawn child entities containing a [`ChildOf`] relationship.
|
|
pub type ChildSpawner<'w> = RelatedSpawner<'w, ChildOf>;
|
|
|
|
/// A type alias over [`RelatedSpawnerCommands`] used to spawn child entities containing a [`ChildOf`] relationship.
|
|
pub type ChildSpawnerCommands<'w> = RelatedSpawnerCommands<'w, ChildOf>;
|
|
|
|
impl<'w> EntityWorldMut<'w> {
|
|
/// Spawns children of this entity (with a [`ChildOf`] relationship) by taking a function that operates on a [`ChildSpawner`].
|
|
pub fn with_children(&mut self, func: impl FnOnce(&mut ChildSpawner)) -> &mut Self {
|
|
self.with_related(func);
|
|
self
|
|
}
|
|
|
|
/// Adds the given children to this entity
|
|
pub fn add_children(&mut self, children: &[Entity]) -> &mut Self {
|
|
self.add_related::<ChildOf>(children)
|
|
}
|
|
|
|
/// Adds the given child to this entity
|
|
pub fn add_child(&mut self, child: Entity) -> &mut Self {
|
|
self.add_related::<ChildOf>(&[child])
|
|
}
|
|
|
|
/// Spawns the passed bundle and adds it to this entity as a child.
|
|
///
|
|
/// For efficient spawning of multiple children, use [`with_children`].
|
|
///
|
|
/// [`with_children`]: EntityWorldMut::with_children
|
|
pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
|
|
let id = self.id();
|
|
self.world_scope(|world| {
|
|
world.spawn((bundle, ChildOf(id)));
|
|
});
|
|
self
|
|
}
|
|
|
|
/// Removes the [`ChildOf`] component, if it exists.
|
|
#[deprecated(since = "0.16.0", note = "Use entity_mut.remove::<ChildOf>()")]
|
|
pub fn remove_parent(&mut self) -> &mut Self {
|
|
self.remove::<ChildOf>();
|
|
self
|
|
}
|
|
|
|
/// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists.
|
|
#[deprecated(since = "0.16.0", note = "Use entity_mut.insert(ChildOf(entity))")]
|
|
pub fn set_parent(&mut self, parent: Entity) -> &mut Self {
|
|
self.insert(ChildOf(parent));
|
|
self
|
|
}
|
|
}
|
|
|
|
impl<'a> EntityCommands<'a> {
|
|
/// Spawns children of this entity (with a [`ChildOf`] relationship) by taking a function that operates on a [`ChildSpawner`].
|
|
pub fn with_children(
|
|
&mut self,
|
|
func: impl FnOnce(&mut RelatedSpawnerCommands<ChildOf>),
|
|
) -> &mut Self {
|
|
self.with_related(func);
|
|
self
|
|
}
|
|
|
|
/// Adds the given children to this entity
|
|
pub fn add_children(&mut self, children: &[Entity]) -> &mut Self {
|
|
self.add_related::<ChildOf>(children)
|
|
}
|
|
|
|
/// Adds the given child to this entity
|
|
pub fn add_child(&mut self, child: Entity) -> &mut Self {
|
|
self.add_related::<ChildOf>(&[child])
|
|
}
|
|
|
|
/// Spawns the passed bundle and adds it to this entity as a child.
|
|
///
|
|
/// For efficient spawning of multiple children, use [`with_children`].
|
|
///
|
|
/// [`with_children`]: EntityCommands::with_children
|
|
pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
|
|
let id = self.id();
|
|
self.commands.spawn((bundle, ChildOf(id)));
|
|
self
|
|
}
|
|
|
|
/// Removes the [`ChildOf`] component, if it exists.
|
|
#[deprecated(since = "0.16.0", note = "Use entity_commands.remove::<ChildOf>()")]
|
|
pub fn remove_parent(&mut self) -> &mut Self {
|
|
self.remove::<ChildOf>();
|
|
self
|
|
}
|
|
|
|
/// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists.
|
|
#[deprecated(since = "0.16.0", note = "Use entity_commands.insert(ChildOf(entity))")]
|
|
pub fn set_parent(&mut self, parent: Entity) -> &mut Self {
|
|
self.insert(ChildOf(parent));
|
|
self
|
|
}
|
|
}
|
|
|
|
/// An `on_insert` component hook that when run, will validate that the parent of a given entity
|
|
/// contains component `C`. This will print a warning if the parent does not contain `C`.
|
|
pub fn validate_parent_has_component<C: Component>(
|
|
world: DeferredWorld,
|
|
HookContext { entity, caller, .. }: HookContext,
|
|
) {
|
|
let entity_ref = world.entity(entity);
|
|
let Some(child_of) = entity_ref.get::<ChildOf>() else {
|
|
return;
|
|
};
|
|
if !world
|
|
.get_entity(child_of.get())
|
|
.is_ok_and(|e| e.contains::<C>())
|
|
{
|
|
// TODO: print name here once Name lives in bevy_ecs
|
|
let name: Option<String> = None;
|
|
warn!(
|
|
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
|
|
This will cause inconsistent behaviors! See: https://bevyengine.org/learn/errors/b0004",
|
|
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
|
|
ty_name = ShortName::of::<C>(),
|
|
name = name.map_or_else(
|
|
|| format!("Entity {}", entity),
|
|
|s| format!("The {s} entity")
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{
|
|
entity::Entity,
|
|
hierarchy::{ChildOf, Children},
|
|
relationship::RelationshipTarget,
|
|
world::World,
|
|
};
|
|
use alloc::{vec, vec::Vec};
|
|
|
|
#[derive(PartialEq, Eq, Debug)]
|
|
struct Node {
|
|
entity: Entity,
|
|
children: Vec<Node>,
|
|
}
|
|
|
|
impl Node {
|
|
fn new(entity: Entity) -> Self {
|
|
Self {
|
|
entity,
|
|
children: Vec::new(),
|
|
}
|
|
}
|
|
|
|
fn new_with(entity: Entity, children: Vec<Node>) -> Self {
|
|
Self { entity, children }
|
|
}
|
|
}
|
|
|
|
fn get_hierarchy(world: &World, entity: Entity) -> Node {
|
|
Node {
|
|
entity,
|
|
children: world
|
|
.entity(entity)
|
|
.get::<Children>()
|
|
.map_or_else(Default::default, |c| {
|
|
c.iter().map(|e| get_hierarchy(world, e)).collect()
|
|
}),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn hierarchy() {
|
|
let mut world = World::new();
|
|
let root = world.spawn_empty().id();
|
|
let child1 = world.spawn(ChildOf(root)).id();
|
|
let grandchild = world.spawn(ChildOf(child1)).id();
|
|
let child2 = world.spawn(ChildOf(root)).id();
|
|
|
|
// Spawn
|
|
let hierarchy = get_hierarchy(&world, root);
|
|
assert_eq!(
|
|
hierarchy,
|
|
Node::new_with(
|
|
root,
|
|
vec![
|
|
Node::new_with(child1, vec![Node::new(grandchild)]),
|
|
Node::new(child2)
|
|
]
|
|
)
|
|
);
|
|
|
|
// Removal
|
|
world.entity_mut(child1).remove::<ChildOf>();
|
|
let hierarchy = get_hierarchy(&world, root);
|
|
assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)]));
|
|
|
|
// Insert
|
|
world.entity_mut(child1).insert(ChildOf(root));
|
|
let hierarchy = get_hierarchy(&world, root);
|
|
assert_eq!(
|
|
hierarchy,
|
|
Node::new_with(
|
|
root,
|
|
vec![
|
|
Node::new(child2),
|
|
Node::new_with(child1, vec![Node::new(grandchild)])
|
|
]
|
|
)
|
|
);
|
|
|
|
// Recursive Despawn
|
|
world.entity_mut(root).despawn();
|
|
assert!(world.get_entity(root).is_err());
|
|
assert!(world.get_entity(child1).is_err());
|
|
assert!(world.get_entity(child2).is_err());
|
|
assert!(world.get_entity(grandchild).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn with_children() {
|
|
let mut world = World::new();
|
|
let mut child1 = Entity::PLACEHOLDER;
|
|
let mut child2 = Entity::PLACEHOLDER;
|
|
let root = world
|
|
.spawn_empty()
|
|
.with_children(|p| {
|
|
child1 = p.spawn_empty().id();
|
|
child2 = p.spawn_empty().id();
|
|
})
|
|
.id();
|
|
|
|
let hierarchy = get_hierarchy(&world, root);
|
|
assert_eq!(
|
|
hierarchy,
|
|
Node::new_with(root, vec![Node::new(child1), Node::new(child2)])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn add_children() {
|
|
let mut world = World::new();
|
|
let child1 = world.spawn_empty().id();
|
|
let child2 = world.spawn_empty().id();
|
|
let root = world.spawn_empty().add_children(&[child1, child2]).id();
|
|
|
|
let hierarchy = get_hierarchy(&world, root);
|
|
assert_eq!(
|
|
hierarchy,
|
|
Node::new_with(root, vec![Node::new(child1), Node::new(child2)])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn self_parenting_invalid() {
|
|
let mut world = World::new();
|
|
let id = world.spawn_empty().id();
|
|
world.entity_mut(id).insert(ChildOf(id));
|
|
assert!(
|
|
world.entity(id).get::<ChildOf>().is_none(),
|
|
"invalid ChildOf relationships should self-remove"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn missing_parent_invalid() {
|
|
let mut world = World::new();
|
|
let parent = world.spawn_empty().id();
|
|
world.entity_mut(parent).despawn();
|
|
let id = world.spawn(ChildOf(parent)).id();
|
|
assert!(
|
|
world.entity(id).get::<ChildOf>().is_none(),
|
|
"invalid ChildOf relationships should self-remove"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn reinsert_same_parent() {
|
|
let mut world = World::new();
|
|
let parent = world.spawn_empty().id();
|
|
let id = world.spawn(ChildOf(parent)).id();
|
|
world.entity_mut(id).insert(ChildOf(parent));
|
|
assert_eq!(
|
|
Some(&ChildOf(parent)),
|
|
world.entity(id).get::<ChildOf>(),
|
|
"ChildOf should still be there"
|
|
);
|
|
}
|
|
}
|