Skip to content

Commit b47217b

Browse files
committed
Spawn specific entities: spawn or insert operations, refactor spawn internals, world clearing (#2673)
This upstreams the code changes used by the new renderer to enable cross-app Entity reuse: * Spawning at specific entities * get_or_spawn: spawns an entity if it doesn't already exist and returns an EntityMut * insert_or_spawn_batch: the batched equivalent to `world.get_or_spawn(entity).insert_bundle(bundle)` * Clearing entities and storages * Allocating Entities with "invalid" archetypes. These entities cannot be queried / are treated as "non existent". They serve as "reserved" entities that won't show up when calling `spawn()`. They must be "specifically spawned at" using apis like `get_or_spawn(entity)`. In combination, these changes enable the "render world" to clear entities / storages each frame and reserve all "app world entities". These can then be spawned during the "render extract step". This refactors "spawn" and "insert" code in a way that I think is a massive improvement to legibility and re-usability. It also yields marginal performance wins by reducing some duplicate lookups (less than a percentage point improvement on insertion benchmarks). There is also some potential for future unsafe reduction (by making BatchSpawner and BatchInserter generic). But for now I want to cut down generic usage to a minimum to encourage smaller binaries and faster compiles. This is currently a draft because it needs more tests (although this code has already had some real-world testing on my custom-shaders branch). I also fixed the benchmarks (which currently don't compile!) / added new ones to illustrate batching wins. After these changes, Bevy ECS is basically ready to accommodate the new renderer. I think the biggest missing piece at this point is "sub apps".
1 parent c5717b5 commit b47217b

File tree

12 files changed

+1163
-327
lines changed

12 files changed

+1163
-327
lines changed

benches/benches/bevy_ecs/commands.rs

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use bevy::ecs::{
2+
entity::Entity,
23
system::{Command, CommandQueue, Commands},
34
world::World,
45
};
@@ -8,10 +9,12 @@ criterion_group!(
89
benches,
910
empty_commands,
1011
spawn_commands,
12+
insert_commands,
1113
fake_commands,
1214
zero_sized_commands,
1315
medium_sized_commands,
1416
large_sized_commands
17+
get_or_spawn,
1518
);
1619
criterion_main!(benches);
1720

@@ -76,6 +79,58 @@ fn spawn_commands(criterion: &mut Criterion) {
7679
group.finish();
7780
}
7881

82+
#[derive(Default)]
83+
struct Matrix([[f32; 4]; 4]);
84+
85+
#[derive(Default)]
86+
struct Vec3([f32; 3]);
87+
88+
fn insert_commands(criterion: &mut Criterion) {
89+
let mut group = criterion.benchmark_group("insert_commands");
90+
group.warm_up_time(std::time::Duration::from_millis(500));
91+
group.measurement_time(std::time::Duration::from_secs(4));
92+
93+
let entity_count = 10_000;
94+
group.bench_function(format!("insert"), |bencher| {
95+
let mut world = World::default();
96+
let mut command_queue = CommandQueue::default();
97+
let mut entities = Vec::new();
98+
for i in 0..entity_count {
99+
entities.push(world.spawn().id());
100+
}
101+
102+
bencher.iter(|| {
103+
let mut commands = Commands::new(&mut command_queue, &world);
104+
for entity in entities.iter() {
105+
commands.entity(*entity).insert_bundle((Matrix::default(), Vec3::default()));
106+
}
107+
drop(commands);
108+
command_queue.apply(&mut world);
109+
});
110+
});
111+
group.bench_function(format!("insert_batch"), |bencher| {
112+
let mut world = World::default();
113+
let mut command_queue = CommandQueue::default();
114+
let mut entities = Vec::new();
115+
for i in 0..entity_count {
116+
entities.push(world.spawn().id());
117+
}
118+
119+
bencher.iter(|| {
120+
let mut commands = Commands::new(&mut command_queue, &world);
121+
let mut values = Vec::with_capacity(entity_count);
122+
for entity in entities.iter() {
123+
values.push((*entity, (Matrix::default(), Vec3::default())));
124+
}
125+
commands.insert_or_spawn_batch(values);
126+
drop(commands);
127+
command_queue.apply(&mut world);
128+
});
129+
});
130+
131+
group.finish();
132+
}
133+
79134
struct FakeCommandA;
80135
struct FakeCommandB(u64);
81136

@@ -106,7 +161,7 @@ fn fake_commands(criterion: &mut Criterion) {
106161
bencher.iter(|| {
107162
let mut commands = Commands::new(&mut command_queue, &world);
108163
for i in 0..command_count {
109-
if black_box(i % 2 == 0)
164+
if black_box(i % 2 == 0) {
110165
commands.add(FakeCommandA);
111166
} else {
112167
commands.add(FakeCommandB(0));
@@ -125,7 +180,7 @@ fn fake_commands(criterion: &mut Criterion) {
125180
struct SizedCommand<T: Default + Send + Sync + 'static>(T);
126181

127182
impl<T: Default + Send + Sync + 'static> Command for SizedCommand<T> {
128-
fn write(self: Box<Self>, world: &mut World) {
183+
fn write(self, world: &mut World) {
129184
black_box(self);
130185
black_box(world);
131186
}
@@ -175,3 +230,41 @@ fn medium_sized_commands(criterion: &mut Criterion) {
175230
fn large_sized_commands(criterion: &mut Criterion) {
176231
sized_commands_impl::<SizedCommand<LargeStruct>>(criterion);
177232
}
233+
234+
fn get_or_spawn(criterion: &mut Criterion) {
235+
let mut group = criterion.benchmark_group("get_or_spawn");
236+
group.warm_up_time(std::time::Duration::from_millis(500));
237+
group.measurement_time(std::time::Duration::from_secs(4));
238+
239+
group.bench_function("individual", |bencher| {
240+
let mut world = World::default();
241+
let mut command_queue = CommandQueue::default();
242+
243+
bencher.iter(|| {
244+
let mut commands = Commands::new(&mut command_queue, &world);
245+
for i in 0..10_000 {
246+
commands
247+
.get_or_spawn(Entity::new(i))
248+
.insert_bundle((Matrix::default(), Vec3::default()));
249+
}
250+
command_queue.apply(&mut world);
251+
});
252+
});
253+
254+
group.bench_function("batched", |bencher| {
255+
let mut world = World::default();
256+
let mut command_queue = CommandQueue::default();
257+
258+
bencher.iter(|| {
259+
let mut commands = Commands::new(&mut command_queue, &world);
260+
let mut values = Vec::with_capacity(10_000);
261+
for i in 0..10_000 {
262+
values.push((Entity::new(i), (Matrix::default(), Vec3::default())));
263+
}
264+
commands.insert_or_spawn_batch(values);
265+
command_queue.apply(&mut world);
266+
});
267+
});
268+
269+
group.finish();
270+
}

crates/bevy_ecs/src/archetype.rs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,15 @@ use std::{
1515
pub struct ArchetypeId(usize);
1616

1717
impl ArchetypeId {
18+
pub const EMPTY: ArchetypeId = ArchetypeId(0);
19+
pub const RESOURCE: ArchetypeId = ArchetypeId(1);
20+
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
21+
1822
#[inline]
1923
pub const fn new(index: usize) -> Self {
2024
ArchetypeId(index)
2125
}
2226

23-
#[inline]
24-
pub const fn empty() -> ArchetypeId {
25-
ArchetypeId(0)
26-
}
27-
28-
#[inline]
29-
pub const fn resource() -> ArchetypeId {
30-
ArchetypeId(1)
31-
}
32-
3327
#[inline]
3428
pub fn index(self) -> usize {
3529
self.0
@@ -60,7 +54,7 @@ impl Edges {
6054
}
6155

6256
#[inline]
63-
pub fn set_add_bundle(
57+
pub fn insert_add_bundle(
6458
&mut self,
6559
bundle_id: BundleId,
6660
archetype_id: ArchetypeId,
@@ -81,7 +75,7 @@ impl Edges {
8175
}
8276

8377
#[inline]
84-
pub fn set_remove_bundle(&mut self, bundle_id: BundleId, archetype_id: Option<ArchetypeId>) {
78+
pub fn insert_remove_bundle(&mut self, bundle_id: BundleId, archetype_id: Option<ArchetypeId>) {
8579
self.remove_bundle.insert(bundle_id, archetype_id);
8680
}
8781

@@ -94,7 +88,7 @@ impl Edges {
9488
}
9589

9690
#[inline]
97-
pub fn set_remove_bundle_intersection(
91+
pub fn insert_remove_bundle_intersection(
9892
&mut self,
9993
bundle_id: BundleId,
10094
archetype_id: Option<ArchetypeId>,
@@ -309,6 +303,11 @@ impl Archetype {
309303
.get(component_id)
310304
.map(|info| info.archetype_component_id)
311305
}
306+
307+
pub(crate) fn clear_entities(&mut self) {
308+
self.entities.clear();
309+
self.table_info.entity_rows.clear();
310+
}
312311
}
313312

314313
/// A generational id that changes every time the set of archetypes changes
@@ -377,7 +376,7 @@ impl Default for Archetypes {
377376
// adds the resource archetype. it is "special" in that it is inaccessible via a "hash",
378377
// which prevents entities from being added to it
379378
archetypes.archetypes.push(Archetype::new(
380-
ArchetypeId::resource(),
379+
ArchetypeId::RESOURCE,
381380
TableId::empty(),
382381
Cow::Owned(Vec::new()),
383382
Cow::Owned(Vec::new()),
@@ -402,33 +401,30 @@ impl Archetypes {
402401
#[inline]
403402
pub fn empty(&self) -> &Archetype {
404403
// SAFE: empty archetype always exists
405-
unsafe { self.archetypes.get_unchecked(ArchetypeId::empty().index()) }
404+
unsafe { self.archetypes.get_unchecked(ArchetypeId::EMPTY.index()) }
406405
}
407406

408407
#[inline]
409408
pub fn empty_mut(&mut self) -> &mut Archetype {
410409
// SAFE: empty archetype always exists
411410
unsafe {
412411
self.archetypes
413-
.get_unchecked_mut(ArchetypeId::empty().index())
412+
.get_unchecked_mut(ArchetypeId::EMPTY.index())
414413
}
415414
}
416415

417416
#[inline]
418417
pub fn resource(&self) -> &Archetype {
419418
// SAFE: resource archetype always exists
420-
unsafe {
421-
self.archetypes
422-
.get_unchecked(ArchetypeId::resource().index())
423-
}
419+
unsafe { self.archetypes.get_unchecked(ArchetypeId::RESOURCE.index()) }
424420
}
425421

426422
#[inline]
427423
pub fn resource_mut(&mut self) -> &mut Archetype {
428424
// SAFE: resource archetype always exists
429425
unsafe {
430426
self.archetypes
431-
.get_unchecked_mut(ArchetypeId::resource().index())
427+
.get_unchecked_mut(ArchetypeId::RESOURCE.index())
432428
}
433429
}
434430

@@ -519,6 +515,12 @@ impl Archetypes {
519515
pub fn archetype_components_len(&self) -> usize {
520516
self.archetype_component_count
521517
}
518+
519+
pub fn clear_entities(&mut self) {
520+
for archetype in self.archetypes.iter_mut() {
521+
archetype.clear_entities();
522+
}
523+
}
522524
}
523525

524526
impl Index<ArchetypeId> for Archetypes {

0 commit comments

Comments
 (0)