Skip to content

Commit 4562bb4

Browse files
SpecificProtagonistmockersf
authored andcommitted
Fix spawn tracking for spawn commands (#19351)
See also https://discord.com/channels/691052431525675048/1374187654425481266/1375553989185372292. Set spawn info in `Commands::spawn_empty`. Also added a benchmark for `Commands::spawn`. See added test.
1 parent 6fc2e91 commit 4562bb4

File tree

4 files changed

+91
-6
lines changed

4 files changed

+91
-6
lines changed

benches/benches/bevy_ecs/world/commands.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,31 @@ pub fn spawn_commands(criterion: &mut Criterion) {
6262
group.finish();
6363
}
6464

65+
pub fn nonempty_spawn_commands(criterion: &mut Criterion) {
66+
let mut group = criterion.benchmark_group("nonempty_spawn_commands");
67+
group.warm_up_time(core::time::Duration::from_millis(500));
68+
group.measurement_time(core::time::Duration::from_secs(4));
69+
70+
for entity_count in [100, 1_000, 10_000] {
71+
group.bench_function(format!("{}_entities", entity_count), |bencher| {
72+
let mut world = World::default();
73+
let mut command_queue = CommandQueue::default();
74+
75+
bencher.iter(|| {
76+
let mut commands = Commands::new(&mut command_queue, &world);
77+
for i in 0..entity_count {
78+
if black_box(i % 2 == 0) {
79+
commands.spawn(A);
80+
}
81+
}
82+
command_queue.apply(&mut world);
83+
});
84+
});
85+
}
86+
87+
group.finish();
88+
}
89+
6590
#[derive(Default, Component)]
6691
struct Matrix([[f32; 4]; 4]);
6792

benches/benches/bevy_ecs/world/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ criterion_group!(
1717
benches,
1818
empty_commands,
1919
spawn_commands,
20+
nonempty_spawn_commands,
2021
insert_commands,
2122
fake_commands,
2223
zero_sized_commands,

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ pub use unique_vec::{UniqueEntityEquivalentVec, UniqueEntityVec};
7474
use crate::{
7575
archetype::{ArchetypeId, ArchetypeRow},
7676
change_detection::MaybeLocation,
77+
component::Tick,
7778
identifier::{
7879
error::IdentifierError,
7980
kinds::IdKind,
@@ -84,7 +85,13 @@ use crate::{
8485
};
8586
use alloc::vec::Vec;
8687
use bevy_platform::sync::atomic::Ordering;
87-
use core::{fmt, hash::Hash, mem, num::NonZero, panic::Location};
88+
use core::{
89+
fmt,
90+
hash::Hash,
91+
mem::{self},
92+
num::NonZero,
93+
panic::Location,
94+
};
8895
use log::warn;
8996

9097
#[cfg(feature = "serialize")]
@@ -866,6 +873,20 @@ impl Entities {
866873
meta.location = location;
867874
}
868875

876+
/// # Safety
877+
/// - `index` must be a valid entity index.
878+
#[inline]
879+
pub(crate) unsafe fn mark_spawn_despawn(&mut self, index: u32, by: MaybeLocation, _at: Tick) {
880+
// // SAFETY: Caller guarantees that `index` a valid entity index
881+
// let meta = unsafe { self.meta.get_unchecked_mut(index as usize) };
882+
// meta.spawned_or_despawned_by = MaybeUninit::new(SpawnedOrDespawned { by, at });
883+
by.map(|caller| {
884+
// SAFETY: Caller guarantees that `index` a valid entity index
885+
let meta = unsafe { self.meta.get_unchecked_mut(index as usize) };
886+
meta.spawned_or_despawned_by = MaybeLocation::new(Some(caller));
887+
});
888+
}
889+
869890
/// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this
870891
/// `index` will count `generation` starting from the prior `generation` + the specified
871892
/// value + 1.

crates/bevy_ecs/src/system/commands/mod.rs

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,24 @@ impl<'w, 's> Commands<'w, 's> {
318318
/// - [`spawn`](Self::spawn) to spawn an entity with components.
319319
/// - [`spawn_batch`](Self::spawn_batch) to spawn many entities
320320
/// with the same combination of components.
321+
#[track_caller]
321322
pub fn spawn_empty(&mut self) -> EntityCommands {
322323
let entity = self.entities.reserve_entity();
323-
EntityCommands {
324+
let mut entity_commands = EntityCommands {
324325
entity,
325326
commands: self.reborrow(),
326-
}
327+
};
328+
let caller = MaybeLocation::caller();
329+
entity_commands.queue(move |entity: EntityWorldMut| {
330+
let index = entity.id().index();
331+
let world = entity.into_world_mut();
332+
let tick = world.change_tick();
333+
// SAFETY: Entity has been flushed
334+
unsafe {
335+
world.entities_mut().mark_spawn_despawn(index, caller, tick);
336+
}
337+
});
338+
entity_commands
327339
}
328340

329341
/// Spawns a new [`Entity`] with the given components
@@ -370,9 +382,35 @@ impl<'w, 's> Commands<'w, 's> {
370382
/// with the same combination of components.
371383
#[track_caller]
372384
pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands {
373-
let mut entity = self.spawn_empty();
374-
entity.insert(bundle);
375-
entity
385+
let entity = self.entities.reserve_entity();
386+
let mut entity_commands = EntityCommands {
387+
entity,
388+
commands: self.reborrow(),
389+
};
390+
let caller = MaybeLocation::caller();
391+
392+
entity_commands.queue(move |mut entity: EntityWorldMut| {
393+
// Store metadata about the spawn operation.
394+
// This is the same as in `spawn_empty`, but merged into
395+
// the same command for better performance.
396+
let index = entity.id().index();
397+
entity.world_scope(|world| {
398+
let tick = world.change_tick();
399+
// SAFETY: Entity has been flushed
400+
unsafe {
401+
world.entities_mut().mark_spawn_despawn(index, caller, tick);
402+
}
403+
});
404+
405+
entity.insert_with_caller(
406+
bundle,
407+
InsertMode::Replace,
408+
caller,
409+
crate::relationship::RelationshipHookMode::Run,
410+
);
411+
});
412+
// entity_command::insert(bundle, InsertMode::Replace)
413+
entity_commands
376414
}
377415

378416
/// Returns the [`EntityCommands`] for the given [`Entity`].

0 commit comments

Comments
 (0)