From 6df0567b4643673ebb4b443af5b709bac2cf824f Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 14:57:01 +0100 Subject: [PATCH 01/16] relationship macro upgrade --- crates/bevy_ecs/macros/src/component.rs | 116 ++++++++++++++++-------- crates/bevy_ecs/src/relationship/mod.rs | 10 +- 2 files changed, 86 insertions(+), 40 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 76732c0b895ac..40bbc116463ab 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -9,8 +9,8 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::{Comma, Paren}, - Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Fields, Ident, Index, LitStr, Member, - Path, Result, Token, Visibility, + Data, DataStruct, DeriveInput, ExprClosure, ExprPath, Field, Fields, Ident, Index, LitStr, + Member, Path, Result, Token, Visibility, }; pub fn derive_event(input: TokenStream) -> TokenStream { @@ -651,25 +651,29 @@ fn derive_relationship( let Some(relationship) = &attrs.relationship else { return Ok(None); }; - const RELATIONSHIP_FORMAT_MESSAGE: &str = "Relationship derives must be a tuple struct with the only element being an EntityTargets type (ex: ChildOf(Entity))"; - if let Data::Struct(DataStruct { - fields: Fields::Unnamed(unnamed_fields), + let Data::Struct(DataStruct { + fields, struct_token, .. }) = &ast.data - { - if unnamed_fields.unnamed.len() != 1 { - return Err(syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE)); - } - if unnamed_fields.unnamed.first().is_none() { - return Err(syn::Error::new( - struct_token.span(), - RELATIONSHIP_FORMAT_MESSAGE, - )); - } - } else { - return Err(syn::Error::new(ast.span(), RELATIONSHIP_FORMAT_MESSAGE)); + else { + return Err(syn::Error::new( + ast.span(), + "Relationship can only be derived for structs.", + )); + }; + let field = relationship_field(fields, struct_token.span())?; + + let relationship_member: Member = match field { + Some(field) => field.ident.clone().map_or(Member::from(0), Member::Named), + None => return Err(syn::Error::new( + fields.span(), + "Relationship can only be derived for structs with a single unnamed field or for structs where one field is annotated with #[relationship].", + )), }; + let members = fields + .members() + .filter(|member| member != &relationship_member); let struct_name = &ast.ident; let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); @@ -682,12 +686,15 @@ fn derive_relationship( #[inline(always)] fn get(&self) -> #bevy_ecs_path::entity::Entity { - self.0 + self.#relationship_member } #[inline] fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { - Self(entity) + Self { + #(#members: Default::default(),),* + #relationship_member: entity + } } } })) @@ -702,30 +709,36 @@ fn derive_relationship_target( return Ok(None); }; - const RELATIONSHIP_TARGET_FORMAT_MESSAGE: &str = "RelationshipTarget derives must be a tuple struct with the first element being a private RelationshipSourceCollection (ex: Children(Vec))"; - let collection = if let Data::Struct(DataStruct { - fields: Fields::Unnamed(unnamed_fields), + let Data::Struct(DataStruct { + fields, struct_token, .. }) = &ast.data - { - if let Some(first) = unnamed_fields.unnamed.first() { - if first.vis != Visibility::Inherited { - return Err(syn::Error::new(first.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); - } - first.ty.clone() - } else { - return Err(syn::Error::new( - struct_token.span(), - RELATIONSHIP_TARGET_FORMAT_MESSAGE, - )); - } - } else { + else { return Err(syn::Error::new( ast.span(), - RELATIONSHIP_TARGET_FORMAT_MESSAGE, + "RelationshipTarget can only be derived for structs.", )); }; + let field = relationship_field(fields, struct_token.span())?; + + let field = match field { + Some(field) => field, + None => return Err(syn::Error::new( + fields.span(), + "RelationshipTarget can only be derived for structs with a single private unnamed field or for structs where one field is annotated with #[relationship] and is private.", + )), + }; + if field.vis != Visibility::Inherited { + return Err(syn::Error::new(field.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); + } + let collection = &field.ty; + + let relationship_member = field.ident.clone().map_or(Member::from(0), Member::Named); + + let members = fields + .members() + .filter(|member| member != &relationship_member); let relationship = &relationship_target.relationship; let struct_name = &ast.ident; @@ -739,18 +752,43 @@ fn derive_relationship_target( #[inline] fn collection(&self) -> &Self::Collection { - &self.0 + &self.#relationship_member } #[inline] fn collection_mut_risky(&mut self) -> &mut Self::Collection { - &mut self.0 + &mut self.#relationship_member } #[inline] fn from_collection_risky(collection: Self::Collection) -> Self { - Self(collection) + Self { + #(#members: Default::default(),),* + #relationship_member: collection + } } } })) } + +fn relationship_field(fields: &Fields, span: Span) -> Result> { + let field = match fields { + Fields::Named(fields) => fields.named.iter().find(|field| { + field + .attrs + .iter() + .any(|attr| attr.path().is_ident("relationship")) + }), + Fields::Unnamed(fields) => fields + .unnamed + .len() + .eq(&1) + .then(|| fields.unnamed.first()) + .flatten(), + Fields::Unit => return Err(syn::Error::new( + span, + "Relationship and RelationshipTarget can only be derived for named or unnamed structs, not unit structs.", + )), + }; + Ok(field) +} diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 540b049541bd9..975ac735c0045 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -35,12 +35,20 @@ use log::warn; /// /// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. /// +/// Relationship and RelationshipTarget can only be derived for structs with a single unnamed field +/// or for structs where one field is annotated with #[relationship]. +/// RelationshipTarget also requires that the relationship field is private to prevent users from directly mutating it, +/// which could invalidate the correctness of relationships. /// ``` /// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; /// #[derive(Component)] /// #[relationship(relationship_target = Children)] -/// pub struct ChildOf(pub Entity); +/// pub struct ChildOf { +/// #[relationship] +/// pub child: Entity, +/// internal: u8, +/// }; /// /// #[derive(Component)] /// #[relationship_target(relationship = ChildOf)] From 6ad6afd0e31683ae22b056a35fce46be92e4c148 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 15:11:40 +0100 Subject: [PATCH 02/16] docs and fmt --- crates/bevy_ecs/macros/src/component.rs | 1 + crates/bevy_ecs/src/relationship/mod.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 40bbc116463ab..ea34f95fba733 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -771,6 +771,7 @@ fn derive_relationship_target( })) } +/// returns the field that has the attribute #[relationship] or if the fields are unnamed the only field. fn relationship_field(fields: &Fields, span: Span) -> Result> { let field = match fields { Fields::Named(fields) => fields.named.iter().find(|field| { diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 975ac735c0045..9917f0863a145 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -37,7 +37,7 @@ use log::warn; /// /// Relationship and RelationshipTarget can only be derived for structs with a single unnamed field /// or for structs where one field is annotated with #[relationship]. -/// RelationshipTarget also requires that the relationship field is private to prevent users from directly mutating it, +/// RelationshipTarget also requires that the relationship field is private to prevent users from directly mutating it, /// which could invalidate the correctness of relationships. /// ``` /// # use bevy_ecs::component::Component; From 56224df21f7e7a0d599acf1e349f57b37724595b Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 15:26:33 +0100 Subject: [PATCH 03/16] named single field and better paths --- crates/bevy_ecs/macros/src/component.rs | 8 +++++--- crates/bevy_ecs/src/relationship/mod.rs | 10 ++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index ea34f95fba733..db89a58683a2b 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -692,7 +692,7 @@ fn derive_relationship( #[inline] fn from(entity: #bevy_ecs_path::entity::Entity) -> Self { Self { - #(#members: Default::default(),),* + #(#members: core::default::Default::default(),),* #relationship_member: entity } } @@ -763,7 +763,7 @@ fn derive_relationship_target( #[inline] fn from_collection_risky(collection: Self::Collection) -> Self { Self { - #(#members: Default::default(),),* + #(#members: core::default::Default::default(),),* #relationship_member: collection } } @@ -771,9 +771,11 @@ fn derive_relationship_target( })) } -/// returns the field that has the attribute #[relationship] or if the fields are unnamed the only field. +/// Returns the field with the `#[relationship]` attribute, the only field if unnamed, +/// or the only field in a [`Fields::Named`] with one field, otherwise None. fn relationship_field(fields: &Fields, span: Span) -> Result> { let field = match fields { + Fields::Named(fields) if fields.named.len() == 1 => fields.named.first(), Fields::Named(fields) => fields.named.iter().find(|field| { field .attrs diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 9917f0863a145..21e8e97834be2 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -35,10 +35,12 @@ use log::warn; /// /// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. /// -/// Relationship and RelationshipTarget can only be derived for structs with a single unnamed field -/// or for structs where one field is annotated with #[relationship]. -/// RelationshipTarget also requires that the relationship field is private to prevent users from directly mutating it, -/// which could invalidate the correctness of relationships. +/// Relationship and RelationshipTarget can only be derived for structs with a single unnamed field, single named field +/// or for named structs where one field is annotated with #[relationship]. +/// If there are additional fields, they must all implement [`Default`]. +/// +/// RelationshipTarget also requires that the relationship field is private to prevent direct mutation, +/// ensuring the correctness of relationships. /// ``` /// # use bevy_ecs::component::Component; /// # use bevy_ecs::entity::Entity; From b88b3d75e394463a1a4c9625d920bc560998f3bb Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 15:30:07 +0100 Subject: [PATCH 04/16] let else --- crates/bevy_ecs/macros/src/component.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index db89a58683a2b..48cba9696338c 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -722,13 +722,13 @@ fn derive_relationship_target( }; let field = relationship_field(fields, struct_token.span())?; - let field = match field { - Some(field) => field, - None => return Err(syn::Error::new( + let Some(field) = field else { + return Err(syn::Error::new( fields.span(), "RelationshipTarget can only be derived for structs with a single private unnamed field or for structs where one field is annotated with #[relationship] and is private.", - )), + )) }; + if field.vis != Visibility::Inherited { return Err(syn::Error::new(field.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); } From 114ce656bdf2e8e99e1593656a9b72c3e796fac3 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 15:36:34 +0100 Subject: [PATCH 05/16] ugh fmt --- crates/bevy_ecs/macros/src/component.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 48cba9696338c..eca9dd6982471 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -726,7 +726,7 @@ fn derive_relationship_target( return Err(syn::Error::new( fields.span(), "RelationshipTarget can only be derived for structs with a single private unnamed field or for structs where one field is annotated with #[relationship] and is private.", - )) + )); }; if field.vis != Visibility::Inherited { From 8dd5ecf6c529f6c3e7ecc61d0f2c4ec0c682c987 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 15:42:32 +0100 Subject: [PATCH 06/16] docs --- crates/bevy_ecs/src/relationship/mod.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index 21e8e97834be2..bc5283d36cdff 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -35,11 +35,13 @@ use log::warn; /// /// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. /// -/// Relationship and RelationshipTarget can only be derived for structs with a single unnamed field, single named field -/// or for named structs where one field is annotated with #[relationship]. +/// ## Derive +/// +/// [`Relationship`] and [`RelationshipTarget`] can only be derived for structs with a single unnamed field, single named field +/// or for named structs where one field is annotated with `#[relationship]`. /// If there are additional fields, they must all implement [`Default`]. /// -/// RelationshipTarget also requires that the relationship field is private to prevent direct mutation, +/// [`RelationshipTarget`] also requires that the relationship field is private to prevent direct mutation, /// ensuring the correctness of relationships. /// ``` /// # use bevy_ecs::component::Component; From 2fe51a0c935ff18ab3f602326d776d4a5d1aa629 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Mon, 17 Feb 2025 15:45:34 +0100 Subject: [PATCH 07/16] last fmt? --- crates/bevy_ecs/src/relationship/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index bc5283d36cdff..e88a4428fe65d 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -36,7 +36,7 @@ use log::warn; /// [`Relationship`] and [`RelationshipTarget`] should always be derived via the [`Component`] trait to ensure the hooks are set up properly. /// /// ## Derive -/// +/// /// [`Relationship`] and [`RelationshipTarget`] can only be derived for structs with a single unnamed field, single named field /// or for named structs where one field is annotated with `#[relationship]`. /// If there are additional fields, they must all implement [`Default`]. From 9ada21bd463b32feb3eefe53251400b45df6bd2b Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 19 Feb 2025 19:17:24 +0100 Subject: [PATCH 08/16] fix visit_entities macro --- crates/bevy_ecs/macros/src/component.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index eca9dd6982471..a278dab684664 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -260,6 +260,15 @@ fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> T Data::Struct(DataStruct { ref fields, .. }) => { let mut visited_fields = Vec::new(); let mut visited_indices = Vec::new(); + + if is_relationship { + if let Some(field) = relationship_field(fields, fields.span()).ok().flatten() { + match field.ident { + Some(ref ident) => visited_fields.push(ident.clone()), + None => visited_indices.push(Index::from(0)), + } + } + } match fields { Fields::Named(fields) => { for field in &fields.named { @@ -276,9 +285,7 @@ fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> T } Fields::Unnamed(fields) => { for (index, field) in fields.unnamed.iter().enumerate() { - if index == 0 && is_relationship { - visited_indices.push(Index::from(0)); - } else if field + if field .attrs .iter() .any(|a| a.meta.path().is_ident(ENTITIES_ATTR)) @@ -289,7 +296,6 @@ fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> T } Fields::Unit => {} } - if visited_fields.is_empty() && visited_indices.is_empty() { TokenStream2::new() } else { From 58ef916aa5258e8680addd3f624c60aad73145f3 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 19 Feb 2025 19:41:16 +0100 Subject: [PATCH 09/16] childof(entity) -> ChildOf { parent: Entity }} --- crates/bevy_ecs/src/entity/clone_entities.rs | 6 +-- crates/bevy_ecs/src/hierarchy.rs | 45 ++++++++++--------- .../src/relationship/related_methods.rs | 6 +-- crates/bevy_input_focus/src/tab_navigation.rs | 4 +- crates/bevy_render/src/view/visibility/mod.rs | 2 +- crates/bevy_text/src/text.rs | 4 +- crates/bevy_transform/src/helper.rs | 4 +- crates/bevy_transform/src/systems.rs | 4 +- .../src/experimental/ghost_hierarchy.rs | 2 +- crates/bevy_ui/src/update.rs | 2 +- examples/3d/visibility_range.rs | 2 +- examples/animation/gltf_skinned_mesh.rs | 2 +- examples/ui/ghost_nodes.rs | 2 +- 13 files changed, 45 insertions(+), 40 deletions(-) diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 389f30ff8d7b2..e387949e10bbb 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1263,9 +1263,9 @@ mod tests { fn recursive_clone() { 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(); + let child1 = world.spawn(ChildOf {parent: root}).id(); + let grandchild = world.spawn(ChildOf {parent: child1}).id(); + let child2 = world.spawn(ChildOf {parent: root}).id(); let clone_root = world.spawn_empty().id(); EntityCloner::build(&mut world) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 2c4736d19b4a4..43b9473be331b 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -52,9 +52,9 @@ use log::warn; /// # 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(); +/// let child1 = world.spawn(ChildOf {parent: root}).id(); +/// let child2 = world.spawn(ChildOf {parent: root}).id(); +/// let grandchild = world.spawn(ChildOf {parent: child1}).id(); /// /// assert_eq!(&**world.entity(root).get::().unwrap(), &[child1, child2]); /// assert_eq!(&**world.entity(child1).get::().unwrap(), &[grandchild]); @@ -94,12 +94,15 @@ use log::warn; )] #[relationship(relationship_target = Children)] #[doc(alias = "IsChild", alias = "Parent")] -pub struct ChildOf(pub Entity); +pub struct ChildOf { + /// The parent entity of this child entity. + pub parent: Entity, +} impl ChildOf { /// Returns the parent entity, which is the "target" of this relationship. pub fn get(&self) -> Entity { - self.0 + self.parent } } @@ -108,7 +111,7 @@ impl Deref for ChildOf { #[inline] fn deref(&self) -> &Self::Target { - &self.0 + &self.parent } } @@ -119,7 +122,9 @@ impl Deref for ChildOf { impl FromWorld for ChildOf { #[inline(always)] fn from_world(_world: &mut World) -> Self { - ChildOf(Entity::PLACEHOLDER) + ChildOf { + parent: Entity::PLACEHOLDER, + } } } @@ -198,7 +203,7 @@ impl<'w> EntityWorldMut<'w> { pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { let id = self.id(); self.world_scope(|world| { - world.spawn((bundle, ChildOf(id))); + world.spawn((bundle, ChildOf { parent: id })); }); self } @@ -213,7 +218,7 @@ impl<'w> EntityWorldMut<'w> { /// 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.insert(ChildOf { parent }); self } } @@ -245,7 +250,7 @@ impl<'a> EntityCommands<'a> { /// [`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.commands.spawn((bundle, ChildOf { parent: id })); self } @@ -259,7 +264,7 @@ impl<'a> EntityCommands<'a> { /// 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.insert(ChildOf { parent }); self } } @@ -375,9 +380,9 @@ mod tests { 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(); + let child1 = world.spawn(ChildOf {parent: root}).id(); + let grandchild = world.spawn(ChildOf {parent: root}).id(); + let child2 = world.spawn(ChildOf {parent: root}).id(); // Spawn let hierarchy = get_hierarchy(&world, root); @@ -398,7 +403,7 @@ mod tests { assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)])); // Insert - world.entity_mut(child1).insert(ChildOf(root)); + world.entity_mut(child1).insert(ChildOf {parent: root}); let hierarchy = get_hierarchy(&world, root); assert_eq!( hierarchy, @@ -457,7 +462,7 @@ mod tests { fn self_parenting_invalid() { let mut world = World::new(); let id = world.spawn_empty().id(); - world.entity_mut(id).insert(ChildOf(id)); + world.entity_mut(id).insert(ChildOf { parent: id }); assert!( world.entity(id).get::().is_none(), "invalid ChildOf relationships should self-remove" @@ -469,7 +474,7 @@ mod tests { let mut world = World::new(); let parent = world.spawn_empty().id(); world.entity_mut(parent).despawn(); - let id = world.spawn(ChildOf(parent)).id(); + let id = world.spawn(ChildOf {parent}).id(); assert!( world.entity(id).get::().is_none(), "invalid ChildOf relationships should self-remove" @@ -480,10 +485,10 @@ mod tests { 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)); + let id = world.spawn(ChildOf {parent}).id(); + world.entity_mut(id).insert(ChildOf {parent}); assert_eq!( - Some(&ChildOf(parent)), + Some(&ChildOf { parent }), world.entity(id).get::(), "ChildOf should still be there" ); diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 150bd02ebdbe6..11ade7182904b 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -259,9 +259,9 @@ mod tests { let mut world = World::new(); let a = world.spawn_empty().id(); - let b = world.spawn(ChildOf(a)).id(); - let c = world.spawn(ChildOf(a)).id(); - let d = world.spawn(ChildOf(b)).id(); + let b = world.spawn(ChildOf {parent: a}).id(); + let c = world.spawn(ChildOf { parent: a }).id(); + let d = world.spawn(ChildOf { parent: b }).id(); world .entity_mut(a) diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 683e5d12d5fe8..146f44462cf12 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -375,8 +375,8 @@ mod tests { let world = app.world_mut(); let tab_group_entity = world.spawn(TabGroup::new(0)).id(); - let tab_entity_1 = world.spawn((TabIndex(0), ChildOf(tab_group_entity))).id(); - let tab_entity_2 = world.spawn((TabIndex(1), ChildOf(tab_group_entity))).id(); + let tab_entity_1 = world.spawn((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 = SystemState::new(world); let tab_navigation = system_state.get(world); diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 09dc516b6ea9b..fcade4715cf45 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -786,7 +786,7 @@ mod test { .entity_mut(parent2) .insert(Visibility::Visible); // Simulate a change in the parent component - app.world_mut().entity_mut(child2).insert(ChildOf(parent2)); // example of changing parent + app.world_mut().entity_mut(child2).insert(ChildOf { parent: parent2 }); // example of changing parent // Run the system again to propagate changes app.update(); diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 7c88010de9118..558d79e5de51c 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -524,7 +524,7 @@ pub fn detect_text_needs_rerender( )); continue; }; - let mut parent: Entity = span_parent.0; + let mut parent: Entity = span_parent.get(); // 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 @@ -555,7 +555,7 @@ pub fn detect_text_needs_rerender( )); break; }; - parent = next_parent.0; + parent = next_parent.get(); } } } diff --git a/crates/bevy_transform/src/helper.rs b/crates/bevy_transform/src/helper.rs index 777ca81a9dbd5..7438095ad25f9 100644 --- a/crates/bevy_transform/src/helper.rs +++ b/crates/bevy_transform/src/helper.rs @@ -123,8 +123,8 @@ mod tests { for transform in transforms { let mut e = app.world_mut().spawn(transform); - if let Some(entity) = entity { - e.insert(ChildOf(entity)); + if let Some(parent) = entity { + e.insert(ChildOf { parent }); } entity = Some(e.id()); diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 697e8af3b73cf..176270292f197 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -206,8 +206,8 @@ mod test { let root = commands.spawn(offset_transform(3.3)).id(); let parent = commands.spawn(offset_transform(4.4)).id(); let child = commands.spawn(offset_transform(5.5)).id(); - commands.entity(parent).insert(ChildOf(root)); - commands.entity(child).insert(ChildOf(parent)); + commands.entity(parent).insert(ChildOf {parent: root }); + commands.entity(child).insert(ChildOf {parent }); command_queue.apply(&mut world); schedule.run(&mut world); diff --git a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs index 8e663964f7eb9..172884b3263ad 100644 --- a/crates/bevy_ui/src/experimental/ghost_hierarchy.rs +++ b/crates/bevy_ui/src/experimental/ghost_hierarchy.rs @@ -150,7 +150,7 @@ impl<'w, 's> UiChildren<'w, 's> { /// Returns the UI parent of the provided entity. pub fn get_parent(&'s self, entity: Entity) -> Option { - self.parents_query.get(entity).ok().map(|parent| parent.0) + self.parents_query.get(entity).ok().map(|p| p.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. diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 4c3b28a6923f9..e0fe1b29213df 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -177,7 +177,7 @@ pub fn update_ui_context_system( } for (entity, child_of) in reparented_nodes.iter() { - let Ok(computed_target) = computed_target_query.get(child_of.0) else { + let Ok(computed_target) = computed_target_query.get(child_of.parent) else { continue; }; diff --git a/examples/3d/visibility_range.rs b/examples/3d/visibility_range.rs index 013b39d8d1e2c..50c82f69f5583 100644 --- a/examples/3d/visibility_range.rs +++ b/examples/3d/visibility_range.rs @@ -187,7 +187,7 @@ fn set_visibility_ranges( break; } match child_of { - Some(child_of) => current = child_of.0, + Some(child_of) => current = child_of.parent, None => break, } } diff --git a/examples/animation/gltf_skinned_mesh.rs b/examples/animation/gltf_skinned_mesh.rs index 90e95d53f465f..48f7cd6f39c89 100644 --- a/examples/animation/gltf_skinned_mesh.rs +++ b/examples/animation/gltf_skinned_mesh.rs @@ -51,7 +51,7 @@ fn joint_animation( // Iter skinned mesh entity for child_of in &children { // Mesh node is the parent of the skinned mesh entity. - let mesh_node_entity = child_of.get(); + let mesh_node_entity = child_of.parent; // Get `Children` in the mesh node. let mesh_node_parent = parents.get(mesh_node_entity).unwrap(); diff --git a/examples/ui/ghost_nodes.rs b/examples/ui/ghost_nodes.rs index b516ad09640a6..839a0d5a3f614 100644 --- a/examples/ui/ghost_nodes.rs +++ b/examples/ui/ghost_nodes.rs @@ -117,7 +117,7 @@ fn button_system( // Update button labels to match their parent counter for (children, child_of) in &labels_query { - let counter = counter_query.get(child_of.get()).unwrap(); + let counter = counter_query.get(child_of.parent).unwrap(); let mut text = text_query.get_mut(children[0]).unwrap(); **text = counter.0.to_string(); From feb3fd219849e50754ba23cdb585cb5b831388c7 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 19 Feb 2025 19:46:16 +0100 Subject: [PATCH 10/16] fmt --- crates/bevy_ecs/src/entity/clone_entities.rs | 6 +++--- crates/bevy_ecs/src/hierarchy.rs | 14 +++++++------- .../src/relationship/related_methods.rs | 2 +- crates/bevy_input_focus/src/tab_navigation.rs | 18 ++++++++++++++++-- crates/bevy_render/src/view/visibility/mod.rs | 4 +++- crates/bevy_transform/src/systems.rs | 4 ++-- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index e387949e10bbb..8ee5b213cd054 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -1263,9 +1263,9 @@ mod tests { fn recursive_clone() { let mut world = World::new(); let root = world.spawn_empty().id(); - let child1 = world.spawn(ChildOf {parent: root}).id(); - let grandchild = world.spawn(ChildOf {parent: child1}).id(); - let child2 = world.spawn(ChildOf {parent: root}).id(); + let child1 = world.spawn(ChildOf { parent: root }).id(); + let grandchild = world.spawn(ChildOf { parent: child1 }).id(); + let child2 = world.spawn(ChildOf { parent: root }).id(); let clone_root = world.spawn_empty().id(); EntityCloner::build(&mut world) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 43b9473be331b..480c2408dc109 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -380,9 +380,9 @@ mod tests { fn hierarchy() { let mut world = World::new(); let root = world.spawn_empty().id(); - let child1 = world.spawn(ChildOf {parent: root}).id(); - let grandchild = world.spawn(ChildOf {parent: root}).id(); - let child2 = world.spawn(ChildOf {parent: root}).id(); + let child1 = world.spawn(ChildOf { parent: root }).id(); + let grandchild = world.spawn(ChildOf { parent: root }).id(); + let child2 = world.spawn(ChildOf { parent: root }).id(); // Spawn let hierarchy = get_hierarchy(&world, root); @@ -403,7 +403,7 @@ mod tests { assert_eq!(hierarchy, Node::new_with(root, vec![Node::new(child2)])); // Insert - world.entity_mut(child1).insert(ChildOf {parent: root}); + world.entity_mut(child1).insert(ChildOf { parent: root }); let hierarchy = get_hierarchy(&world, root); assert_eq!( hierarchy, @@ -474,7 +474,7 @@ mod tests { let mut world = World::new(); let parent = world.spawn_empty().id(); world.entity_mut(parent).despawn(); - let id = world.spawn(ChildOf {parent}).id(); + let id = world.spawn(ChildOf { parent }).id(); assert!( world.entity(id).get::().is_none(), "invalid ChildOf relationships should self-remove" @@ -485,8 +485,8 @@ mod tests { 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}); + let id = world.spawn(ChildOf { parent }).id(); + world.entity_mut(id).insert(ChildOf { parent }); assert_eq!( Some(&ChildOf { parent }), world.entity(id).get::(), diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index 11ade7182904b..ab23c700bc444 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -259,7 +259,7 @@ mod tests { let mut world = World::new(); let a = world.spawn_empty().id(); - let b = world.spawn(ChildOf {parent: a}).id(); + let b = world.spawn(ChildOf { parent: a }).id(); let c = world.spawn(ChildOf { parent: a }).id(); let d = world.spawn(ChildOf { parent: b }).id(); diff --git a/crates/bevy_input_focus/src/tab_navigation.rs b/crates/bevy_input_focus/src/tab_navigation.rs index 146f44462cf12..d8ce83c8d7817 100644 --- a/crates/bevy_input_focus/src/tab_navigation.rs +++ b/crates/bevy_input_focus/src/tab_navigation.rs @@ -375,8 +375,22 @@ mod tests { let world = app.world_mut(); let tab_group_entity = world.spawn(TabGroup::new(0)).id(); - let tab_entity_1 = world.spawn((TabIndex(0), ChildOf {parent: tab_group_entity})).id(); - let tab_entity_2 = world.spawn((TabIndex(1), ChildOf {parent: tab_group_entity})).id(); + let tab_entity_1 = world + .spawn(( + 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 = SystemState::new(world); let tab_navigation = system_state.get(world); diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index fcade4715cf45..da3054f520e29 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -786,7 +786,9 @@ mod test { .entity_mut(parent2) .insert(Visibility::Visible); // Simulate a change in the parent component - app.world_mut().entity_mut(child2).insert(ChildOf { parent: parent2 }); // example of changing parent + app.world_mut() + .entity_mut(child2) + .insert(ChildOf { parent: parent2 }); // example of changing parent // Run the system again to propagate changes app.update(); diff --git a/crates/bevy_transform/src/systems.rs b/crates/bevy_transform/src/systems.rs index 176270292f197..f48b958fe7f50 100644 --- a/crates/bevy_transform/src/systems.rs +++ b/crates/bevy_transform/src/systems.rs @@ -206,8 +206,8 @@ mod test { let root = commands.spawn(offset_transform(3.3)).id(); let parent = commands.spawn(offset_transform(4.4)).id(); let child = commands.spawn(offset_transform(5.5)).id(); - commands.entity(parent).insert(ChildOf {parent: root }); - commands.entity(child).insert(ChildOf {parent }); + commands.entity(parent).insert(ChildOf { parent: root }); + commands.entity(child).insert(ChildOf { parent }); command_queue.apply(&mut world); schedule.run(&mut world); From 3e2b5a0053b700a08db75066087fd4f4fbc663a9 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 19 Feb 2025 19:51:10 +0100 Subject: [PATCH 11/16] missed one --- benches/benches/bevy_ecs/entity_cloning.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benches/benches/bevy_ecs/entity_cloning.rs b/benches/benches/bevy_ecs/entity_cloning.rs index 7c474cc4f8aaf..12343dd2ee248 100644 --- a/benches/benches/bevy_ecs/entity_cloning.rs +++ b/benches/benches/bevy_ecs/entity_cloning.rs @@ -153,9 +153,9 @@ fn bench_clone_hierarchy( hierarchy_level.clear(); - for parent_id in current_hierarchy_level { + for parent in current_hierarchy_level { for _ in 0..children { - let child_id = world.spawn((B::default(), ChildOf(parent_id))).id(); + let child_id = world.spawn((B::default(), ChildOf { parent })).id(); hierarchy_level.push(child_id); } } From 44d3dfae4533722c414a7a91d38074acad639193 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 19 Feb 2025 19:56:49 +0100 Subject: [PATCH 12/16] another one --- examples/3d/mixed_lighting.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/3d/mixed_lighting.rs b/examples/3d/mixed_lighting.rs index d827549824723..2a9a2f6ad9622 100644 --- a/examples/3d/mixed_lighting.rs +++ b/examples/3d/mixed_lighting.rs @@ -460,7 +460,7 @@ fn move_sphere( }; // Grab its transform. - let Ok(mut transform) = transforms.get_mut(child_of.0) else { + let Ok(mut transform) = transforms.get_mut(child_of.parent) else { return; }; From af71b22d26d792458e2eaef9c5146a63fd4ac2d2 Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 19 Feb 2025 20:06:23 +0100 Subject: [PATCH 13/16] whoops failing test --- crates/bevy_ecs/src/hierarchy.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 480c2408dc109..04c77fde34eda 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -381,7 +381,7 @@ mod tests { let mut world = World::new(); let root = world.spawn_empty().id(); let child1 = world.spawn(ChildOf { parent: root }).id(); - let grandchild = world.spawn(ChildOf { parent: root }).id(); + let grandchild = world.spawn(ChildOf { parent: child1 }).id(); let child2 = world.spawn(ChildOf { parent: root }).id(); // Spawn From f74d25a1c25014cac9eecd7e036e2607382fd4dc Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Thu, 20 Feb 2025 12:48:57 +0100 Subject: [PATCH 14/16] better relationship_field --- crates/bevy_ecs/macros/src/component.rs | 63 +++++++++++++------------ 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index a278dab684664..abc1c5fda91e8 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -262,11 +262,14 @@ fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> T let mut visited_indices = Vec::new(); if is_relationship { - if let Some(field) = relationship_field(fields, fields.span()).ok().flatten() { - match field.ident { - Some(ref ident) => visited_fields.push(ident.clone()), - None => visited_indices.push(Index::from(0)), - } + let field = match relationship_field(fields, "VisitEntities", fields.span()) { + Ok(f) => f, + Err(e) => return e.to_compile_error(), + }; + + match field.ident { + Some(ref ident) => visited_fields.push(ident.clone()), + None => visited_indices.push(Index::from(0)), } } match fields { @@ -668,15 +671,10 @@ fn derive_relationship( "Relationship can only be derived for structs.", )); }; - let field = relationship_field(fields, struct_token.span())?; + let field = relationship_field(fields, "Relationship", struct_token.span())?; + + let relationship_member: Member = field.ident.clone().map_or(Member::from(0), Member::Named); - let relationship_member: Member = match field { - Some(field) => field.ident.clone().map_or(Member::from(0), Member::Named), - None => return Err(syn::Error::new( - fields.span(), - "Relationship can only be derived for structs with a single unnamed field or for structs where one field is annotated with #[relationship].", - )), - }; let members = fields .members() .filter(|member| member != &relationship_member); @@ -726,14 +724,7 @@ fn derive_relationship_target( "RelationshipTarget can only be derived for structs.", )); }; - let field = relationship_field(fields, struct_token.span())?; - - let Some(field) = field else { - return Err(syn::Error::new( - fields.span(), - "RelationshipTarget can only be derived for structs with a single private unnamed field or for structs where one field is annotated with #[relationship] and is private.", - )); - }; + let field = relationship_field(fields, "RelationshipTarget", struct_token.span())?; if field.vis != Visibility::Inherited { return Err(syn::Error::new(field.span(), "The collection in RelationshipTarget must be private to prevent users from directly mutating it, which could invalidate the correctness of relationships.")); @@ -778,26 +769,36 @@ fn derive_relationship_target( } /// Returns the field with the `#[relationship]` attribute, the only field if unnamed, -/// or the only field in a [`Fields::Named`] with one field, otherwise None. -fn relationship_field(fields: &Fields, span: Span) -> Result> { - let field = match fields { - Fields::Named(fields) if fields.named.len() == 1 => fields.named.first(), +/// or the only field in a [`Fields::Named`] with one field, otherwise `Err`. +fn relationship_field<'a>( + fields: &'a Fields, + derive: &'static str, + span: Span, +) -> Result<&'a Field> { + match fields { + Fields::Named(fields) if fields.named.len() == 1 => Ok(fields.named.first().unwrap()), Fields::Named(fields) => fields.named.iter().find(|field| { field .attrs .iter() .any(|attr| attr.path().is_ident("relationship")) - }), + }).ok_or(syn::Error::new( + span, + format!("{derive} derive expected named structs with a single field or with a field annotated with #[relationship].") + )), Fields::Unnamed(fields) => fields .unnamed .len() .eq(&1) .then(|| fields.unnamed.first()) - .flatten(), - Fields::Unit => return Err(syn::Error::new( + .flatten() + .ok_or(syn::Error::new( + span, + format!("{derive} derive expected unnamed structs with one field."), + )), + Fields::Unit => Err(syn::Error::new( span, - "Relationship and RelationshipTarget can only be derived for named or unnamed structs, not unit structs.", + format!("{derive} derive expected named or unnamed struct, found unit struct."), )), - }; - Ok(field) + } } From 0a27eccadc6930625c054816ed7410189f2487bc Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 26 Feb 2025 01:42:28 +0100 Subject: [PATCH 15/16] minor tweak --- crates/bevy_ecs/src/hierarchy.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 04c77fde34eda..152b663617f6d 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -201,9 +201,9 @@ impl<'w> EntityWorldMut<'w> { /// /// [`with_children`]: EntityWorldMut::with_children pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { - let id = self.id(); + let parent = self.id(); self.world_scope(|world| { - world.spawn((bundle, ChildOf { parent: id })); + world.spawn((bundle, ChildOf { parent })); }); self } From 5de70f85a814d03f8f1c924759896175f3ad672e Mon Sep 17 00:00:00 2001 From: Tim Overbeek Date: Wed, 26 Feb 2025 19:00:20 +0100 Subject: [PATCH 16/16] feedback --- crates/bevy_ecs/src/hierarchy.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 152b663617f6d..914ae09f02292 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -249,8 +249,8 @@ impl<'a> EntityCommands<'a> { /// /// [`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 { parent: id })); + let parent = self.id(); + self.commands.spawn((bundle, ChildOf { parent })); self }