diff --git a/benches/Cargo.toml b/benches/Cargo.toml index 28fc57e0fe6eb..f625796250092 100644 --- a/benches/Cargo.toml +++ b/benches/Cargo.toml @@ -83,6 +83,11 @@ name = "picking" path = "benches/bevy_picking/main.rs" harness = false +[[bench]] +name = "hierarchy" +path = "benches/bevy_hierarchy/main.rs" +harness = false + [[bench]] name = "reflect" path = "benches/bevy_reflect/main.rs" diff --git a/benches/benches/bevy_hierarchy/main.rs b/benches/benches/bevy_hierarchy/main.rs new file mode 100644 index 0000000000000..82e59792ba9dd --- /dev/null +++ b/benches/benches/bevy_hierarchy/main.rs @@ -0,0 +1,5 @@ +use criterion::criterion_main; + +mod spawn; + +criterion_main!(spawn::benches); diff --git a/benches/benches/bevy_hierarchy/spawn.rs b/benches/benches/bevy_hierarchy/spawn.rs new file mode 100644 index 0000000000000..73093a9445477 --- /dev/null +++ b/benches/benches/bevy_hierarchy/spawn.rs @@ -0,0 +1,41 @@ +use bevy_ecs::prelude::*; +use bevy_ecs::world::CommandQueue; +use bevy_hierarchy::prelude::*; +use core::hint::black_box; +use criterion::{criterion_group, Criterion}; + +criterion_group!(benches, spawn_children); + +fn spawn_children(c: &mut Criterion) { + let mut group = c.benchmark_group("spawn_children"); + group.warm_up_time(core::time::Duration::from_millis(500)); + group.measurement_time(core::time::Duration::from_secs(4)); + + for entity_count in (1..7).map(|i| 10i32.pow(i)) { + group.bench_function(format!("{}_entities", entity_count), |bencher| { + let mut world = World::default(); + let mut command_queue = CommandQueue::default(); + + bencher.iter(|| { + let mut commands = Commands::new(&mut command_queue, &world); + let mut entity = commands.spawn_empty(); + + entity.with_children(|c| { + for _ in 0..entity_count { + c.spawn_empty(); + } + }); + + entity.with_child(()); + entity.with_children(|c| { + for _ in 0..entity_count { + c.spawn_empty(); + } + }); + command_queue.apply(black_box(&mut world)); + }); + + black_box(world); + }); + } +} diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index 811c4f4eaa8e3..3f0abf4df982b 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -173,6 +173,7 @@ fn remove_children(parent: Entity, children: &[Entity], world: &mut World) { /// ``` pub struct ChildBuilder<'a> { commands: Commands<'a, 'a>, + /// These children are guaranteed to be spawned entities, and do not need re-parenting checks. children: SmallVec<[Entity; 8]>, parent: Entity, } @@ -213,13 +214,15 @@ impl ChildBuild for ChildBuilder<'_> { Self: 'a; fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands { - let e = self.commands.spawn(bundle); + // Spawn the entity with the `Parent` component to avoid archetype changes. + let e = self.commands.spawn((bundle, Parent(self.parent))); self.children.push(e.id()); e } fn spawn_empty(&mut self) -> EntityCommands { - let e = self.commands.spawn_empty(); + // Spawn the entity with the `Parent` component to avoid archetype changes. + let e = self.commands.spawn(Parent(self.parent)); self.children.push(e.id()); e } @@ -346,20 +349,37 @@ impl BuildChildren for EntityCommands<'_> { }; spawn_children(&mut builder); - let children = builder.children; - if children.contains(&parent) { - panic!("Entity cannot be a child of itself."); - } + + // Send hierarchy events + let events = children + .iter() + .map(|&child| HierarchyEvent::ChildAdded { child, parent }) + .collect::>(); + self.commands_mut().queue(move |world: &mut World| { + push_events(world, events); + }); + self.queue(move |mut entity: EntityWorldMut| { - entity.add_children(&children); - }) + // `ChildBuilder` is responsible for setting the `Parent` component on all the children + // as they are spawned to avoid archetype changes. All that is left for us to do is push + // these children onto the parent entity's `Children` component. Because these are + // freshly spawned entities, we don't need to check for re-parenting, duplicates, or if + // they already exist in the `Parent` component. This avoids a lot of work! + if let Some(mut children_component) = entity.get_mut::() { + children_component.0.extend(children); + } else { + entity.insert(Children(children)); + } + }); + self } fn with_child(&mut self, bundle: B) -> &mut Self { - let child = self.commands().spawn(bundle).id(); self.queue(move |mut entity: EntityWorldMut| { - entity.add_child(child); + // Performance: note that `with_child` is faster than `add_child`, because it knows the + // entity is freshly spawned, and does not need to do any re-parenting checks. + entity.with_child(bundle); }) } @@ -499,13 +519,13 @@ impl BuildChildren for EntityWorldMut<'_> { fn with_child(&mut self, bundle: B) -> &mut Self { let parent = self.id(); - let child = self.world_scope(|world| world.spawn((bundle, Parent(parent))).id()); - if let Some(mut children_component) = self.get_mut::() { - children_component.0.retain(|value| child != *value); - children_component.0.push(child); - } else { - self.insert(Children::from_entities(&[child])); - } + self.world_scope(|world| { + let child = world.spawn((bundle, Parent(parent))).id(); + // Because we know this entity was just spawned, we don't need to check the child list + // to see if it is already present, it is impossible. + add_child_unchecked(world, parent, child); + push_events(world, [HierarchyEvent::ChildAdded { child, parent }]); + }); self }