Extract bundle spawner code

This commit is contained in:
Giacomo Stevanato 2025-07-10 19:43:52 +02:00
parent fa878af9f6
commit 0d4f8ea461
No known key found for this signature in database
2 changed files with 197 additions and 187 deletions

View File

@ -5,6 +5,7 @@
mod impls;
mod insert;
mod remove;
mod spawner;
#[cfg(test)]
mod tests;
@ -62,26 +63,20 @@ mod tests;
pub use bevy_ecs_macros::Bundle;
use crate::{
archetype::{
Archetype, ArchetypeCreated, ArchetypeId, BundleComponentStatus, ComponentStatus,
SpawnBundleStatus,
},
archetype::{Archetype, BundleComponentStatus, ComponentStatus},
change_detection::MaybeLocation,
component::{
ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor,
RequiredComponents, StorageType, Tick,
},
entity::{Entities, Entity, EntityLocation},
lifecycle::{ADD, INSERT},
prelude::World,
entity::Entity,
query::DebugCheckedUnwrap,
relationship::RelationshipHookMode,
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut},
world::EntityWorldMut,
};
use alloc::{boxed::Box, vec, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_ptr::{ConstNonNull, OwningPtr};
use bevy_ptr::OwningPtr;
use bevy_utils::TypeIdMap;
use core::{any::TypeId, ptr::NonNull};
@ -610,183 +605,6 @@ pub(crate) enum ArchetypeMoveType {
},
}
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
pub(crate) struct BundleSpawner<'w> {
world: UnsafeWorldCell<'w>,
bundle_info: ConstNonNull<BundleInfo>,
table: NonNull<Table>,
archetype: NonNull<Archetype>,
change_tick: Tick,
}
impl<'w> BundleSpawner<'w> {
#[inline]
pub fn new<T: Bundle>(world: &'w mut World, change_tick: Tick) -> Self {
// SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too.
let mut registrator =
unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) };
let bundle_id = world
.bundles
.register_info::<T>(&mut registrator, &mut world.storages);
// SAFETY: we initialized this bundle_id in `init_info`
unsafe { Self::new_with_id(world, bundle_id, change_tick) }
}
/// Creates a new [`BundleSpawner`].
///
/// # Safety
/// Caller must ensure that `bundle_id` exists in `world.bundles`
#[inline]
pub(crate) unsafe fn new_with_id(
world: &'w mut World,
bundle_id: BundleId,
change_tick: Tick,
) -> Self {
let bundle_info = world.bundles.get_unchecked(bundle_id);
let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
ArchetypeId::EMPTY,
);
let archetype = &mut world.archetypes[new_archetype_id];
let table = &mut world.storages.tables[archetype.table_id()];
let spawner = Self {
bundle_info: bundle_info.into(),
table: table.into(),
archetype: archetype.into(),
change_tick,
world: world.as_unsafe_world_cell(),
};
if is_new_created {
spawner
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
spawner
}
#[inline]
pub fn reserve_storage(&mut self, additional: usize) {
// SAFETY: There are no outstanding world references
let (archetype, table) = unsafe { (self.archetype.as_mut(), self.table.as_mut()) };
archetype.reserve(additional);
table.reserve(additional);
}
/// # Safety
/// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
#[inline]
#[track_caller]
pub unsafe fn spawn_non_existent<T: DynamicBundle>(
&mut self,
entity: Entity,
bundle: T,
caller: MaybeLocation,
) -> (EntityLocation, T::Effect) {
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
let bundle_info = self.bundle_info.as_ref();
let (location, after_effect) = {
let table = self.table.as_mut();
let archetype = self.archetype.as_mut();
// SAFETY: Mutable references do not alias and will be dropped after this block
let (sparse_sets, entities) = {
let world = self.world.world_mut();
(&mut world.storages.sparse_sets, &mut world.entities)
};
let table_row = table.allocate(entity);
let location = archetype.allocate(entity, table_row);
let after_effect = bundle_info.write_components(
table,
sparse_sets,
&SpawnBundleStatus,
bundle_info.required_components.iter(),
entity,
table_row,
self.change_tick,
bundle,
InsertMode::Replace,
caller,
);
entities.set(entity.index(), Some(location));
entities.mark_spawn_despawn(entity.index(), caller, self.change_tick);
(location, after_effect)
};
// SAFETY: We have no outstanding mutable references to world as they were dropped
let mut deferred_world = unsafe { self.world.into_deferred() };
// SAFETY: `DeferredWorld` cannot provide mutable access to `Archetypes`.
let archetype = self.archetype.as_ref();
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(
archetype,
entity,
bundle_info.iter_contributed_components(),
caller,
);
if archetype.has_add_observer() {
deferred_world.trigger_observers(
ADD,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
}
deferred_world.trigger_on_insert(
archetype,
entity,
bundle_info.iter_contributed_components(),
caller,
RelationshipHookMode::Run,
);
if archetype.has_insert_observer() {
deferred_world.trigger_observers(
INSERT,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
}
};
(location, after_effect)
}
/// # Safety
/// `T` must match this [`BundleInfo`]'s type
#[inline]
pub unsafe fn spawn<T: Bundle>(
&mut self,
bundle: T,
caller: MaybeLocation,
) -> (Entity, T::Effect) {
let entity = self.entities().alloc();
// SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type
let (_, after_effect) = unsafe { self.spawn_non_existent(entity, bundle, caller) };
(entity, after_effect)
}
#[inline]
pub(crate) fn entities(&mut self) -> &mut Entities {
// SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers
unsafe { &mut self.world.world_mut().entities }
}
/// # Safety
/// - `Self` must be dropped after running this function as it may invalidate internal pointers.
#[inline]
pub(crate) unsafe fn flush_commands(&mut self) {
// SAFETY: pointers on self can be invalidated,
self.world.world_mut().flush();
}
}
/// Metadata for bundles. Stores a [`BundleInfo`] for each type of [`Bundle`] in a given world.
#[derive(Default)]
pub struct Bundles {

View File

@ -0,0 +1,192 @@
use core::ptr::NonNull;
use bevy_ptr::ConstNonNull;
use crate::{
archetype::{Archetype, ArchetypeCreated, ArchetypeId, SpawnBundleStatus},
bundle::{Bundle, BundleId, BundleInfo, DynamicBundle, InsertMode},
change_detection::MaybeLocation,
component::{ComponentsRegistrator, Tick},
entity::{Entities, Entity, EntityLocation},
lifecycle::{ADD, INSERT},
relationship::RelationshipHookMode,
storage::Table,
world::{unsafe_world_cell::UnsafeWorldCell, World},
};
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
pub(crate) struct BundleSpawner<'w> {
world: UnsafeWorldCell<'w>,
bundle_info: ConstNonNull<BundleInfo>,
table: NonNull<Table>,
archetype: NonNull<Archetype>,
change_tick: Tick,
}
impl<'w> BundleSpawner<'w> {
#[inline]
pub fn new<T: Bundle>(world: &'w mut World, change_tick: Tick) -> Self {
// SAFETY: These come from the same world. `world.components_registrator` can't be used since we borrow other fields too.
let mut registrator =
unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) };
let bundle_id = world
.bundles
.register_info::<T>(&mut registrator, &mut world.storages);
// SAFETY: we initialized this bundle_id in `init_info`
unsafe { Self::new_with_id(world, bundle_id, change_tick) }
}
/// Creates a new [`BundleSpawner`].
///
/// # Safety
/// Caller must ensure that `bundle_id` exists in `world.bundles`
#[inline]
pub(crate) unsafe fn new_with_id(
world: &'w mut World,
bundle_id: BundleId,
change_tick: Tick,
) -> Self {
let bundle_info = world.bundles.get_unchecked(bundle_id);
let (new_archetype_id, is_new_created) = bundle_info.insert_bundle_into_archetype(
&mut world.archetypes,
&mut world.storages,
&world.components,
&world.observers,
ArchetypeId::EMPTY,
);
let archetype = &mut world.archetypes[new_archetype_id];
let table = &mut world.storages.tables[archetype.table_id()];
let spawner = Self {
bundle_info: bundle_info.into(),
table: table.into(),
archetype: archetype.into(),
change_tick,
world: world.as_unsafe_world_cell(),
};
if is_new_created {
spawner
.world
.into_deferred()
.trigger(ArchetypeCreated(new_archetype_id));
}
spawner
}
#[inline]
pub fn reserve_storage(&mut self, additional: usize) {
// SAFETY: There are no outstanding world references
let (archetype, table) = unsafe { (self.archetype.as_mut(), self.table.as_mut()) };
archetype.reserve(additional);
table.reserve(additional);
}
/// # Safety
/// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
#[inline]
#[track_caller]
pub unsafe fn spawn_non_existent<T: DynamicBundle>(
&mut self,
entity: Entity,
bundle: T,
caller: MaybeLocation,
) -> (EntityLocation, T::Effect) {
// SAFETY: We do not make any structural changes to the archetype graph through self.world so these pointers always remain valid
let bundle_info = self.bundle_info.as_ref();
let (location, after_effect) = {
let table = self.table.as_mut();
let archetype = self.archetype.as_mut();
// SAFETY: Mutable references do not alias and will be dropped after this block
let (sparse_sets, entities) = {
let world = self.world.world_mut();
(&mut world.storages.sparse_sets, &mut world.entities)
};
let table_row = table.allocate(entity);
let location = archetype.allocate(entity, table_row);
let after_effect = bundle_info.write_components(
table,
sparse_sets,
&SpawnBundleStatus,
bundle_info.required_components.iter(),
entity,
table_row,
self.change_tick,
bundle,
InsertMode::Replace,
caller,
);
entities.set(entity.index(), Some(location));
entities.mark_spawn_despawn(entity.index(), caller, self.change_tick);
(location, after_effect)
};
// SAFETY: We have no outstanding mutable references to world as they were dropped
let mut deferred_world = unsafe { self.world.into_deferred() };
// SAFETY: `DeferredWorld` cannot provide mutable access to `Archetypes`.
let archetype = self.archetype.as_ref();
// SAFETY: All components in the bundle are guaranteed to exist in the World
// as they must be initialized before creating the BundleInfo.
unsafe {
deferred_world.trigger_on_add(
archetype,
entity,
bundle_info.iter_contributed_components(),
caller,
);
if archetype.has_add_observer() {
deferred_world.trigger_observers(
ADD,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
}
deferred_world.trigger_on_insert(
archetype,
entity,
bundle_info.iter_contributed_components(),
caller,
RelationshipHookMode::Run,
);
if archetype.has_insert_observer() {
deferred_world.trigger_observers(
INSERT,
Some(entity),
bundle_info.iter_contributed_components(),
caller,
);
}
};
(location, after_effect)
}
/// # Safety
/// `T` must match this [`BundleInfo`]'s type
#[inline]
pub unsafe fn spawn<T: Bundle>(
&mut self,
bundle: T,
caller: MaybeLocation,
) -> (Entity, T::Effect) {
let entity = self.entities().alloc();
// SAFETY: entity is allocated (but non-existent), `T` matches this BundleInfo's type
let (_, after_effect) = unsafe { self.spawn_non_existent(entity, bundle, caller) };
(entity, after_effect)
}
#[inline]
pub(crate) fn entities(&mut self) -> &mut Entities {
// SAFETY: No outstanding references to self.world, changes to entities cannot invalidate our internal pointers
unsafe { &mut self.world.world_mut().entities }
}
/// # Safety
/// - `Self` must be dropped after running this function as it may invalidate internal pointers.
#[inline]
pub(crate) unsafe fn flush_commands(&mut self) {
// SAFETY: pointers on self can be invalidated,
self.world.world_mut().flush();
}
}