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 { | pub(crate) enum ComponentStatus { | ||||||
|     Added, |     Added, | ||||||
|     Mutated, |     Mutated, | ||||||
| @ -45,6 +46,36 @@ pub struct AddBundle { | |||||||
|     pub(crate) bundle_status: Vec<ComponentStatus>, |     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
 | /// Archetypes and bundles form a graph. Adding or removing a bundle moves
 | ||||||
| /// an [`Entity`] to a new [`Archetype`].
 | /// an [`Entity`] to a new [`Archetype`].
 | ||||||
| ///
 | ///
 | ||||||
|  | |||||||
| @ -5,7 +5,10 @@ | |||||||
| pub use bevy_ecs_macros::Bundle; | pub use bevy_ecs_macros::Bundle; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     archetype::{AddBundle, Archetype, ArchetypeId, Archetypes, ComponentStatus}, |     archetype::{ | ||||||
|  |         Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus, | ||||||
|  |         SpawnBundleStatus, | ||||||
|  |     }, | ||||||
|     component::{Component, ComponentId, ComponentTicks, Components, StorageType}, |     component::{Component, ComponentId, ComponentTicks, Components, StorageType}, | ||||||
|     entity::{Entities, Entity, EntityLocation}, |     entity::{Entities, Entity, EntityLocation}, | ||||||
|     storage::{SparseSetIndex, SparseSets, Storages, Table}, |     storage::{SparseSetIndex, SparseSets, Storages, Table}, | ||||||
| @ -340,13 +343,10 @@ impl BundleInfo { | |||||||
|     ) -> BundleSpawner<'a, 'b> { |     ) -> BundleSpawner<'a, 'b> { | ||||||
|         let new_archetype_id = |         let new_archetype_id = | ||||||
|             self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); |             self.add_bundle_to_archetype(archetypes, storages, components, ArchetypeId::EMPTY); | ||||||
|         let (empty_archetype, archetype) = |         let archetype = &mut archetypes[new_archetype_id]; | ||||||
|             archetypes.get_2_mut(ArchetypeId::EMPTY, new_archetype_id); |  | ||||||
|         let table = &mut storages.tables[archetype.table_id()]; |         let table = &mut storages.tables[archetype.table_id()]; | ||||||
|         let add_bundle = empty_archetype.edges().get_add_bundle(self.id()).unwrap(); |  | ||||||
|         BundleSpawner { |         BundleSpawner { | ||||||
|             archetype, |             archetype, | ||||||
|             add_bundle, |  | ||||||
|             bundle_info: self, |             bundle_info: self, | ||||||
|             table, |             table, | ||||||
|             entities, |             entities, | ||||||
| @ -355,16 +355,29 @@ impl BundleInfo { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// This writes components from a given [`Bundle`] to the given entity.
 | ||||||
|  |     ///
 | ||||||
|     /// # Safety
 |     /// # 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
 |     /// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the
 | ||||||
|     /// `entity`, `bundle` must match this [`BundleInfo`]'s type
 |     /// `entity`, `bundle` must match this [`BundleInfo`]'s type
 | ||||||
|     #[inline] |     #[inline] | ||||||
|     #[allow(clippy::too_many_arguments)] |     #[allow(clippy::too_many_arguments)] | ||||||
|     unsafe fn write_components<T: Bundle>( |     unsafe fn write_components<T: Bundle, S: BundleComponentStatus>( | ||||||
|         &self, |         &self, | ||||||
|         table: &mut Table, |         table: &mut Table, | ||||||
|         sparse_sets: &mut SparseSets, |         sparse_sets: &mut SparseSets, | ||||||
|         add_bundle: &AddBundle, |         bundle_component_status: &S, | ||||||
|         entity: Entity, |         entity: Entity, | ||||||
|         table_row: usize, |         table_row: usize, | ||||||
|         change_tick: u32, |         change_tick: u32, | ||||||
| @ -378,7 +391,8 @@ impl BundleInfo { | |||||||
|             match self.storage_types[bundle_component] { |             match self.storage_types[bundle_component] { | ||||||
|                 StorageType::Table => { |                 StorageType::Table => { | ||||||
|                     let column = table.get_column_mut(component_id).unwrap(); |                     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 => { |                         ComponentStatus::Added => { | ||||||
|                             column.initialize( |                             column.initialize( | ||||||
|                                 table_row, |                                 table_row, | ||||||
| @ -624,7 +638,6 @@ impl<'a, 'b> BundleInserter<'a, 'b> { | |||||||
| pub(crate) struct BundleSpawner<'a, 'b> { | pub(crate) struct BundleSpawner<'a, 'b> { | ||||||
|     pub(crate) archetype: &'a mut Archetype, |     pub(crate) archetype: &'a mut Archetype, | ||||||
|     pub(crate) entities: &'a mut Entities, |     pub(crate) entities: &'a mut Entities, | ||||||
|     add_bundle: &'a AddBundle, |  | ||||||
|     bundle_info: &'b BundleInfo, |     bundle_info: &'b BundleInfo, | ||||||
|     table: &'a mut Table, |     table: &'a mut Table, | ||||||
|     sparse_sets: &'a mut SparseSets, |     sparse_sets: &'a mut SparseSets, | ||||||
| @ -649,7 +662,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> { | |||||||
|         self.bundle_info.write_components( |         self.bundle_info.write_components( | ||||||
|             self.table, |             self.table, | ||||||
|             self.sparse_sets, |             self.sparse_sets, | ||||||
|             self.add_bundle, |             &SpawnBundleStatus, | ||||||
|             entity, |             entity, | ||||||
|             table_row, |             table_row, | ||||||
|             self.change_tick, |             self.change_tick, | ||||||
|  | |||||||
| @ -1940,4 +1940,10 @@ mod tests { | |||||||
| 
 | 
 | ||||||
|         assert_eq!(entity_counters.len(), 0); |         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
	 Carter Anderson
						Carter Anderson