Fix spawn tracking for spawn commands (#19351)
# Objective See also https://discord.com/channels/691052431525675048/1374187654425481266/1375553989185372292. ## Solution Set spawn info in `Commands::spawn_empty`. Also added a benchmark for `Commands::spawn`. ## Testing See added test.
This commit is contained in:
parent
54c9f03021
commit
158d9aff0e
@ -62,6 +62,31 @@ pub fn spawn_commands(criterion: &mut Criterion) {
|
|||||||
group.finish();
|
group.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nonempty_spawn_commands(criterion: &mut Criterion) {
|
||||||
|
let mut group = criterion.benchmark_group("nonempty_spawn_commands");
|
||||||
|
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||||
|
group.measurement_time(core::time::Duration::from_secs(4));
|
||||||
|
|
||||||
|
for entity_count in [100, 1_000, 10_000] {
|
||||||
|
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);
|
||||||
|
for i in 0..entity_count {
|
||||||
|
if black_box(i % 2 == 0) {
|
||||||
|
commands.spawn(A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
command_queue.apply(&mut world);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
group.finish();
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Component)]
|
#[derive(Default, Component)]
|
||||||
struct Matrix([[f32; 4]; 4]);
|
struct Matrix([[f32; 4]; 4]);
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ criterion_group!(
|
|||||||
benches,
|
benches,
|
||||||
empty_commands,
|
empty_commands,
|
||||||
spawn_commands,
|
spawn_commands,
|
||||||
|
nonempty_spawn_commands,
|
||||||
insert_commands,
|
insert_commands,
|
||||||
fake_commands,
|
fake_commands,
|
||||||
zero_sized_commands,
|
zero_sized_commands,
|
||||||
|
@ -943,6 +943,15 @@ impl Entities {
|
|||||||
meta.spawned_or_despawned = MaybeUninit::new(SpawnedOrDespawned { by, at });
|
meta.spawned_or_despawned = MaybeUninit::new(SpawnedOrDespawned { by, at });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// - `index` must be a valid entity index.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn mark_spawn_despawn(&mut self, index: u32, by: MaybeLocation, at: Tick) {
|
||||||
|
// SAFETY: Caller guarantees that `index` a valid entity index
|
||||||
|
let meta = unsafe { self.meta.get_unchecked_mut(index as usize) };
|
||||||
|
meta.spawned_or_despawned = MaybeUninit::new(SpawnedOrDespawned { by, at });
|
||||||
|
}
|
||||||
|
|
||||||
/// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this
|
/// Increments the `generation` of a freed [`Entity`]. The next entity ID allocated with this
|
||||||
/// `index` will count `generation` starting from the prior `generation` + the specified
|
/// `index` will count `generation` starting from the prior `generation` + the specified
|
||||||
/// value + 1.
|
/// value + 1.
|
||||||
|
@ -16,7 +16,7 @@ use core::marker::PhantomData;
|
|||||||
use crate::{
|
use crate::{
|
||||||
self as bevy_ecs,
|
self as bevy_ecs,
|
||||||
bundle::{Bundle, InsertMode, NoBundleEffect},
|
bundle::{Bundle, InsertMode, NoBundleEffect},
|
||||||
change_detection::Mut,
|
change_detection::{MaybeLocation, Mut},
|
||||||
component::{Component, ComponentId, Mutable},
|
component::{Component, ComponentId, Mutable},
|
||||||
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
|
entity::{Entities, Entity, EntityClonerBuilder, EntityDoesNotExistError},
|
||||||
error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
|
error::{ignore, warn, BevyError, CommandWithEntity, ErrorContext, HandleError},
|
||||||
@ -317,12 +317,24 @@ impl<'w, 's> Commands<'w, 's> {
|
|||||||
/// - [`spawn`](Self::spawn) to spawn an entity with components.
|
/// - [`spawn`](Self::spawn) to spawn an entity with components.
|
||||||
/// - [`spawn_batch`](Self::spawn_batch) to spawn many entities
|
/// - [`spawn_batch`](Self::spawn_batch) to spawn many entities
|
||||||
/// with the same combination of components.
|
/// with the same combination of components.
|
||||||
|
#[track_caller]
|
||||||
pub fn spawn_empty(&mut self) -> EntityCommands {
|
pub fn spawn_empty(&mut self) -> EntityCommands {
|
||||||
let entity = self.entities.reserve_entity();
|
let entity = self.entities.reserve_entity();
|
||||||
EntityCommands {
|
let mut entity_commands = EntityCommands {
|
||||||
entity,
|
entity,
|
||||||
commands: self.reborrow(),
|
commands: self.reborrow(),
|
||||||
|
};
|
||||||
|
let caller = MaybeLocation::caller();
|
||||||
|
entity_commands.queue(move |entity: EntityWorldMut| {
|
||||||
|
let index = entity.id().index();
|
||||||
|
let world = entity.into_world_mut();
|
||||||
|
let tick = world.change_tick();
|
||||||
|
// SAFETY: Entity has been flushed
|
||||||
|
unsafe {
|
||||||
|
world.entities_mut().mark_spawn_despawn(index, caller, tick);
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
entity_commands
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawns a new [`Entity`] with the given components
|
/// Spawns a new [`Entity`] with the given components
|
||||||
@ -369,9 +381,35 @@ impl<'w, 's> Commands<'w, 's> {
|
|||||||
/// with the same combination of components.
|
/// with the same combination of components.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands {
|
pub fn spawn<T: Bundle>(&mut self, bundle: T) -> EntityCommands {
|
||||||
let mut entity = self.spawn_empty();
|
let entity = self.entities.reserve_entity();
|
||||||
entity.insert(bundle);
|
let mut entity_commands = EntityCommands {
|
||||||
entity
|
entity,
|
||||||
|
commands: self.reborrow(),
|
||||||
|
};
|
||||||
|
let caller = MaybeLocation::caller();
|
||||||
|
|
||||||
|
entity_commands.queue(move |mut entity: EntityWorldMut| {
|
||||||
|
// Store metadata about the spawn operation.
|
||||||
|
// This is the same as in `spawn_empty`, but merged into
|
||||||
|
// the same command for better performance.
|
||||||
|
let index = entity.id().index();
|
||||||
|
entity.world_scope(|world| {
|
||||||
|
let tick = world.change_tick();
|
||||||
|
// SAFETY: Entity has been flushed
|
||||||
|
unsafe {
|
||||||
|
world.entities_mut().mark_spawn_despawn(index, caller, tick);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
entity.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
InsertMode::Replace,
|
||||||
|
caller,
|
||||||
|
crate::relationship::RelationshipHookMode::Run,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
// entity_command::insert(bundle, InsertMode::Replace)
|
||||||
|
entity_commands
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the [`EntityCommands`] for the given [`Entity`].
|
/// Returns the [`EntityCommands`] for the given [`Entity`].
|
||||||
@ -2577,4 +2615,17 @@ mod tests {
|
|||||||
assert!(world.contains_resource::<W<i32>>());
|
assert!(world.contains_resource::<W<i32>>());
|
||||||
assert!(world.contains_resource::<W<f64>>());
|
assert!(world.contains_resource::<W<f64>>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn track_spawn_ticks() {
|
||||||
|
let mut world = World::default();
|
||||||
|
world.increment_change_tick();
|
||||||
|
let expected = world.change_tick();
|
||||||
|
let id = world.commands().spawn_empty().id();
|
||||||
|
world.flush();
|
||||||
|
assert_eq!(
|
||||||
|
Some(expected),
|
||||||
|
world.entities().entity_get_spawned_or_despawned_at(id)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user