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!`](https://github.com/bevyengine/bevy/discussions/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.
This commit is contained in:
parent
4c2cef2223
commit
5b29402cc8
@ -341,6 +341,9 @@ pub trait BuildChildren {
|
||||
/// Takes a closure which builds children for this entity using [`ChildBuild`].
|
||||
fn with_children(&mut self, f: impl FnOnce(&mut Self::Builder<'_>)) -> &mut Self;
|
||||
|
||||
/// Spawns the passed bundle and adds it to this entity as a child.
|
||||
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self;
|
||||
|
||||
/// Pushes children to the back of the builder's children. For any entities that are
|
||||
/// already a child of this one, this method does nothing.
|
||||
///
|
||||
@ -432,6 +435,13 @@ impl BuildChildren for EntityCommands<'_> {
|
||||
self
|
||||
}
|
||||
|
||||
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
|
||||
let parent = self.id();
|
||||
let child = self.commands().spawn(bundle).id();
|
||||
self.commands().add(PushChild { parent, child });
|
||||
self
|
||||
}
|
||||
|
||||
fn push_children(&mut self, children: &[Entity]) -> &mut Self {
|
||||
let parent = self.id();
|
||||
if children.contains(&parent) {
|
||||
@ -566,6 +576,17 @@ impl BuildChildren for EntityWorldMut<'_> {
|
||||
self
|
||||
}
|
||||
|
||||
fn with_child<B: Bundle>(&mut self, bundle: B) -> &mut Self {
|
||||
let child = self.world_scope(|world| world.spawn(bundle).id());
|
||||
if let Some(mut children_component) = self.get_mut::<Children>() {
|
||||
children_component.0.retain(|value| child != *value);
|
||||
children_component.0.push(child);
|
||||
} else {
|
||||
self.insert(Children::from_entities(&[child]));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
fn add_child(&mut self, child: Entity) -> &mut Self {
|
||||
let parent = self.id();
|
||||
if child == parent {
|
||||
@ -692,6 +713,14 @@ mod tests {
|
||||
assert_eq!(world.get::<Children>(parent).map(|c| &**c), children);
|
||||
}
|
||||
|
||||
/// Assert the number of children in the parent's [`Children`] component if it exists.
|
||||
fn assert_num_children(world: &World, parent: Entity, num_children: usize) {
|
||||
assert_eq!(
|
||||
world.get::<Children>(parent).map(|c| c.len()).unwrap_or(0),
|
||||
num_children
|
||||
);
|
||||
}
|
||||
|
||||
/// Used to omit a number of events that are not relevant to a particular test.
|
||||
fn omit_events(world: &mut World, number: usize) {
|
||||
let mut events_resource = world.resource_mut::<Events<HierarchyEvent>>();
|
||||
@ -859,6 +888,19 @@ mod tests {
|
||||
assert_eq!(*world.get::<Parent>(children[1]).unwrap(), Parent(parent));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_child() {
|
||||
let mut world = World::default();
|
||||
let mut queue = CommandQueue::default();
|
||||
let mut commands = Commands::new(&mut queue, &world);
|
||||
|
||||
let parent = commands.spawn(C(1)).id();
|
||||
commands.entity(parent).with_child(C(2));
|
||||
|
||||
queue.apply(&mut world);
|
||||
assert_eq!(world.get::<Children>(parent).unwrap().0.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn push_and_insert_and_remove_children_commands() {
|
||||
let mut world = World::default();
|
||||
@ -1228,4 +1270,23 @@ mod tests {
|
||||
let children = query.get(&world, parent);
|
||||
assert!(children.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_child() {
|
||||
let world = &mut World::new();
|
||||
world.insert_resource(Events::<HierarchyEvent>::default());
|
||||
|
||||
let a = world.spawn_empty().id();
|
||||
let b = ();
|
||||
let c = ();
|
||||
let d = ();
|
||||
|
||||
world.entity_mut(a).with_child(b);
|
||||
|
||||
assert_num_children(world, a, 1);
|
||||
|
||||
world.entity_mut(a).with_child(c).with_child(d);
|
||||
|
||||
assert_num_children(world, a, 3);
|
||||
}
|
||||
}
|
||||
|
||||
@ -83,8 +83,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
background_color: NORMAL_BUTTON.into(),
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
.with_child(TextBundle::from_section(
|
||||
"Button",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraSans-Bold.ttf"),
|
||||
@ -93,5 +92,4 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
},
|
||||
));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user