Skip to content

Commit 2e653e5

Browse files
committed
Fix spawning empty bundles (#6425)
# Objective Alternative to #6424 Fixes #6226 Fixes spawning empty bundles ## Solution Add `BundleComponentStatus` trait and implement it for `AddBundle` and a new `SpawnBundleStatus` type (which always returns an Added status). `write_components` is now generic on `BundleComponentStatus` instead of taking `AddBundle` directly. This means BundleSpawner can now avoid needing AddBundle from the Empty archetype, which means BundleSpawner no longer needs a reference to the original archetype. In theory this cuts down on the work done in `write_components` when spawning, but I'm seeing no change in the spawn benchmarks.
1 parent e6a0164 commit 2e653e5

File tree

3 files changed

+60
-10
lines changed

3 files changed

+60
-10
lines changed

crates/bevy_ecs/src/archetype.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl ArchetypeId {
3535
}
3636
}
3737

38+
#[derive(Copy, Clone)]
3839
pub(crate) enum ComponentStatus {
3940
Added,
4041
Mutated,
@@ -45,6 +46,36 @@ pub struct AddBundle {
4546
pub(crate) bundle_status: Vec<ComponentStatus>,
4647
}
4748

49+
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
50+
/// being added to a given entity, relative to that entity's original archetype.
51+
/// See [`crate::bundle::BundleInfo::write_components`] for more info.
52+
pub(crate) trait BundleComponentStatus {
53+
/// Returns the Bundle's component status for the given "bundle index"
54+
///
55+
/// # Safety
56+
/// Callers must ensure that index is always a valid bundle index for the
57+
/// Bundle associated with this [`BundleComponentStatus`]
58+
unsafe fn get_status(&self, index: usize) -> ComponentStatus;
59+
}
60+
61+
impl BundleComponentStatus for AddBundle {
62+
#[inline]
63+
unsafe fn get_status(&self, index: usize) -> ComponentStatus {
64+
// SAFETY: caller has ensured index is a valid bundle index for this bundle
65+
*self.bundle_status.get_unchecked(index)
66+
}
67+
}
68+
69+
pub(crate) struct SpawnBundleStatus;
70+
71+
impl BundleComponentStatus for SpawnBundleStatus {
72+
#[inline]
73+
unsafe fn get_status(&self, _index: usize) -> ComponentStatus {
74+
// Components added during a spawn_bundle call are always treated as added
75+
ComponentStatus::Added
76+
}
77+
}
78+
4879
/// Archetypes and bundles form a graph. Adding or removing a bundle moves
4980
/// an [`Entity`] to a new [`Archetype`].
5081
///

crates/bevy_ecs/src/bundle.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
pub use bevy_ecs_macros::Bundle;
66

77
use crate::{
8-
archetype::{AddBundle, Archetype, ArchetypeId, Archetypes, ComponentStatus},
8+
archetype::{
9+
Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
10+
SpawnBundleStatus,
11+
},
912
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
1013
entity::{Entities, Entity, EntityLocation},
1114
storage::{SparseSetIndex, SparseSets, Storages, Table},
@@ -340,13 +343,10 @@ impl BundleInfo {
340343
) -> BundleSpawner<'a, 'b> {
341344
let new_archetype_id =
342345
self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY);
343-
let (empty_archetype, archetype) =
344-
archetypes.get_2_mut(ArchetypeId::EMPTY, new_archetype_id);
346+
let archetype = &mut archetypes[new_archetype_id];
345347
let table = &mut storages.tables[archetype.table_id()];
346-
let add_bundle = empty_archetype.edges().get_add_bundle(self.id()).unwrap();
347348
BundleSpawner {
348349
archetype,
349-
add_bundle,
350350
bundle_info: self,
351351
table,
352352
entities,
@@ -355,16 +355,29 @@ impl BundleInfo {
355355
}
356356
}
357357

358+
/// This writes components from a given [`Bundle`] to the given entity.
359+
///
358360
/// # Safety
361+
///
362+
/// `bundle_component_status` must return the "correct" [`ComponentStatus`] for each component
363+
/// in the [`Bundle`], with respect to the entity's original archetype (prior to the bundle being added)
364+
/// For example, if the original archetype already has `ComponentA` and `T` also has `ComponentA`, the status
365+
/// should be `Mutated`. If the original archetype does not have `ComponentA`, the status should be `Added`.
366+
/// When "inserting" a bundle into an existing entity, [`AddBundle`](crate::archetype::AddBundle)
367+
/// should be used, which will report `Added` vs `Mutated` status based on the current archetype's structure.
368+
/// When spawning a bundle, [`SpawnBundleStatus`] can be used instead, which removes the need
369+
/// to look up the [`AddBundle`](crate::archetype::AddBundle) in the archetype graph, which requires
370+
/// ownership of the entity's current archetype.
371+
///
359372
/// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the
360373
/// `entity`, `bundle` must match this [`BundleInfo`]'s type
361374
#[inline]
362375
#[allow(clippy::too_many_arguments)]
363-
unsafe fn write_components<T: Bundle>(
376+
unsafe fn write_components<T: Bundle, S: BundleComponentStatus>(
364377
&self,
365378
table: &mut Table,
366379
sparse_sets: &mut SparseSets,
367-
add_bundle: &AddBundle,
380+
bundle_component_status: &S,
368381
entity: Entity,
369382
table_row: usize,
370383
change_tick: u32,
@@ -378,7 +391,8 @@ impl BundleInfo {
378391
match self.storage_types[bundle_component] {
379392
StorageType::Table => {
380393
let column = table.get_column_mut(component_id).unwrap();
381-
match add_bundle.bundle_status.get_unchecked(bundle_component) {
394+
// SAFETY: bundle_component is a valid index for this bundle
395+
match bundle_component_status.get_status(bundle_component) {
382396
ComponentStatus::Added => {
383397
column.initialize(
384398
table_row,
@@ -624,7 +638,6 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
624638
pub(crate) struct BundleSpawner<'a, 'b> {
625639
pub(crate) archetype: &'a mut Archetype,
626640
pub(crate) entities: &'a mut Entities,
627-
add_bundle: &'a AddBundle,
628641
bundle_info: &'b BundleInfo,
629642
table: &'a mut Table,
630643
sparse_sets: &'a mut SparseSets,
@@ -649,7 +662,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
649662
self.bundle_info.write_components(
650663
self.table,
651664
self.sparse_sets,
652-
self.add_bundle,
665+
&SpawnBundleStatus,
653666
entity,
654667
table_row,
655668
self.change_tick,

crates/bevy_ecs/src/world/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1940,4 +1940,10 @@ mod tests {
19401940

19411941
assert_eq!(entity_counters.len(), 0);
19421942
}
1943+
1944+
#[test]
1945+
fn spawn_empty_bundle() {
1946+
let mut world = World::new();
1947+
world.spawn(());
1948+
}
19431949
}

0 commit comments

Comments
 (0)