Skip to content

Commit 5b29402

Browse files
authored
Add with_child to simplify spawning when there will only be one child (#14594)
# Objective This idea came up in the context of a hypothetical "text sections as entities" where text sections are children of a text bundle. ```rust commands .spawn(TextBundle::default()) .with_children(|parent} { parent.spawn(TextSection::from("Hello")); }); ``` This is a bit cumbersome (but powerful and probably the way things are headed). [`bsn!`](#14437) will eventually make this nicer, but in the mean time, this might improve ergonomics for the common case where there is only one `TextSection`. ## Solution Add a `with_child` method to the `BuildChildren` trait that spawns a single bundle and adds it as a child to the entity. ```rust commands .spawn(TextBundle::default()) .with_child(TextSection::from("Hello")); ``` ## Testing I added some tests, and modified the `button` example to use the new method. If any potential co-authors want to improve the tests, that would be great. ## Alternatives - Some sort of macro. See https://github.com/tigregalis/bevy_spans_ent/blob/main/examples/macro.rs#L20. I don't love this, personally, and it would probably be obsoleted by `bsn!`. - Wait for `bsn!` - Add `with_children_batch` that takes an `Into<Iterator>` of bundles. ```rust with_children_batch(vec![TextSection::from("Hello")]) ``` This is maybe not as useful as it sounds -- it only works with homogeneous bundles, so no marker components or styles. - If this doesn't seem valuable, doing nothing is cool with me.
1 parent 4c2cef2 commit 5b29402

File tree

2 files changed

+69
-10
lines changed

2 files changed

+69
-10
lines changed

crates/bevy_hierarchy/src/child_builder.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,9 @@ pub trait BuildChildren {
341341
/// Takes a closure which builds children for this entity using [`ChildBuild`].
342342
fn with_children(&mut self, f: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self;
343343

344+
/// Spawns the passed bundle and adds it to this entity as a child.
345+
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self;
346+
344347
/// Pushes children to the back of the builder's children. For any entities that are
345348
/// already a child of this one, this method does nothing.
346349
///
@@ -432,6 +435,13 @@ impl BuildChildren for EntityCommands<'_> {
432435
self
433436
}
434437

438+
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
439+
let parent = self.id();
440+
let child = self.commands().spawn(bundle).id();
441+
self.commands().add(PushChild { parent, child });
442+
self
443+
}
444+
435445
fn push_children(&mut self, children: &[Entity]) -> &mut Self {
436446
let parent = self.id();
437447
if children.contains(&parent) {
@@ -566,6 +576,17 @@ impl BuildChildren for EntityWorldMut<'_> {
566576
self
567577
}
568578

579+
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
580+
let child = self.world_scope(|world| world.spawn(bundle).id());
581+
if let Some(mut children_component) = self.get_mut::<Children>() {
582+
children_component.0.retain(|value| child != *value);
583+
children_component.0.push(child);
584+
} else {
585+
self.insert(Children::from_entities(&[child]));
586+
}
587+
self
588+
}
589+
569590
fn add_child(&mut self, child: Entity) -> &mut Self {
570591
let parent = self.id();
571592
if child == parent {
@@ -692,6 +713,14 @@ mod tests {
692713
assert_eq!(world.get::<Children>(parent).map(|c| &**c), children);
693714
}
694715

716+
/// Assert the number of children in the parent's [`Children`] component if it exists.
717+
fn assert_num_children(world: &World, parent: Entity, num_children: usize) {
718+
assert_eq!(
719+
world.get::<Children>(parent).map(|c| c.len()).unwrap_or(0),
720+
num_children
721+
);
722+
}
723+
695724
/// Used to omit a number of events that are not relevant to a particular test.
696725
fn omit_events(world: &mut World, number: usize) {
697726
let mut events_resource = world.resource_mut::<Events<HierarchyEvent>>();
@@ -859,6 +888,19 @@ mod tests {
859888
assert_eq!(*world.get::<Parent>(children[1]).unwrap(), Parent(parent));
860889
}
861890

891+
#[test]
892+
fn build_child() {
893+
let mut world = World::default();
894+
let mut queue = CommandQueue::default();
895+
let mut commands = Commands::new(&mut queue, &world);
896+
897+
let parent = commands.spawn(C(1)).id();
898+
commands.entity(parent).with_child(C(2));
899+
900+
queue.apply(&mut world);
901+
assert_eq!(world.get::<Children>(parent).unwrap().0.len(), 1);
902+
}
903+
862904
#[test]
863905
fn push_and_insert_and_remove_children_commands() {
864906
let mut world = World::default();
@@ -1228,4 +1270,23 @@ mod tests {
12281270
let children = query.get(&world, parent);
12291271
assert!(children.is_err());
12301272
}
1273+
1274+
#[test]
1275+
fn with_child() {
1276+
let world = &mut World::new();
1277+
world.insert_resource(Events::<HierarchyEvent>::default());
1278+
1279+
let a = world.spawn_empty().id();
1280+
let b = ();
1281+
let c = ();
1282+
let d = ();
1283+
1284+
world.entity_mut(a).with_child(b);
1285+
1286+
assert_num_children(world, a, 1);
1287+
1288+
world.entity_mut(a).with_child(c).with_child(d);
1289+
1290+
assert_num_children(world, a, 3);
1291+
}
12311292
}

examples/ui/button.rs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
8383
background_color: NORMAL_BUTTON.into(),
8484
..default()
8585
})
86-
.with_children(|parent| {
87-
parent.spawn(TextBundle::from_section(
88-
"Button",
89-
TextStyle {
90-
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
91-
font_size: 40.0,
92-
color: Color::srgb(0.9, 0.9, 0.9),
93-
},
94-
));
95-
});
86+
.with_child(TextBundle::from_section(
87+
"Button",
88+
TextStyle {
89+
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
90+
font_size: 40.0,
91+
color: Color::srgb(0.9, 0.9, 0.9),
92+
},
93+
));
9694
});
9795
}

0 commit comments

Comments
 (0)