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.
This commit is contained in:
parent
e6a0164587
commit
2e653e5774
@ -35,6 +35,7 @@ impl ArchetypeId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) enum ComponentStatus {
|
||||
Added,
|
||||
Mutated,
|
||||
@ -45,6 +46,36 @@ pub struct AddBundle {
|
||||
pub(crate) bundle_status: Vec<ComponentStatus>,
|
||||
}
|
||||
|
||||
/// This trait is used to report the status of [`Bundle`](crate::bundle::Bundle) components
|
||||
/// being added to a given entity, relative to that entity's original archetype.
|
||||
/// See [`crate::bundle::BundleInfo::write_components`] for more info.
|
||||
pub(crate) trait BundleComponentStatus {
|
||||
/// Returns the Bundle's component status for the given "bundle index"
|
||||
///
|
||||
/// # Safety
|
||||
/// Callers must ensure that index is always a valid bundle index for the
|
||||
/// Bundle associated with this [`BundleComponentStatus`]
|
||||
unsafe fn get_status(&self, index: usize) -> ComponentStatus;
|
||||
}
|
||||
|
||||
impl BundleComponentStatus for AddBundle {
|
||||
#[inline]
|
||||
unsafe fn get_status(&self, index: usize) -> ComponentStatus {
|
||||
// SAFETY: caller has ensured index is a valid bundle index for this bundle
|
||||
*self.bundle_status.get_unchecked(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct SpawnBundleStatus;
|
||||
|
||||
impl BundleComponentStatus for SpawnBundleStatus {
|
||||
#[inline]
|
||||
unsafe fn get_status(&self, _index: usize) -> ComponentStatus {
|
||||
// Components added during a spawn_bundle call are always treated as added
|
||||
ComponentStatus::Added
|
||||
}
|
||||
}
|
||||
|
||||
/// Archetypes and bundles form a graph. Adding or removing a bundle moves
|
||||
/// an [`Entity`] to a new [`Archetype`].
|
||||
///
|
||||
|
||||
@ -5,7 +5,10 @@
|
||||
pub use bevy_ecs_macros::Bundle;
|
||||
|
||||
use crate::{
|
||||
archetype::{AddBundle, Archetype, ArchetypeId, Archetypes, ComponentStatus},
|
||||
archetype::{
|
||||
Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
|
||||
SpawnBundleStatus,
|
||||
},
|
||||
component::{Component, ComponentId, ComponentTicks, Components, StorageType},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
storage::{SparseSetIndex, SparseSets, Storages, Table},
|
||||
@ -340,13 +343,10 @@ impl BundleInfo {
|
||||
) -> BundleSpawner<'a, 'b> {
|
||||
let new_archetype_id =
|
||||
self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY);
|
||||
let (empty_archetype, archetype) =
|
||||
archetypes.get_2_mut(ArchetypeId::EMPTY, new_archetype_id);
|
||||
let archetype = &mut archetypes[new_archetype_id];
|
||||
let table = &mut storages.tables[archetype.table_id()];
|
||||
let add_bundle = empty_archetype.edges().get_add_bundle(self.id()).unwrap();
|
||||
BundleSpawner {
|
||||
archetype,
|
||||
add_bundle,
|
||||
bundle_info: self,
|
||||
table,
|
||||
entities,
|
||||
@ -355,16 +355,29 @@ impl BundleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// This writes components from a given [`Bundle`] to the given entity.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `bundle_component_status` must return the "correct" [`ComponentStatus`] for each component
|
||||
/// in the [`Bundle`], with respect to the entity's original archetype (prior to the bundle being added)
|
||||
/// For example, if the original archetype already has `ComponentA` and `T` also has `ComponentA`, the status
|
||||
/// should be `Mutated`. If the original archetype does not have `ComponentA`, the status should be `Added`.
|
||||
/// When "inserting" a bundle into an existing entity, [`AddBundle`](crate::archetype::AddBundle)
|
||||
/// should be used, which will report `Added` vs `Mutated` status based on the current archetype's structure.
|
||||
/// When spawning a bundle, [`SpawnBundleStatus`] can be used instead, which removes the need
|
||||
/// to look up the [`AddBundle`](crate::archetype::AddBundle) in the archetype graph, which requires
|
||||
/// ownership of the entity's current archetype.
|
||||
///
|
||||
/// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the
|
||||
/// `entity`, `bundle` must match this [`BundleInfo`]'s type
|
||||
#[inline]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
unsafe fn write_components<T: Bundle>(
|
||||
unsafe fn write_components<T: Bundle, S: BundleComponentStatus>(
|
||||
&self,
|
||||
table: &mut Table,
|
||||
sparse_sets: &mut SparseSets,
|
||||
add_bundle: &AddBundle,
|
||||
bundle_component_status: &S,
|
||||
entity: Entity,
|
||||
table_row: usize,
|
||||
change_tick: u32,
|
||||
@ -378,7 +391,8 @@ impl BundleInfo {
|
||||
match self.storage_types[bundle_component] {
|
||||
StorageType::Table => {
|
||||
let column = table.get_column_mut(component_id).unwrap();
|
||||
match add_bundle.bundle_status.get_unchecked(bundle_component) {
|
||||
// SAFETY: bundle_component is a valid index for this bundle
|
||||
match bundle_component_status.get_status(bundle_component) {
|
||||
ComponentStatus::Added => {
|
||||
column.initialize(
|
||||
table_row,
|
||||
@ -624,7 +638,6 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
|
||||
pub(crate) struct BundleSpawner<'a, 'b> {
|
||||
pub(crate) archetype: &'a mut Archetype,
|
||||
pub(crate) entities: &'a mut Entities,
|
||||
add_bundle: &'a AddBundle,
|
||||
bundle_info: &'b BundleInfo,
|
||||
table: &'a mut Table,
|
||||
sparse_sets: &'a mut SparseSets,
|
||||
@ -649,7 +662,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
|
||||
self.bundle_info.write_components(
|
||||
self.table,
|
||||
self.sparse_sets,
|
||||
self.add_bundle,
|
||||
&SpawnBundleStatus,
|
||||
entity,
|
||||
table_row,
|
||||
self.change_tick,
|
||||
|
||||
@ -1940,4 +1940,10 @@ mod tests {
|
||||
|
||||
assert_eq!(entity_counters.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn spawn_empty_bundle() {
|
||||
let mut world = World::new();
|
||||
world.spawn(());
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user