Switch ChildOf back to tuple struct (#18672)

# Objective

In #17905 we swapped to a named field on `ChildOf` to help resolve
variable naming ambiguity of child vs parent (ex: `child_of.parent`
clearly reads as "I am accessing the parent of the child_of
relationship", whereas `child_of.0` is less clear).

Unfortunately this has the side effect of making initialization less
ideal. `ChildOf { parent }` reads just as well as `ChildOf(parent)`, but
`ChildOf { parent: root }` doesn't read nearly as well as
`ChildOf(root)`.

## Solution

Move back to `ChildOf(pub Entity)` but add a `child_of.parent()`
function and use it for all accesses. The downside here is that users
are no longer "forced" to access the parent field with `parent`
nomenclature, but I think this strikes the right balance.

Take a look at the diff. I think the results provide strong evidence for
this change. Initialization has the benefit of reading much better _and_
of taking up significantly less space, as many lines go from 3 to 1, and
we're cutting out a bunch of syntax in some cases.

Sadly I do think this should land in 0.16 as the cost of doing this
_after_ the relationships migration is high.
This commit is contained in:
Carter Anderson 2025-04-01 17:10:10 -07:00 committed by François Mockers
parent 5cbc20f787
commit 1553ee98ff
22 changed files with 90 additions and 118 deletions

View File

@ -155,7 +155,7 @@ fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
for parent in current_hierarchy_level { for parent in current_hierarchy_level {
for _ in 0..children { for _ in 0..children {
let child_id = world.spawn((B::default(), ChildOf { parent })).id(); let child_id = world.spawn((B::default(), ChildOf(parent))).id();
hierarchy_level.push(child_id); hierarchy_level.push(child_id);
} }
} }

View File

@ -1337,9 +1337,9 @@ mod tests {
fn recursive_clone() { fn recursive_clone() {
let mut world = World::new(); let mut world = World::new();
let root = world.spawn_empty().id(); let root = world.spawn_empty().id();
let child1 = world.spawn(ChildOf { parent: root }).id(); let child1 = world.spawn(ChildOf(root)).id();
let grandchild = world.spawn(ChildOf { parent: child1 }).id(); let grandchild = world.spawn(ChildOf(child1)).id();
let child2 = world.spawn(ChildOf { parent: root }).id(); let child2 = world.spawn(ChildOf(root)).id();
let clone_root = world.spawn_empty().id(); let clone_root = world.spawn_empty().id();
EntityCloner::build(&mut world) EntityCloner::build(&mut world)

View File

@ -54,9 +54,9 @@ use log::warn;
/// # use bevy_ecs::prelude::*; /// # use bevy_ecs::prelude::*;
/// # let mut world = World::new(); /// # let mut world = World::new();
/// let root = world.spawn_empty().id(); /// let root = world.spawn_empty().id();
/// let child1 = world.spawn(ChildOf { parent: root }).id(); /// let child1 = world.spawn(ChildOf(root)).id();
/// let child2 = world.spawn(ChildOf { parent: root }).id(); /// let child2 = world.spawn(ChildOf(root)).id();
/// let grandchild = world.spawn(ChildOf { parent: child1 }).id(); /// let grandchild = world.spawn(ChildOf(child1)).id();
/// ///
/// assert_eq!(&**world.entity(root).get::<Children>().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(root).get::<Children>().unwrap(), &[child1, child2]);
/// assert_eq!(&**world.entity(child1).get::<Children>().unwrap(), &[grandchild]); /// assert_eq!(&**world.entity(child1).get::<Children>().unwrap(), &[grandchild]);
@ -96,9 +96,21 @@ use log::warn;
)] )]
#[relationship(relationship_target = Children)] #[relationship(relationship_target = Children)]
#[doc(alias = "IsChild", alias = "Parent")] #[doc(alias = "IsChild", alias = "Parent")]
pub struct ChildOf { pub struct ChildOf(pub Entity);
impl ChildOf {
/// The parent entity of this child entity. /// The parent entity of this child entity.
pub parent: Entity, #[inline]
pub fn parent(&self) -> Entity {
self.0
}
/// The parent entity of this child entity.
#[deprecated(since = "0.16.0", note = "Use child_of.parent() instead")]
#[inline]
pub fn get(&self) -> Entity {
self.0
}
} }
// TODO: We need to impl either FromWorld or Default so ChildOf can be registered as Reflect. // TODO: We need to impl either FromWorld or Default so ChildOf can be registered as Reflect.
@ -108,9 +120,7 @@ pub struct ChildOf {
impl FromWorld for ChildOf { impl FromWorld for ChildOf {
#[inline(always)] #[inline(always)]
fn from_world(_world: &mut World) -> Self { fn from_world(_world: &mut World) -> Self {
ChildOf { ChildOf(Entity::PLACEHOLDER)
parent: Entity::PLACEHOLDER,
}
} }
} }
@ -316,7 +326,7 @@ impl<'w> EntityWorldMut<'w> {
pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
let parent = self.id(); let parent = self.id();
self.world_scope(|world| { self.world_scope(|world| {
world.spawn((bundle, ChildOf { parent })); world.spawn((bundle, ChildOf(parent)));
}); });
self self
} }
@ -329,12 +339,9 @@ impl<'w> EntityWorldMut<'w> {
} }
/// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists. /// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists.
#[deprecated( #[deprecated(since = "0.16.0", note = "Use entity_mut.insert(ChildOf(entity))")]
since = "0.16.0",
note = "Use entity_mut.insert(ChildOf { parent: entity })"
)]
pub fn set_parent(&mut self, parent: Entity) -> &mut Self { pub fn set_parent(&mut self, parent: Entity) -> &mut Self {
self.insert(ChildOf { parent }); self.insert(ChildOf(parent));
self self
} }
} }
@ -394,7 +401,7 @@ impl<'a> EntityCommands<'a> {
/// [`with_children`]: EntityCommands::with_children /// [`with_children`]: EntityCommands::with_children
pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self {
let parent = self.id(); let parent = self.id();
self.commands.spawn((bundle, ChildOf { parent })); self.commands.spawn((bundle, ChildOf(parent)));
self self
} }
@ -406,12 +413,9 @@ impl<'a> EntityCommands<'a> {
} }
/// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists. /// Inserts the [`ChildOf`] component with the given `parent` entity, if it exists.
#[deprecated( #[deprecated(since = "0.16.0", note = "Use entity_commands.insert(ChildOf(entity))")]
since = "0.16.0",
note = "Use entity_commands.insert(ChildOf { parent: entity })"
)]
pub fn set_parent(&mut self, parent: Entity) -> &mut Self { pub fn set_parent(&mut self, parent: Entity) -> &mut Self {
self.insert(ChildOf { parent }); self.insert(ChildOf(parent));
self self
} }
} }
@ -427,7 +431,7 @@ pub fn validate_parent_has_component<C: Component>(
return; return;
}; };
if !world if !world
.get_entity(child_of.parent) .get_entity(child_of.parent())
.is_ok_and(|e| e.contains::<C>()) .is_ok_and(|e| e.contains::<C>())
{ {
// TODO: print name here once Name lives in bevy_ecs // TODO: print name here once Name lives in bevy_ecs
@ -527,9 +531,9 @@ mod tests {
fn hierarchy() { fn hierarchy() {
let mut world = World::new(); let mut world = World::new();
let root = world.spawn_empty().id(); let root = world.spawn_empty().id();
let child1 = world.spawn(ChildOf { parent: root }).id(); let child1 = world.spawn(ChildOf(root)).id();
let grandchild = world.spawn(ChildOf { parent: child1 }).id(); let grandchild = world.spawn(ChildOf(child1)).id();
let child2 = world.spawn(ChildOf { parent: root }).id(); let child2 = world.spawn(ChildOf(root)).id();
// Spawn // Spawn
let hierarchy = get_hierarchy(&world, root); let hierarchy = get_hierarchy(&world, root);
@ -550,7 +554,7 @@ mod tests {
assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)])); assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)]));
// Insert // Insert
world.entity_mut(child1).insert(ChildOf { parent: root }); world.entity_mut(child1).insert(ChildOf(root));
let hierarchy = get_hierarchy(&world, root); let hierarchy = get_hierarchy(&world, root);
assert_eq!( assert_eq!(
hierarchy, hierarchy,
@ -638,7 +642,7 @@ mod tests {
fn self_parenting_invalid() { fn self_parenting_invalid() {
let mut world = World::new(); let mut world = World::new();
let id = world.spawn_empty().id(); let id = world.spawn_empty().id();
world.entity_mut(id).insert(ChildOf { parent: id }); world.entity_mut(id).insert(ChildOf(id));
assert!( assert!(
world.entity(id).get::<ChildOf>().is_none(), world.entity(id).get::<ChildOf>().is_none(),
"invalid ChildOf relationships should self-remove" "invalid ChildOf relationships should self-remove"
@ -650,7 +654,7 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
let parent = world.spawn_empty().id(); let parent = world.spawn_empty().id();
world.entity_mut(parent).despawn(); world.entity_mut(parent).despawn();
let id = world.spawn(ChildOf { parent }).id(); let id = world.spawn(ChildOf(parent)).id();
assert!( assert!(
world.entity(id).get::<ChildOf>().is_none(), world.entity(id).get::<ChildOf>().is_none(),
"invalid ChildOf relationships should self-remove" "invalid ChildOf relationships should self-remove"
@ -661,10 +665,10 @@ mod tests {
fn reinsert_same_parent() { fn reinsert_same_parent() {
let mut world = World::new(); let mut world = World::new();
let parent = world.spawn_empty().id(); let parent = world.spawn_empty().id();
let id = world.spawn(ChildOf { parent }).id(); let id = world.spawn(ChildOf(parent)).id();
world.entity_mut(id).insert(ChildOf { parent }); world.entity_mut(id).insert(ChildOf(parent));
assert_eq!( assert_eq!(
Some(&ChildOf { parent }), Some(&ChildOf(parent)),
world.entity(id).get::<ChildOf>(), world.entity(id).get::<ChildOf>(),
"ChildOf should still be there" "ChildOf should still be there"
); );
@ -699,11 +703,11 @@ mod tests {
assert_eq!( assert_eq!(
world.entity(child_a).get::<ChildOf>().unwrap(), world.entity(child_a).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(child_c).get::<ChildOf>().unwrap(), world.entity(child_c).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert!(world.entity(child_b).get::<ChildOf>().is_none()); assert!(world.entity(child_b).get::<ChildOf>().is_none());
} }
@ -739,7 +743,7 @@ mod tests {
assert_eq!(children.0, [child]); assert_eq!(children.0, [child]);
assert_eq!( assert_eq!(
world.entity(child).get::<ChildOf>().unwrap(), world.entity(child).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
} }
@ -762,11 +766,11 @@ mod tests {
assert_eq!( assert_eq!(
world.entity(child_a).get::<ChildOf>().unwrap(), world.entity(child_a).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(child_b).get::<ChildOf>().unwrap(), world.entity(child_b).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(parent).get::<Children>().unwrap().0, world.entity(parent).get::<Children>().unwrap().0,
@ -781,15 +785,15 @@ mod tests {
); );
assert_eq!( assert_eq!(
world.entity(child_a).get::<ChildOf>().unwrap(), world.entity(child_a).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(child_c).get::<ChildOf>().unwrap(), world.entity(child_c).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(child_d).get::<ChildOf>().unwrap(), world.entity(child_d).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(parent).get::<Children>().unwrap().0, world.entity(parent).get::<Children>().unwrap().0,
@ -844,11 +848,11 @@ mod tests {
assert_eq!( assert_eq!(
world.entity(child_a).get::<ChildOf>().unwrap(), world.entity(child_a).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(child_b).get::<ChildOf>().unwrap(), world.entity(child_b).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(parent).get::<Children>().unwrap().0, world.entity(parent).get::<Children>().unwrap().0,
@ -863,11 +867,11 @@ mod tests {
); );
assert_eq!( assert_eq!(
world.entity(child_c).get::<ChildOf>().unwrap(), world.entity(child_c).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(child_d).get::<ChildOf>().unwrap(), world.entity(child_d).get::<ChildOf>().unwrap(),
&ChildOf { parent } &ChildOf(parent)
); );
assert_eq!( assert_eq!(
world.entity(parent).get::<Children>().unwrap().0, world.entity(parent).get::<Children>().unwrap().0,
@ -974,11 +978,10 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
let parent = world.spawn_empty().id(); let parent = world.spawn_empty().id();
let other = world.spawn_empty().id(); let other = world.spawn_empty().id();
let child = world.spawn(ChildOf { parent }).id(); let child = world.spawn(ChildOf(parent)).id();
world.entity_mut(child).insert_with_relationship_hook_mode( world
ChildOf { parent: other }, .entity_mut(child)
RelationshipHookMode::Skip, .insert_with_relationship_hook_mode(ChildOf(other), RelationshipHookMode::Skip);
);
assert_eq!( assert_eq!(
&**world.entity(parent).get::<Children>().unwrap(), &**world.entity(parent).get::<Children>().unwrap(),
&[child], &[child],

View File

@ -521,9 +521,9 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
let a = world.spawn_empty().id(); let a = world.spawn_empty().id();
let b = world.spawn(ChildOf { parent: a }).id(); let b = world.spawn(ChildOf(a)).id();
let c = world.spawn(ChildOf { parent: a }).id(); let c = world.spawn(ChildOf(a)).id();
let d = world.spawn(ChildOf { parent: b }).id(); let d = world.spawn(ChildOf(b)).id();
world world
.entity_mut(a) .entity_mut(a)

View File

@ -165,7 +165,7 @@ impl<E: Event + Clone> Traversal<FocusedInput<E>> for WindowTraversal {
// Send event to parent, if it has one. // Send event to parent, if it has one.
if let Some(child_of) = child_of { if let Some(child_of) = child_of {
return Some(child_of.parent); return Some(child_of.parent());
}; };
// Otherwise, send it to the window entity (unless this is a window entity). // Otherwise, send it to the window entity (unless this is a window entity).
@ -338,7 +338,7 @@ impl IsFocused for World {
if e == entity { if e == entity {
return true; return true;
} }
if let Some(parent) = self.entity(e).get::<ChildOf>().map(|c| c.parent) { if let Some(parent) = self.entity(e).get::<ChildOf>().map(ChildOf::parent) {
e = parent; e = parent;
} else { } else {
return false; return false;

View File

@ -375,22 +375,8 @@ mod tests {
let world = app.world_mut(); let world = app.world_mut();
let tab_group_entity = world.spawn(TabGroup::new(0)).id(); let tab_group_entity = world.spawn(TabGroup::new(0)).id();
let tab_entity_1 = world let tab_entity_1 = world.spawn((TabIndex(0), ChildOf(tab_group_entity))).id();
.spawn(( let tab_entity_2 = world.spawn((TabIndex(1), ChildOf(tab_group_entity))).id();
TabIndex(0),
ChildOf {
parent: tab_group_entity,
},
))
.id();
let tab_entity_2 = world
.spawn((
TabIndex(1),
ChildOf {
parent: tab_group_entity,
},
))
.id();
let mut system_state: SystemState<TabNavigation> = SystemState::new(world); let mut system_state: SystemState<TabNavigation> = SystemState::new(world);
let tab_navigation = system_state.get(world); let tab_navigation = system_state.get(world);

View File

@ -92,7 +92,7 @@ where
// Send event to parent, if it has one. // Send event to parent, if it has one.
if let Some(child_of) = child_of { if let Some(child_of) = child_of {
return Some(child_of.parent); return Some(child_of.parent());
}; };
// Otherwise, send it to the window entity (unless this is a window entity). // Otherwise, send it to the window entity (unless this is a window entity).

View File

@ -411,7 +411,7 @@ fn visibility_propagate_system(
Visibility::Hidden => false, Visibility::Hidden => false,
// fall back to true if no parent is found or parent lacks components // fall back to true if no parent is found or parent lacks components
Visibility::Inherited => child_of Visibility::Inherited => child_of
.and_then(|c| visibility_query.get(c.parent).ok()) .and_then(|c| visibility_query.get(c.parent()).ok())
.is_none_or(|(_, x)| x.get()), .is_none_or(|(_, x)| x.get()),
}; };
let (_, mut inherited_visibility) = visibility_query let (_, mut inherited_visibility) = visibility_query
@ -786,9 +786,7 @@ mod test {
.entity_mut(parent2) .entity_mut(parent2)
.insert(Visibility::Visible); .insert(Visibility::Visible);
// Simulate a change in the parent component // Simulate a change in the parent component
app.world_mut() app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent
.entity_mut(child2)
.insert(ChildOf { parent: parent2 }); // example of changing parent
// Run the system again to propagate changes // Run the system again to propagate changes
app.update(); app.update();

View File

@ -325,7 +325,7 @@ mod tests {
.unwrap() .unwrap()
.get::<ChildOf>() .get::<ChildOf>()
.unwrap() .unwrap()
.parent, .parent(),
"something about reloading the scene is touching entities with the same scene Ids" "something about reloading the scene is touching entities with the same scene Ids"
); );
assert_eq!( assert_eq!(
@ -335,7 +335,7 @@ mod tests {
.unwrap() .unwrap()
.get::<ChildOf>() .get::<ChildOf>()
.unwrap() .unwrap()
.parent, .parent(),
"something about reloading the scene is touching components not defined in the scene but on entities defined in the scene" "something about reloading the scene is touching components not defined in the scene but on entities defined in the scene"
); );
assert_eq!( assert_eq!(
@ -345,7 +345,7 @@ mod tests {
.unwrap() .unwrap()
.get::<ChildOf>() .get::<ChildOf>()
.expect("something is wrong with this test, and the scene components don't have a parent/child relationship") .expect("something is wrong with this test, and the scene components don't have a parent/child relationship")
.parent, .parent(),
"something is wrong with this test or the code reloading scenes since the relationship between scene entities is broken" "something is wrong with this test or the code reloading scenes since the relationship between scene entities is broken"
); );
} }

View File

@ -892,30 +892,15 @@ mod tests {
// Spawn entities with different parent first before parenting them to the actual root, allowing us // Spawn entities with different parent first before parenting them to the actual root, allowing us
// to decouple child order from archetype-creation-order // to decouple child order from archetype-creation-order
let child1 = scene_world let child1 = scene_world
.spawn(( .spawn((ChildOf(temporary_root), ComponentA { x: 1.0, y: 1.0 }))
ChildOf {
parent: temporary_root,
},
ComponentA { x: 1.0, y: 1.0 },
))
.id(); .id();
let child2 = scene_world let child2 = scene_world
.spawn(( .spawn((ChildOf(temporary_root), ComponentA { x: 2.0, y: 2.0 }))
ChildOf {
parent: temporary_root,
},
ComponentA { x: 2.0, y: 2.0 },
))
.id(); .id();
// the "first" child is intentionally spawned with a different component to force it into a "newer" archetype, // the "first" child is intentionally spawned with a different component to force it into a "newer" archetype,
// meaning it will be iterated later in the spawn code. // meaning it will be iterated later in the spawn code.
let child0 = scene_world let child0 = scene_world
.spawn(( .spawn((ChildOf(temporary_root), ComponentF))
ChildOf {
parent: temporary_root,
},
ComponentF,
))
.id(); .id();
scene_world scene_world

View File

@ -524,7 +524,7 @@ pub fn detect_text_needs_rerender<Root: Component>(
)); ));
continue; continue;
}; };
let mut parent: Entity = span_child_of.parent; let mut parent: Entity = span_child_of.parent();
// Search for the nearest ancestor with ComputedTextBlock. // Search for the nearest ancestor with ComputedTextBlock.
// Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited // Note: We assume the perf cost from duplicate visits in the case that multiple spans in a block are visited
@ -555,7 +555,7 @@ pub fn detect_text_needs_rerender<Root: Component>(
)); ));
break; break;
}; };
parent = next_child_of.parent; parent = next_child_of.parent();
} }
} }
} }

View File

@ -124,7 +124,7 @@ mod tests {
let mut e = app.world_mut().spawn(transform); let mut e = app.world_mut().spawn(transform);
if let Some(parent) = entity { if let Some(parent) = entity {
e.insert(ChildOf { parent }); e.insert(ChildOf(parent));
} }
entity = Some(e.id()); entity = Some(e.id());

View File

@ -53,14 +53,14 @@ pub fn mark_dirty_trees(
) { ) {
for entity in changed_transforms.iter().chain(orphaned.read()) { for entity in changed_transforms.iter().chain(orphaned.read()) {
let mut next = entity; let mut next = entity;
while let Ok((parent, mut tree)) = transforms.get_mut(next) { while let Ok((child_of, mut tree)) = transforms.get_mut(next) {
if tree.is_changed() && !tree.is_added() { if tree.is_changed() && !tree.is_added() {
// If the component was changed, this part of the tree has already been processed. // If the component was changed, this part of the tree has already been processed.
// Ignore this if the change was caused by the component being added. // Ignore this if the change was caused by the component being added.
break; break;
} }
tree.set_changed(); tree.set_changed();
if let Some(parent) = parent.map(|p| p.parent) { if let Some(parent) = child_of.map(ChildOf::parent) {
next = parent; next = parent;
} else { } else {
break; break;
@ -121,7 +121,7 @@ mod serial {
for (child, child_of) in child_query.iter_many(children) { for (child, child_of) in child_query.iter_many(children) {
assert_eq!( assert_eq!(
child_of.parent, entity, child_of.parent(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
); );
// SAFETY: // SAFETY:
@ -221,7 +221,7 @@ mod serial {
let Some(children) = children else { return }; let Some(children) = children else { return };
for (child, child_of) in child_query.iter_many(children) { for (child, child_of) in child_query.iter_many(children) {
assert_eq!( assert_eq!(
child_of.parent, entity, child_of.parent(), entity,
"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle" "Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
); );
// SAFETY: The caller guarantees that `transform_query` will not be fetched for any // SAFETY: The caller guarantees that `transform_query` will not be fetched for any
@ -452,7 +452,7 @@ mod parallel {
// Static scene optimization // Static scene optimization
return None; return None;
} }
assert_eq!(child_of.parent, parent); assert_eq!(child_of.parent(), parent);
// Transform prop is expensive - this helps avoid updating entire subtrees if // Transform prop is expensive - this helps avoid updating entire subtrees if
// the GlobalTransform is unchanged, at the cost of an added equality check. // the GlobalTransform is unchanged, at the cost of an added equality check.
@ -586,8 +586,8 @@ mod test {
let root = commands.spawn(offset_transform(3.3)).id(); let root = commands.spawn(offset_transform(3.3)).id();
let parent = commands.spawn(offset_transform(4.4)).id(); let parent = commands.spawn(offset_transform(4.4)).id();
let child = commands.spawn(offset_transform(5.5)).id(); let child = commands.spawn(offset_transform(5.5)).id();
commands.entity(parent).insert(ChildOf { parent: root }); commands.entity(parent).insert(ChildOf(root));
commands.entity(child).insert(ChildOf { parent }); commands.entity(child).insert(ChildOf(parent));
command_queue.apply(&mut world); command_queue.apply(&mut world);
schedule.run(&mut world); schedule.run(&mut world);

View File

@ -150,7 +150,7 @@ impl<'w, 's> UiChildren<'w, 's> {
/// Returns the UI parent of the provided entity. /// Returns the UI parent of the provided entity.
pub fn get_parent(&'s self, entity: Entity) -> Option<Entity> { pub fn get_parent(&'s self, entity: Entity) -> Option<Entity> {
self.parents_query.get(entity).ok().map(|p| p.parent) self.parents_query.get(entity).ok().map(ChildOf::parent)
} }
/// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed. /// Given an entity in the UI hierarchy, check if its set of children has changed, e.g if children has been added/removed or if the order has changed.

View File

@ -132,7 +132,7 @@ pub fn ui_layout_system(
// Note: This does not cover the case where a parent's Node component was removed. // Note: This does not cover the case where a parent's Node component was removed.
// Users are responsible for fixing hierarchies if they do that (it is not recommended). // Users are responsible for fixing hierarchies if they do that (it is not recommended).
// Detecting it here would be a permanent perf burden on the hot path. // Detecting it here would be a permanent perf burden on the hot path.
if child_of.is_changed() && !ui_children.is_ui_node(child_of.parent) { if child_of.is_changed() && !ui_children.is_ui_node(child_of.parent()) {
warn!( warn!(
"Node ({entity}) is in a non-UI entity hierarchy. You are using an entity \ "Node ({entity}) is in a non-UI entity hierarchy. You are using an entity \
with UI components as a child of an entity without UI components, your UI layout may be broken." with UI components as a child of an entity without UI components, your UI layout may be broken."

View File

@ -174,7 +174,7 @@ pub fn update_ui_context_system(
} }
for (entity, child_of) in reparented_nodes.iter() { for (entity, child_of) in reparented_nodes.iter() {
let Ok(computed_target) = computed_target_query.get(child_of.parent) else { let Ok(computed_target) = computed_target_query.get(child_of.parent()) else {
continue; continue;
}; };

View File

@ -260,7 +260,7 @@ fn queue_node_for_update(
window_children: &mut Vec<NodeId>, window_children: &mut Vec<NodeId>,
) { ) {
let should_push = if let Some(child_of) = child_of { let should_push = if let Some(child_of) = child_of {
!node_entities.contains(child_of.parent) !node_entities.contains(child_of.parent())
} else { } else {
true true
}; };

View File

@ -460,7 +460,7 @@ fn move_sphere(
}; };
// Grab its transform. // Grab its transform.
let Ok(mut transform) = transforms.get_mut(child_of.parent) else { let Ok(mut transform) = transforms.get_mut(child_of.parent()) else {
return; return;
}; };

View File

@ -187,7 +187,7 @@ fn set_visibility_ranges(
break; break;
} }
match child_of { match child_of {
Some(child_of) => current = child_of.parent, Some(child_of) => current = child_of.parent(),
None => break, None => break,
} }
} }

View File

@ -51,7 +51,7 @@ fn joint_animation(
// Iter skinned mesh entity // Iter skinned mesh entity
for child_of in &children { for child_of in &children {
// Mesh node is the parent of the skinned mesh entity. // Mesh node is the parent of the skinned mesh entity.
let mesh_node_entity = child_of.parent; let mesh_node_entity = child_of.parent();
// Get `Children` in the mesh node. // Get `Children` in the mesh node.
let mesh_node_parent = parents.get(mesh_node_entity).unwrap(); let mesh_node_parent = parents.get(mesh_node_entity).unwrap();

View File

@ -107,7 +107,7 @@ fn assign_clips(
} }
// Go to the next parent. // Go to the next parent.
current = children.get(entity).ok().map(|c| c.parent); current = children.get(entity).ok().map(ChildOf::parent);
} }
} }

View File

@ -110,14 +110,14 @@ fn button_system(
// Update parent counter on click // Update parent counter on click
for (interaction, child_of) in &mut interaction_query { for (interaction, child_of) in &mut interaction_query {
if matches!(interaction, Interaction::Pressed) { if matches!(interaction, Interaction::Pressed) {
let mut counter = counter_query.get_mut(child_of.parent).unwrap(); let mut counter = counter_query.get_mut(child_of.parent()).unwrap();
counter.0 += 1; counter.0 += 1;
} }
} }
// Update button labels to match their parent counter // Update button labels to match their parent counter
for (children, child_of) in &labels_query { for (children, child_of) in &labels_query {
let counter = counter_query.get(child_of.parent).unwrap(); let counter = counter_query.get(child_of.parent()).unwrap();
let mut text = text_query.get_mut(children[0]).unwrap(); let mut text = text_query.get_mut(children[0]).unwrap();
**text = counter.0.to_string(); **text = counter.0.to_string();