Skip to content

Commit 01aedc8

Browse files
committed
Spawn now takes a Bundle (bevyengine#6054)
# Objective Now that we can consolidate Bundles and Components under a single insert (thanks to bevyengine#2975 and bevyengine#6039), almost 100% of world spawns now look like `world.spawn().insert((Some, Tuple, Here))`. Spawning an entity without any components is an extremely uncommon pattern, so it makes sense to give spawn the "first class" ergonomic api. This consolidated api should be made consistent across all spawn apis (such as World and Commands). ## Solution All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input: ```rust // before: commands .spawn() .insert((A, B, C)); world .spawn() .insert((A, B, C); // after commands.spawn((A, B, C)); world.spawn((A, B, C)); ``` All existing instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api. A new `spawn_empty` has been added, replacing the old `spawn` api. By allowing `world.spawn(some_bundle)` to replace `world.spawn().insert(some_bundle)`, this opened the door to removing the initial entity allocation in the "empty" archetype / table done in `spawn()` (and subsequent move to the actual archetype in `.insert(some_bundle)`). This improves spawn performance by over 10%: ![image](https://user-images.githubusercontent.com/2694663/191627587-4ab2f949-4ccd-4231-80eb-80dd4d9ad6b9.png) To take this measurement, I added a new `world_spawn` benchmark. Unfortunately, optimizing `Commands::spawn` is slightly less trivial, as Commands expose the Entity id of spawned entities prior to actually spawning. Doing the optimization would (naively) require assurances that the `spawn(some_bundle)` command is applied before all other commands involving the entity (which would not necessarily be true, if memory serves). Optimizing `Commands::spawn` this way does feel possible, but it will require careful thought (and maybe some additional checks), which deserves its own PR. For now, it has the same performance characteristics of the current `Commands::spawn_bundle` on main. **Note that 99% of this PR is simple renames and refactors. The only code that needs careful scrutiny is the new `World::spawn()` impl, which is relatively straightforward, but it has some new unsafe code (which re-uses battle tested BundlerSpawner code path).** --- ## Changelog - All `spawn` apis (`World::spawn`, `Commands:;spawn`, `ChildBuilder::spawn`, and `WorldChildBuilder::spawn`) now accept a bundle as input - All instances of `spawn_bundle` have been deprecated in favor of the new `spawn` api - World and Commands now have `spawn_empty()`, which is equivalent to the old `spawn()` behavior. ## Migration Guide ```rust // Old (0.8): commands .spawn() .insert_bundle((A, B, C)); // New (0.9) commands.spawn((A, B, C)); // Old (0.8): commands.spawn_bundle((A, B, C)); // New (0.9) commands.spawn((A, B, C)); // Old (0.8): let entity = commands.spawn().id(); // New (0.9) let entity = commands.spawn_empty().id(); // Old (0.8) let entity = world.spawn().id(); // New (0.9) let entity = world.spawn_empty(); ```
1 parent fb74ca3 commit 01aedc8

File tree

164 files changed

+1500
-1378
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

164 files changed

+1500
-1378
lines changed

benches/benches/bevy_ecs/components/add_remove_big_sparse_set.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ impl Benchmark {
2626
for _ in 0..10_000 {
2727
entities.push(
2828
world
29-
.spawn()
30-
.insert((
29+
.spawn((
3130
A(Mat4::from_scale(Vec3::ONE)),
3231
B(Mat4::from_scale(Vec3::ONE)),
3332
C(Mat4::from_scale(Vec3::ONE)),

benches/benches/bevy_ecs/components/add_remove_big_table.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ impl Benchmark {
2525
for _ in 0..10_000 {
2626
entities.push(
2727
world
28-
.spawn()
29-
.insert((
28+
.spawn((
3029
A(Mat4::from_scale(Vec3::ONE)),
3130
B(Mat4::from_scale(Vec3::ONE)),
3231
C(Mat4::from_scale(Vec3::ONE)),

benches/benches/bevy_ecs/components/add_remove_sparse_set.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl Benchmark {
1313
let mut world = World::default();
1414
let mut entities = Vec::with_capacity(10_000);
1515
for _ in 0..10_000 {
16-
entities.push(world.spawn().insert(A(0.0)).id());
16+
entities.push(world.spawn(A(0.0)).id());
1717
}
1818

1919
Self(world, entities)

benches/benches/bevy_ecs/components/add_remove_table.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ impl Benchmark {
1212
let mut world = World::default();
1313
let mut entities = Vec::with_capacity(10_000);
1414
for _ in 0..10_000 {
15-
entities.push(world.spawn().insert(A(0.0)).id());
15+
entities.push(world.spawn(A(0.0)).id());
1616
}
1717

1818
Self(world, entities)

benches/benches/bevy_ecs/components/archetype_updates.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ fn setup(system_count: usize) -> (World, SystemStage) {
2222
/// create `count` entities with distinct archetypes
2323
fn add_archetypes(world: &mut World, count: u16) {
2424
for i in 0..count {
25-
let mut e = world.spawn();
25+
let mut e = world.spawn_empty();
2626
if i & 1 << 0 != 0 {
2727
e.insert(A::<0>(1.0));
2828
}

benches/benches/bevy_ecs/components/insert_simple_unbatched.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl Benchmark {
2323
pub fn run(&mut self) {
2424
let mut world = World::new();
2525
for _ in 0..10_000 {
26-
world.spawn().insert((
26+
world.spawn((
2727
Transform(Mat4::from_scale(Vec3::ONE)),
2828
Position(Vec3::X),
2929
Rotation(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_frag.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..20 {
9-
$world.spawn().insert(($variants(0.0), Data(1.0)));
9+
$world.spawn(($variants(0.0), Data(1.0)));
1010
}
1111
)*
1212
};

benches/benches/bevy_ecs/iteration/iter_frag_foreach.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..20 {
9-
$world.spawn().insert(($variants(0.0), Data(1.0)));
9+
$world.spawn(($variants(0.0), Data(1.0)));
1010
}
1111
)*
1212
};

benches/benches/bevy_ecs/iteration/iter_frag_foreach_sparse.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..5 {
9-
$world.spawn().insert($variants(0.0));
9+
$world.spawn($variants(0.0));
1010
}
1111
)*
1212
};
@@ -21,7 +21,7 @@ impl<'w> Benchmark<'w> {
2121
pub fn new() -> Self {
2222
let mut world = World::new();
2323
for _ in 0..5 {
24-
world.spawn().insert(Data(1.0));
24+
world.spawn(Data(1.0));
2525
}
2626

2727
create_entities!(world; C00, C01, C02, C03, C04, C05, C06, C07, C08, C09);

benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..20 {
9-
$world.spawn().insert((
9+
$world.spawn((
1010
$variants(0.0),
1111
Data::<0>(1.0),
1212
Data::<1>(1.0),

benches/benches/bevy_ecs/iteration/iter_frag_foreach_wide_sparse.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..5 {
9-
$world.spawn().insert($variants(0.0));
9+
$world.spawn($variants(0.0));
1010
}
1111
)*
1212
};
@@ -36,7 +36,7 @@ impl<'w> Benchmark<'w> {
3636
pub fn new() -> Self {
3737
let mut world = World::new();
3838
for _ in 0..5 {
39-
world.spawn().insert((
39+
world.spawn((
4040
Data::<0>(1.0),
4141
Data::<1>(1.0),
4242
Data::<2>(1.0),

benches/benches/bevy_ecs/iteration/iter_frag_sparse.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..5 {
9-
$world.spawn().insert($variants(0.0));
9+
$world.spawn($variants(0.0));
1010
}
1111
)*
1212
};
@@ -21,7 +21,7 @@ impl<'w> Benchmark<'w> {
2121
let mut world = World::new();
2222

2323
for _ in 0..5 {
24-
world.spawn().insert(Data(1.0));
24+
world.spawn(Data(1.0));
2525
}
2626

2727
create_entities!(world; C00, C01, C02, C03, C04, C05, C06, C07, C08, C09);

benches/benches/bevy_ecs/iteration/iter_frag_wide.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..20 {
9-
$world.spawn().insert((
9+
$world.spawn((
1010
$variants(0.0),
1111
Data::<0>(1.0),
1212
Data::<1>(1.0),

benches/benches/bevy_ecs/iteration/iter_frag_wide_sparse.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ macro_rules! create_entities {
66
#[derive(Component)]
77
struct $variants(f32);
88
for _ in 0..5 {
9-
$world.spawn().insert($variants(0.0));
9+
$world.spawn($variants(0.0));
1010
}
1111
)*
1212
};
@@ -36,7 +36,7 @@ impl<'w> Benchmark<'w> {
3636
let mut world = World::new();
3737

3838
for _ in 0..5 {
39-
world.spawn().insert((
39+
world.spawn((
4040
Data::<0>(1.0),
4141
Data::<1>(1.0),
4242
Data::<2>(1.0),

benches/benches/bevy_ecs/iteration/iter_simple.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<'w> Benchmark<'w> {
2121

2222
// TODO: batch this
2323
for _ in 0..10_000 {
24-
world.spawn().insert((
24+
world.spawn((
2525
Transform(Mat4::from_scale(Vec3::ONE)),
2626
Position(Vec3::X),
2727
Rotation(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_foreach.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl<'w> Benchmark<'w> {
2121

2222
// TODO: batch this
2323
for _ in 0..10_000 {
24-
world.spawn().insert((
24+
world.spawn((
2525
Transform(Mat4::from_scale(Vec3::ONE)),
2626
Position(Vec3::X),
2727
Rotation(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_foreach_sparse_set.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl<'w> Benchmark<'w> {
2323

2424
// TODO: batch this
2525
for _ in 0..10_000 {
26-
world.spawn().insert((
26+
world.spawn((
2727
Transform(Mat4::from_scale(Vec3::ONE)),
2828
Position(Vec3::X),
2929
Rotation(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl<'w> Benchmark<'w> {
3535

3636
// TODO: batch this
3737
for _ in 0..10_000 {
38-
world.spawn().insert((
38+
world.spawn((
3939
Transform(Mat4::from_scale(Vec3::ONE)),
4040
Rotation(Vec3::X),
4141
Position::<0>(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_foreach_wide_sparse_set.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl<'w> Benchmark<'w> {
3737

3838
// TODO: batch this
3939
for _ in 0..10_000 {
40-
world.spawn().insert((
40+
world.spawn((
4141
Transform(Mat4::from_scale(Vec3::ONE)),
4242
Rotation(Vec3::X),
4343
Position::<0>(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_sparse_set.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl<'w> Benchmark<'w> {
2323

2424
// TODO: batch this
2525
for _ in 0..10_000 {
26-
world.spawn().insert((
26+
world.spawn((
2727
Transform(Mat4::from_scale(Vec3::ONE)),
2828
Position(Vec3::X),
2929
Rotation(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_system.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ impl Benchmark {
2121

2222
// TODO: batch this
2323
for _ in 0..10_000 {
24-
world.spawn().insert((
24+
world.spawn((
2525
Transform(Mat4::from_scale(Vec3::ONE)),
2626
Position(Vec3::X),
2727
Rotation(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_wide.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl<'w> Benchmark<'w> {
3535

3636
// TODO: batch this
3737
for _ in 0..10_000 {
38-
world.spawn().insert((
38+
world.spawn((
3939
Transform(Mat4::from_scale(Vec3::ONE)),
4040
Rotation(Vec3::X),
4141
Position::<0>(Vec3::X),

benches/benches/bevy_ecs/iteration/iter_simple_wide_sparse_set.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl<'w> Benchmark<'w> {
3737

3838
// TODO: batch this
3939
for _ in 0..10_000 {
40-
world.spawn().insert((
40+
world.spawn((
4141
Transform(Mat4::from_scale(Vec3::ONE)),
4242
Rotation(Vec3::X),
4343
Position::<0>(Vec3::X),

benches/benches/bevy_ecs/scheduling/run_criteria.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ struct TestBool(pub bool);
150150

151151
pub fn run_criteria_yes_with_query(criterion: &mut Criterion) {
152152
let mut world = World::new();
153-
world.spawn().insert(TestBool(true));
153+
world.spawn(TestBool(true));
154154
let mut group = criterion.benchmark_group("run_criteria/yes_using_query");
155155
group.warm_up_time(std::time::Duration::from_millis(500));
156156
group.measurement_time(std::time::Duration::from_secs(3));

benches/benches/bevy_ecs/world/commands.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ pub fn spawn_commands(criterion: &mut Criterion) {
4343
bencher.iter(|| {
4444
let mut commands = Commands::new(&mut command_queue, &world);
4545
for i in 0..entity_count {
46-
let mut entity = commands.spawn();
46+
let mut entity = commands.spawn_empty();
4747

4848
if black_box(i % 2 == 0) {
4949
entity.insert(A);
@@ -87,7 +87,7 @@ pub fn insert_commands(criterion: &mut Criterion) {
8787
let mut command_queue = CommandQueue::default();
8888
let mut entities = Vec::new();
8989
for _ in 0..entity_count {
90-
entities.push(world.spawn().id());
90+
entities.push(world.spawn_empty().id());
9191
}
9292

9393
bencher.iter(|| {
@@ -106,7 +106,7 @@ pub fn insert_commands(criterion: &mut Criterion) {
106106
let mut command_queue = CommandQueue::default();
107107
let mut entities = Vec::new();
108108
for _ in 0..entity_count {
109-
entities.push(world.spawn().id());
109+
entities.push(world.spawn_empty().id());
110110
}
111111

112112
bencher.iter(|| {

benches/benches/bevy_ecs/world/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use criterion::criterion_group;
22

33
mod commands;
4+
mod spawn;
45
mod world_get;
56

67
use commands::*;
8+
use spawn::*;
79
use world_get::*;
810

911
criterion_group!(
@@ -21,6 +23,7 @@ criterion_group!(
2123
world_query_get,
2224
world_query_iter,
2325
world_query_for_each,
26+
world_spawn,
2427
query_get_component_simple,
2528
query_get_component,
2629
query_get,
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
use bevy_ecs::prelude::*;
2+
use criterion::Criterion;
3+
use glam::*;
4+
5+
#[derive(Component)]
6+
struct A(Mat4);
7+
#[derive(Component)]
8+
struct B(Vec4);
9+
10+
pub fn world_spawn(criterion: &mut Criterion) {
11+
let mut group = criterion.benchmark_group("spawn_world");
12+
group.warm_up_time(std::time::Duration::from_millis(500));
13+
group.measurement_time(std::time::Duration::from_secs(4));
14+
15+
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
16+
group.bench_function(format!("{}_entities", entity_count), |bencher| {
17+
let mut world = World::default();
18+
bencher.iter(|| {
19+
for _ in 0..entity_count {
20+
world.spawn((A(Mat4::default()), B(Vec4::default())));
21+
}
22+
});
23+
});
24+
}
25+
26+
group.finish();
27+
}

benches/benches/bevy_ecs/world/world_get.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ pub fn query_get_component_simple(criterion: &mut Criterion) {
268268
group.bench_function("unchecked", |bencher| {
269269
let mut world = World::new();
270270

271-
let entity = world.spawn().insert(A(0.0)).id();
271+
let entity = world.spawn(A(0.0)).id();
272272
let mut query = world.query::<&mut A>();
273273

274274
bencher.iter(|| {
@@ -281,7 +281,7 @@ pub fn query_get_component_simple(criterion: &mut Criterion) {
281281
group.bench_function("system", |bencher| {
282282
let mut world = World::new();
283283

284-
let entity = world.spawn().insert(A(0.0)).id();
284+
let entity = world.spawn(A(0.0)).id();
285285
fn query_system(In(entity): In<Entity>, mut query: Query<&mut A>) {
286286
for _ in 0..100_000 {
287287
let mut a = query.get_mut(entity).unwrap();

crates/bevy_ecs/README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,8 @@ struct Velocity { x: f32, y: f32 }
5757

5858
let mut world = World::new();
5959

60-
let entity = world.spawn()
61-
.insert(Position { x: 0.0, y: 0.0 })
62-
.insert(Velocity { x: 1.0, y: 0.0 })
60+
let entity = world
61+
.spawn((Position { x: 0.0, y: 0.0 }, Velocity { x: 1.0, y: 0.0 }))
6362
.id();
6463

6564
let entity_ref = world.entity(entity);
@@ -141,9 +140,10 @@ fn main() {
141140
let mut world = World::new();
142141

143142
// Spawn an entity with Position and Velocity components
144-
world.spawn()
145-
.insert(Position { x: 0.0, y: 0.0 })
146-
.insert(Velocity { x: 1.0, y: 0.0 });
143+
world.spawn((
144+
Position { x: 0.0, y: 0.0 },
145+
Velocity { x: 1.0, y: 0.0 },
146+
));
147147

148148
// Create a new Schedule, which defines an execution strategy for Systems
149149
let mut schedule = Schedule::default();
@@ -276,10 +276,10 @@ struct PlayerBundle {
276276
let mut world = World::new();
277277

278278
// Spawn a new entity and insert the default PlayerBundle
279-
world.spawn().insert(PlayerBundle::default());
279+
world.spawn(PlayerBundle::default());
280280

281281
// Bundles play well with Rust's struct update syntax
282-
world.spawn().insert(PlayerBundle {
282+
world.spawn(PlayerBundle {
283283
position: Position { x: 1.0, y: 1.0 },
284284
..Default::default()
285285
});

crates/bevy_ecs/examples/change_detection.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ enum SimulationSystem {
6565
// If an entity gets spawned, we increase the counter in the EntityCounter resource
6666
fn spawn_entities(mut commands: Commands, mut entity_counter: ResMut<EntityCounter>) {
6767
if rand::thread_rng().gen_bool(0.6) {
68-
let entity_id = commands.spawn_bundle(Age::default()).id();
68+
let entity_id = commands.spawn(Age::default()).id();
6969
println!(" spawning {:?}", entity_id);
7070
entity_counter.value += 1;
7171
}

0 commit comments

Comments
 (0)