Add BundleRemover
(#18521)
# Objective It has long been a todo item in the ecs to create a `BundleRemover` alongside the inserter, spawner, etc. This is an uncontroversial first step of #18514. ## Solution Move existing code from complex helper functions to one generalized `BundleRemover`. ## Testing Existing tests.
This commit is contained in:
parent
bea0a0a9bc
commit
f6543502b4
@ -20,7 +20,10 @@ use crate::{
|
|||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE},
|
world::{
|
||||||
|
unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REMOVE,
|
||||||
|
ON_REPLACE,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use alloc::{boxed::Box, vec, vec::Vec};
|
use alloc::{boxed::Box, vec, vec::Vec};
|
||||||
use bevy_platform::collections::{HashMap, HashSet};
|
use bevy_platform::collections::{HashMap, HashSet};
|
||||||
@ -1371,6 +1374,274 @@ impl<'w> BundleInserter<'w> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
|
||||||
|
pub(crate) struct BundleRemover<'w> {
|
||||||
|
world: UnsafeWorldCell<'w>,
|
||||||
|
bundle_info: ConstNonNull<BundleInfo>,
|
||||||
|
old_and_new_table: Option<(NonNull<Table>, NonNull<Table>)>,
|
||||||
|
old_archetype: NonNull<Archetype>,
|
||||||
|
new_archetype: NonNull<Archetype>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'w> BundleRemover<'w> {
|
||||||
|
/// Creates a new [`BundleRemover`], if such a remover would do anything.
|
||||||
|
///
|
||||||
|
/// If `require_all` is true, the [`BundleRemover`] is only created if the entire bundle is present on the archetype.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Caller must ensure that `archetype_id` is valid
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn new<T: Bundle>(
|
||||||
|
world: &'w mut World,
|
||||||
|
archetype_id: ArchetypeId,
|
||||||
|
require_all: bool,
|
||||||
|
) -> Option<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`, and caller ensures archetype is valid.
|
||||||
|
unsafe { Self::new_with_id(world, archetype_id, bundle_id, require_all) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new [`BundleRemover`], if such a remover would do anything.
|
||||||
|
///
|
||||||
|
/// If `require_all` is true, the [`BundleRemover`] is only created if the entire bundle is present on the archetype.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// Caller must ensure that `bundle_id` exists in `world.bundles` and `archetype_id` is valid.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn new_with_id(
|
||||||
|
world: &'w mut World,
|
||||||
|
archetype_id: ArchetypeId,
|
||||||
|
bundle_id: BundleId,
|
||||||
|
require_all: bool,
|
||||||
|
) -> Option<Self> {
|
||||||
|
let bundle_info = world.bundles.get_unchecked(bundle_id);
|
||||||
|
// SAFETY: Caller ensures archetype and bundle ids are correct.
|
||||||
|
let new_archetype_id = unsafe {
|
||||||
|
bundle_info.remove_bundle_from_archetype(
|
||||||
|
&mut world.archetypes,
|
||||||
|
&mut world.storages,
|
||||||
|
&world.components,
|
||||||
|
&world.observers,
|
||||||
|
archetype_id,
|
||||||
|
!require_all,
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
if new_archetype_id == archetype_id {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let (old_archetype, new_archetype) =
|
||||||
|
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
|
||||||
|
|
||||||
|
let tables = if old_archetype.table_id() == new_archetype.table_id() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let (old, new) = world
|
||||||
|
.storages
|
||||||
|
.tables
|
||||||
|
.get_2_mut(old_archetype.table_id(), new_archetype.table_id());
|
||||||
|
Some((old.into(), new.into()))
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Self {
|
||||||
|
bundle_info: bundle_info.into(),
|
||||||
|
new_archetype: new_archetype.into(),
|
||||||
|
old_archetype: old_archetype.into(),
|
||||||
|
old_and_new_table: tables,
|
||||||
|
world: world.as_unsafe_world_cell(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This can be passed to [`remove`](Self::remove) as the `pre_remove` function if you don't want to do anything before removing.
|
||||||
|
pub fn empty_pre_remove(
|
||||||
|
_: &mut SparseSets,
|
||||||
|
_: Option<&mut Table>,
|
||||||
|
_: &Components,
|
||||||
|
_: &[ComponentId],
|
||||||
|
) -> (bool, ()) {
|
||||||
|
(true, ())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs the removal.
|
||||||
|
///
|
||||||
|
/// `pre_remove` should return a bool for if the components still need to be dropped.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The `location` must have the same archetype as the remover.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn remove<T: 'static>(
|
||||||
|
&mut self,
|
||||||
|
entity: Entity,
|
||||||
|
location: EntityLocation,
|
||||||
|
caller: MaybeLocation,
|
||||||
|
pre_remove: impl FnOnce(
|
||||||
|
&mut SparseSets,
|
||||||
|
Option<&mut Table>,
|
||||||
|
&Components,
|
||||||
|
&[ComponentId],
|
||||||
|
) -> (bool, T),
|
||||||
|
) -> (EntityLocation, T) {
|
||||||
|
// Hooks
|
||||||
|
// SAFETY: all bundle components exist in World
|
||||||
|
unsafe {
|
||||||
|
// SAFETY: We only keep access to archetype/bundle data.
|
||||||
|
let mut deferred_world = self.world.into_deferred();
|
||||||
|
let bundle_components_in_archetype = || {
|
||||||
|
self.bundle_info
|
||||||
|
.as_ref()
|
||||||
|
.iter_explicit_components()
|
||||||
|
.filter(|component_id| self.old_archetype.as_ref().contains(*component_id))
|
||||||
|
};
|
||||||
|
if self.old_archetype.as_ref().has_replace_observer() {
|
||||||
|
deferred_world.trigger_observers(
|
||||||
|
ON_REPLACE,
|
||||||
|
entity,
|
||||||
|
bundle_components_in_archetype(),
|
||||||
|
caller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
deferred_world.trigger_on_replace(
|
||||||
|
self.old_archetype.as_ref(),
|
||||||
|
entity,
|
||||||
|
bundle_components_in_archetype(),
|
||||||
|
caller,
|
||||||
|
RelationshipHookMode::Run,
|
||||||
|
);
|
||||||
|
if self.old_archetype.as_ref().has_remove_observer() {
|
||||||
|
deferred_world.trigger_observers(
|
||||||
|
ON_REMOVE,
|
||||||
|
entity,
|
||||||
|
bundle_components_in_archetype(),
|
||||||
|
caller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
deferred_world.trigger_on_remove(
|
||||||
|
self.old_archetype.as_ref(),
|
||||||
|
entity,
|
||||||
|
bundle_components_in_archetype(),
|
||||||
|
caller,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// SAFETY: We still have the cell, so this is unique, it doesn't conflict with other references, and we drop it shortly.
|
||||||
|
let world = unsafe { self.world.world_mut() };
|
||||||
|
|
||||||
|
let (needs_drop, pre_remove_result) = pre_remove(
|
||||||
|
&mut world.storages.sparse_sets,
|
||||||
|
self.old_and_new_table
|
||||||
|
.as_ref()
|
||||||
|
// SAFETY: There is no conflicting access for this scope.
|
||||||
|
.map(|(old, _)| unsafe { &mut *old.as_ptr() }),
|
||||||
|
&world.components,
|
||||||
|
self.bundle_info.as_ref().explicit_components(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Handle sparse set removes
|
||||||
|
for component_id in self.bundle_info.as_ref().iter_explicit_components() {
|
||||||
|
if self.old_archetype.as_ref().contains(component_id) {
|
||||||
|
world.removed_components.send(component_id, entity);
|
||||||
|
|
||||||
|
// Make sure to drop components stored in sparse sets.
|
||||||
|
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
|
||||||
|
if let Some(StorageType::SparseSet) =
|
||||||
|
self.old_archetype.as_ref().get_storage_type(component_id)
|
||||||
|
{
|
||||||
|
world
|
||||||
|
.storages
|
||||||
|
.sparse_sets
|
||||||
|
.get_mut(component_id)
|
||||||
|
// Set exists because the component existed on the entity
|
||||||
|
.unwrap()
|
||||||
|
// If it was already forgotten, it would not be in the set.
|
||||||
|
.remove(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle archetype change
|
||||||
|
let remove_result = self
|
||||||
|
.old_archetype
|
||||||
|
.as_mut()
|
||||||
|
.swap_remove(location.archetype_row);
|
||||||
|
// if an entity was moved into this entity's archetype row, update its archetype row
|
||||||
|
if let Some(swapped_entity) = remove_result.swapped_entity {
|
||||||
|
let swapped_location = world.entities.get(swapped_entity).unwrap();
|
||||||
|
|
||||||
|
world.entities.set(
|
||||||
|
swapped_entity.index(),
|
||||||
|
EntityLocation {
|
||||||
|
archetype_id: swapped_location.archetype_id,
|
||||||
|
archetype_row: location.archetype_row,
|
||||||
|
table_id: swapped_location.table_id,
|
||||||
|
table_row: swapped_location.table_row,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle table change
|
||||||
|
let new_location = if let Some((mut old_table, mut new_table)) = self.old_and_new_table {
|
||||||
|
let move_result = if needs_drop {
|
||||||
|
// SAFETY: old_table_row exists
|
||||||
|
unsafe {
|
||||||
|
old_table
|
||||||
|
.as_mut()
|
||||||
|
.move_to_and_drop_missing_unchecked(location.table_row, new_table.as_mut())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// SAFETY: old_table_row exists
|
||||||
|
unsafe {
|
||||||
|
old_table.as_mut().move_to_and_forget_missing_unchecked(
|
||||||
|
location.table_row,
|
||||||
|
new_table.as_mut(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: move_result.new_row is a valid position in new_archetype's table
|
||||||
|
let new_location = unsafe {
|
||||||
|
self.new_archetype
|
||||||
|
.as_mut()
|
||||||
|
.allocate(entity, move_result.new_row)
|
||||||
|
};
|
||||||
|
|
||||||
|
// if an entity was moved into this entity's table row, update its table row
|
||||||
|
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||||
|
let swapped_location = world.entities.get(swapped_entity).unwrap();
|
||||||
|
|
||||||
|
world.entities.set(
|
||||||
|
swapped_entity.index(),
|
||||||
|
EntityLocation {
|
||||||
|
archetype_id: swapped_location.archetype_id,
|
||||||
|
archetype_row: swapped_location.archetype_row,
|
||||||
|
table_id: swapped_location.table_id,
|
||||||
|
table_row: location.table_row,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
world.archetypes[swapped_location.archetype_id]
|
||||||
|
.set_entity_table_row(swapped_location.archetype_row, location.table_row);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_location
|
||||||
|
} else {
|
||||||
|
// The tables are the same
|
||||||
|
self.new_archetype
|
||||||
|
.as_mut()
|
||||||
|
.allocate(entity, location.table_row)
|
||||||
|
};
|
||||||
|
|
||||||
|
// SAFETY: The entity is valid and has been moved to the new location already.
|
||||||
|
unsafe {
|
||||||
|
world.entities.set(entity.index(), new_location);
|
||||||
|
}
|
||||||
|
|
||||||
|
(new_location, pre_remove_result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
|
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
|
||||||
pub(crate) struct BundleSpawner<'w> {
|
pub(crate) struct BundleSpawner<'w> {
|
||||||
world: UnsafeWorldCell<'w>,
|
world: UnsafeWorldCell<'w>,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
archetype::{Archetype, ArchetypeId, Archetypes},
|
archetype::{Archetype, ArchetypeId},
|
||||||
bundle::{
|
bundle::{
|
||||||
Bundle, BundleEffect, BundleFromComponents, BundleId, BundleInfo, BundleInserter,
|
Bundle, BundleEffect, BundleFromComponents, BundleInserter, BundleRemover, DynamicBundle,
|
||||||
DynamicBundle, InsertMode,
|
InsertMode,
|
||||||
},
|
},
|
||||||
change_detection::{MaybeLocation, MutUntyped},
|
change_detection::{MaybeLocation, MutUntyped},
|
||||||
component::{
|
component::{
|
||||||
@ -10,20 +10,17 @@ use crate::{
|
|||||||
StorageType,
|
StorageType,
|
||||||
},
|
},
|
||||||
entity::{
|
entity::{
|
||||||
ContainsEntity, Entities, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent,
|
ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent, EntityLocation,
|
||||||
EntityLocation,
|
|
||||||
},
|
},
|
||||||
event::Event,
|
event::Event,
|
||||||
observer::Observer,
|
observer::Observer,
|
||||||
query::{Access, ReadOnlyQueryData},
|
query::{Access, DebugCheckedUnwrap, ReadOnlyQueryData},
|
||||||
relationship::RelationshipHookMode,
|
relationship::RelationshipHookMode,
|
||||||
removal_detection::RemovedComponentEvents,
|
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
storage::Storages,
|
|
||||||
system::IntoObserverSystem,
|
system::IntoObserverSystem,
|
||||||
world::{
|
world::{
|
||||||
error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, DeferredWorld, Mut, Ref,
|
error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, Mut, Ref, World,
|
||||||
World, ON_DESPAWN, ON_REMOVE, ON_REPLACE,
|
ON_DESPAWN, ON_REMOVE, ON_REPLACE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
@ -1978,281 +1975,57 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||||
// TODO: BundleRemover?
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn take<T: Bundle + BundleFromComponents>(&mut self) -> Option<T> {
|
pub fn take<T: Bundle + BundleFromComponents>(&mut self) -> Option<T> {
|
||||||
self.assert_not_despawned();
|
self.assert_not_despawned();
|
||||||
let world = &mut self.world;
|
|
||||||
let storages = &mut world.storages;
|
|
||||||
// SAFETY: These come from the same world.
|
|
||||||
let mut registrator =
|
|
||||||
unsafe { ComponentsRegistrator::new(&mut world.components, &mut world.component_ids) };
|
|
||||||
let bundle_id = world.bundles.register_info::<T>(&mut registrator, storages);
|
|
||||||
// SAFETY: We just ensured this bundle exists
|
|
||||||
let bundle_info = unsafe { world.bundles.get_unchecked(bundle_id) };
|
|
||||||
let old_location = self.location;
|
|
||||||
// SAFETY: `archetype_id` exists because it is referenced in the old `EntityLocation` which is valid,
|
|
||||||
// components exist in `bundle_info` because `Bundles::init_info` initializes a `BundleInfo` containing all components of the bundle type `T`
|
|
||||||
let new_archetype_id = unsafe {
|
|
||||||
bundle_info.remove_bundle_from_archetype(
|
|
||||||
&mut world.archetypes,
|
|
||||||
storages,
|
|
||||||
®istrator,
|
|
||||||
&world.observers,
|
|
||||||
old_location.archetype_id,
|
|
||||||
false,
|
|
||||||
)?
|
|
||||||
};
|
|
||||||
|
|
||||||
if new_archetype_id == old_location.archetype_id {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let entity = self.entity;
|
let entity = self.entity;
|
||||||
// SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld
|
let location = self.location;
|
||||||
let (old_archetype, bundle_info, mut deferred_world) = unsafe {
|
|
||||||
let bundle_info: *const BundleInfo = bundle_info;
|
let mut remover =
|
||||||
let world = world.as_unsafe_world_cell();
|
// SAFETY: The archetype id must be valid since this entity is in it.
|
||||||
(
|
unsafe { BundleRemover::new::<T>(self.world, self.location.archetype_id, true) }?;
|
||||||
&world.archetypes()[old_location.archetype_id],
|
// SAFETY: The passed location has the sane archetype as the remover, since they came from the same location.
|
||||||
&*bundle_info,
|
let (new_location, result) = unsafe {
|
||||||
world.into_deferred(),
|
remover.remove(
|
||||||
|
entity,
|
||||||
|
location,
|
||||||
|
MaybeLocation::caller(),
|
||||||
|
|sets, table, components, bundle_components| {
|
||||||
|
let mut bundle_components = bundle_components.iter().copied();
|
||||||
|
(
|
||||||
|
false,
|
||||||
|
T::from_components(&mut (sets, table), &mut |(sets, table)| {
|
||||||
|
let component_id = bundle_components.next().unwrap();
|
||||||
|
// SAFETY: the component existed to be removed, so its id must be valid.
|
||||||
|
let component_info = components.get_info_unchecked(component_id);
|
||||||
|
match component_info.storage_type() {
|
||||||
|
StorageType::Table => {
|
||||||
|
table
|
||||||
|
.as_mut()
|
||||||
|
// SAFETY: The table must be valid if the component is in it.
|
||||||
|
.debug_checked_unwrap()
|
||||||
|
// SAFETY: The remover is cleaning this up.
|
||||||
|
.take_component(component_id, location.table_row)
|
||||||
|
}
|
||||||
|
StorageType::SparseSet => sets
|
||||||
|
.get_mut(component_id)
|
||||||
|
.unwrap()
|
||||||
|
.remove_and_forget(entity)
|
||||||
|
.unwrap(),
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
self.location = new_location;
|
||||||
|
|
||||||
// SAFETY: all bundle components exist in World
|
|
||||||
unsafe {
|
|
||||||
trigger_on_replace_and_on_remove_hooks_and_observers(
|
|
||||||
&mut deferred_world,
|
|
||||||
old_archetype,
|
|
||||||
entity,
|
|
||||||
bundle_info,
|
|
||||||
MaybeLocation::caller(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let archetypes = &mut world.archetypes;
|
|
||||||
let storages = &mut world.storages;
|
|
||||||
let components = &mut world.components;
|
|
||||||
let entities = &mut world.entities;
|
|
||||||
let removed_components = &mut world.removed_components;
|
|
||||||
|
|
||||||
let entity = self.entity;
|
|
||||||
let mut bundle_components = bundle_info.iter_explicit_components();
|
|
||||||
// SAFETY: bundle components are iterated in order, which guarantees that the component type
|
|
||||||
// matches
|
|
||||||
let result = unsafe {
|
|
||||||
T::from_components(storages, &mut |storages| {
|
|
||||||
let component_id = bundle_components.next().unwrap();
|
|
||||||
// SAFETY:
|
|
||||||
// - entity location is valid
|
|
||||||
// - table row is removed below, without dropping the contents
|
|
||||||
// - `components` comes from the same world as `storages`
|
|
||||||
// - the component exists on the entity
|
|
||||||
take_component(
|
|
||||||
storages,
|
|
||||||
components,
|
|
||||||
removed_components,
|
|
||||||
component_id,
|
|
||||||
entity,
|
|
||||||
old_location,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
#[expect(
|
|
||||||
clippy::undocumented_unsafe_blocks,
|
|
||||||
reason = "Needs to be documented; see #17345."
|
|
||||||
)]
|
|
||||||
unsafe {
|
|
||||||
Self::move_entity_from_remove::<false>(
|
|
||||||
entity,
|
|
||||||
&mut self.location,
|
|
||||||
old_location.archetype_id,
|
|
||||||
old_location,
|
|
||||||
entities,
|
|
||||||
archetypes,
|
|
||||||
storages,
|
|
||||||
new_archetype_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
Some(result)
|
Some(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
///
|
|
||||||
/// `new_archetype_id` must have the same or a subset of the components
|
|
||||||
/// in `old_archetype_id`. Probably more safety stuff too, audit a call to
|
|
||||||
/// this fn as if the code here was written inline
|
|
||||||
///
|
|
||||||
/// when DROP is true removed components will be dropped otherwise they will be forgotten
|
|
||||||
// We use a const generic here so that we are less reliant on
|
|
||||||
// inlining for rustc to optimize out the `match DROP`
|
|
||||||
unsafe fn move_entity_from_remove<const DROP: bool>(
|
|
||||||
entity: Entity,
|
|
||||||
self_location: &mut EntityLocation,
|
|
||||||
old_archetype_id: ArchetypeId,
|
|
||||||
old_location: EntityLocation,
|
|
||||||
entities: &mut Entities,
|
|
||||||
archetypes: &mut Archetypes,
|
|
||||||
storages: &mut Storages,
|
|
||||||
new_archetype_id: ArchetypeId,
|
|
||||||
) {
|
|
||||||
let old_archetype = &mut archetypes[old_archetype_id];
|
|
||||||
let remove_result = old_archetype.swap_remove(old_location.archetype_row);
|
|
||||||
// if an entity was moved into this entity's archetype row, update its archetype row
|
|
||||||
if let Some(swapped_entity) = remove_result.swapped_entity {
|
|
||||||
let swapped_location = entities.get(swapped_entity).unwrap();
|
|
||||||
|
|
||||||
entities.set(
|
|
||||||
swapped_entity.index(),
|
|
||||||
EntityLocation {
|
|
||||||
archetype_id: swapped_location.archetype_id,
|
|
||||||
archetype_row: old_location.archetype_row,
|
|
||||||
table_id: swapped_location.table_id,
|
|
||||||
table_row: swapped_location.table_row,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let old_table_row = remove_result.table_row;
|
|
||||||
let old_table_id = old_archetype.table_id();
|
|
||||||
let new_archetype = &mut archetypes[new_archetype_id];
|
|
||||||
|
|
||||||
let new_location = if old_table_id == new_archetype.table_id() {
|
|
||||||
new_archetype.allocate(entity, old_table_row)
|
|
||||||
} else {
|
|
||||||
let (old_table, new_table) = storages
|
|
||||||
.tables
|
|
||||||
.get_2_mut(old_table_id, new_archetype.table_id());
|
|
||||||
|
|
||||||
let move_result = if DROP {
|
|
||||||
// SAFETY: old_table_row exists
|
|
||||||
unsafe { old_table.move_to_and_drop_missing_unchecked(old_table_row, new_table) }
|
|
||||||
} else {
|
|
||||||
// SAFETY: old_table_row exists
|
|
||||||
unsafe { old_table.move_to_and_forget_missing_unchecked(old_table_row, new_table) }
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: move_result.new_row is a valid position in new_archetype's table
|
|
||||||
let new_location = unsafe { new_archetype.allocate(entity, move_result.new_row) };
|
|
||||||
|
|
||||||
// if an entity was moved into this entity's table row, update its table row
|
|
||||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
|
||||||
let swapped_location = entities.get(swapped_entity).unwrap();
|
|
||||||
|
|
||||||
entities.set(
|
|
||||||
swapped_entity.index(),
|
|
||||||
EntityLocation {
|
|
||||||
archetype_id: swapped_location.archetype_id,
|
|
||||||
archetype_row: swapped_location.archetype_row,
|
|
||||||
table_id: swapped_location.table_id,
|
|
||||||
table_row: old_location.table_row,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
archetypes[swapped_location.archetype_id]
|
|
||||||
.set_entity_table_row(swapped_location.archetype_row, old_table_row);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_location
|
|
||||||
};
|
|
||||||
|
|
||||||
*self_location = new_location;
|
|
||||||
// SAFETY: The entity is valid and has been moved to the new location already.
|
|
||||||
unsafe {
|
|
||||||
entities.set(entity.index(), new_location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the components of `bundle` from `entity`.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - A `BundleInfo` with the corresponding `BundleId` must have been initialized.
|
|
||||||
unsafe fn remove_bundle(&mut self, bundle: BundleId, caller: MaybeLocation) -> EntityLocation {
|
|
||||||
let entity = self.entity;
|
|
||||||
let world = &mut self.world;
|
|
||||||
let location = self.location;
|
|
||||||
// SAFETY: the caller guarantees that the BundleInfo for this id has been initialized.
|
|
||||||
let bundle_info = world.bundles.get_unchecked(bundle);
|
|
||||||
|
|
||||||
// SAFETY: `archetype_id` exists because it is referenced in `location` which is valid
|
|
||||||
// and components in `bundle_info` must exist due to this function's safety invariants.
|
|
||||||
let new_archetype_id = bundle_info
|
|
||||||
.remove_bundle_from_archetype(
|
|
||||||
&mut world.archetypes,
|
|
||||||
&mut world.storages,
|
|
||||||
&world.components,
|
|
||||||
&world.observers,
|
|
||||||
location.archetype_id,
|
|
||||||
// components from the bundle that are not present on the entity are ignored
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
.expect("intersections should always return a result");
|
|
||||||
|
|
||||||
if new_archetype_id == location.archetype_id {
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: Archetypes and Bundles cannot be mutably aliased through DeferredWorld
|
|
||||||
let (old_archetype, bundle_info, mut deferred_world) = unsafe {
|
|
||||||
let bundle_info: *const BundleInfo = bundle_info;
|
|
||||||
let world = world.as_unsafe_world_cell();
|
|
||||||
(
|
|
||||||
&world.archetypes()[location.archetype_id],
|
|
||||||
&*bundle_info,
|
|
||||||
world.into_deferred(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
// SAFETY: all bundle components exist in World
|
|
||||||
unsafe {
|
|
||||||
trigger_on_replace_and_on_remove_hooks_and_observers(
|
|
||||||
&mut deferred_world,
|
|
||||||
old_archetype,
|
|
||||||
entity,
|
|
||||||
bundle_info,
|
|
||||||
caller,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let old_archetype = &world.archetypes[location.archetype_id];
|
|
||||||
for component_id in bundle_info.iter_explicit_components() {
|
|
||||||
if old_archetype.contains(component_id) {
|
|
||||||
world.removed_components.send(component_id, entity);
|
|
||||||
|
|
||||||
// Make sure to drop components stored in sparse sets.
|
|
||||||
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
|
|
||||||
if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) {
|
|
||||||
world
|
|
||||||
.storages
|
|
||||||
.sparse_sets
|
|
||||||
.get_mut(component_id)
|
|
||||||
// Set exists because the component existed on the entity
|
|
||||||
.unwrap()
|
|
||||||
.remove(entity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SAFETY: `new_archetype_id` is a subset of the components in `old_location.archetype_id`
|
|
||||||
// because it is created by removing a bundle from these components.
|
|
||||||
let mut new_location = location;
|
|
||||||
Self::move_entity_from_remove::<true>(
|
|
||||||
entity,
|
|
||||||
&mut new_location,
|
|
||||||
location.archetype_id,
|
|
||||||
location,
|
|
||||||
&mut world.entities,
|
|
||||||
&mut world.archetypes,
|
|
||||||
&mut world.storages,
|
|
||||||
new_archetype_id,
|
|
||||||
);
|
|
||||||
|
|
||||||
new_location
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes any components in the [`Bundle`] from the entity.
|
/// Removes any components in the [`Bundle`] from the entity.
|
||||||
///
|
///
|
||||||
/// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details.
|
/// See [`EntityCommands::remove`](crate::system::EntityCommands::remove) for more details.
|
||||||
@ -2260,7 +2033,6 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||||
// TODO: BundleRemover?
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
|
pub fn remove<T: Bundle>(&mut self) -> &mut Self {
|
||||||
self.remove_with_caller::<T>(MaybeLocation::caller())
|
self.remove_with_caller::<T>(MaybeLocation::caller())
|
||||||
@ -2269,18 +2041,25 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn remove_with_caller<T: Bundle>(&mut self, caller: MaybeLocation) -> &mut Self {
|
pub(crate) fn remove_with_caller<T: Bundle>(&mut self, caller: MaybeLocation) -> &mut Self {
|
||||||
self.assert_not_despawned();
|
self.assert_not_despawned();
|
||||||
let storages = &mut self.world.storages;
|
|
||||||
// SAFETY: These come from the same world.
|
|
||||||
let mut registrator = unsafe {
|
|
||||||
ComponentsRegistrator::new(&mut self.world.components, &mut self.world.component_ids)
|
|
||||||
};
|
|
||||||
let bundle_info = self
|
|
||||||
.world
|
|
||||||
.bundles
|
|
||||||
.register_info::<T>(&mut registrator, storages);
|
|
||||||
|
|
||||||
// SAFETY: the `BundleInfo` is initialized above
|
let Some(mut remover) =
|
||||||
self.location = unsafe { self.remove_bundle(bundle_info, caller) };
|
// SAFETY: The archetype id must be valid since this entity is in it.
|
||||||
|
(unsafe { BundleRemover::new::<T>(self.world, self.location.archetype_id, false) })
|
||||||
|
else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
|
let new_location = unsafe {
|
||||||
|
remover.remove(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
caller,
|
||||||
|
BundleRemover::empty_pre_remove,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.0;
|
||||||
|
|
||||||
|
self.location = new_location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
self
|
self
|
||||||
@ -2302,16 +2081,31 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.assert_not_despawned();
|
self.assert_not_despawned();
|
||||||
let storages = &mut self.world.storages;
|
let storages = &mut self.world.storages;
|
||||||
|
let bundles = &mut self.world.bundles;
|
||||||
// SAFETY: These come from the same world.
|
// SAFETY: These come from the same world.
|
||||||
let mut registrator = unsafe {
|
let mut registrator = unsafe {
|
||||||
ComponentsRegistrator::new(&mut self.world.components, &mut self.world.component_ids)
|
ComponentsRegistrator::new(&mut self.world.components, &mut self.world.component_ids)
|
||||||
};
|
};
|
||||||
let bundles = &mut self.world.bundles;
|
|
||||||
|
|
||||||
let bundle_id = bundles.register_contributed_bundle_info::<T>(&mut registrator, storages);
|
let bundle_id = bundles.register_contributed_bundle_info::<T>(&mut registrator, storages);
|
||||||
|
|
||||||
// SAFETY: the dynamic `BundleInfo` is initialized above
|
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
|
||||||
self.location = unsafe { self.remove_bundle(bundle_id, caller) };
|
let Some(mut remover) = (unsafe {
|
||||||
|
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false)
|
||||||
|
}) else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
|
let new_location = unsafe {
|
||||||
|
remover.remove(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
caller,
|
||||||
|
BundleRemover::empty_pre_remove,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.0;
|
||||||
|
|
||||||
|
self.location = new_location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
self
|
self
|
||||||
@ -2358,8 +2152,24 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
.bundles
|
.bundles
|
||||||
.init_dynamic_info(&mut self.world.storages, ®istrator, to_remove);
|
.init_dynamic_info(&mut self.world.storages, ®istrator, to_remove);
|
||||||
|
|
||||||
// SAFETY: the `BundleInfo` for the components to remove is initialized above
|
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
|
||||||
self.location = unsafe { self.remove_bundle(remove_bundle, caller) };
|
let Some(mut remover) = (unsafe {
|
||||||
|
BundleRemover::new_with_id(self.world, self.location.archetype_id, remove_bundle, false)
|
||||||
|
}) else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
|
let new_location = unsafe {
|
||||||
|
remover.remove(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
caller,
|
||||||
|
BundleRemover::empty_pre_remove,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.0;
|
||||||
|
|
||||||
|
self.location = new_location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
self
|
self
|
||||||
@ -2393,8 +2203,24 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
component_id,
|
component_id,
|
||||||
);
|
);
|
||||||
|
|
||||||
// SAFETY: the `BundleInfo` for this `component_id` is initialized above
|
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
|
||||||
self.location = unsafe { self.remove_bundle(bundle_id, caller) };
|
let Some(mut remover) = (unsafe {
|
||||||
|
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false)
|
||||||
|
}) else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
|
let new_location = unsafe {
|
||||||
|
remover.remove(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
caller,
|
||||||
|
BundleRemover::empty_pre_remove,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.0;
|
||||||
|
|
||||||
|
self.location = new_location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
self
|
self
|
||||||
@ -2419,9 +2245,24 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
component_ids,
|
component_ids,
|
||||||
);
|
);
|
||||||
|
|
||||||
// SAFETY: the `BundleInfo` for this `bundle_id` is initialized above
|
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
|
||||||
unsafe { self.remove_bundle(bundle_id, MaybeLocation::caller()) };
|
let Some(mut remover) = (unsafe {
|
||||||
|
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false)
|
||||||
|
}) else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
|
let new_location = unsafe {
|
||||||
|
remover.remove(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
MaybeLocation::caller(),
|
||||||
|
BundleRemover::empty_pre_remove,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.0;
|
||||||
|
|
||||||
|
self.location = new_location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
self
|
self
|
||||||
@ -2449,8 +2290,24 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
component_ids.as_slice(),
|
component_ids.as_slice(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// SAFETY: the `BundleInfo` for this `component_id` is initialized above
|
// SAFETY: We just created the bundle, and the archetype is valid, since we are in it.
|
||||||
self.location = unsafe { self.remove_bundle(bundle_id, caller) };
|
let Some(mut remover) = (unsafe {
|
||||||
|
BundleRemover::new_with_id(self.world, self.location.archetype_id, bundle_id, false)
|
||||||
|
}) else {
|
||||||
|
return self;
|
||||||
|
};
|
||||||
|
// SAFETY: The remover archetype came from the passed location and the removal can not fail.
|
||||||
|
let new_location = unsafe {
|
||||||
|
remover.remove(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
caller,
|
||||||
|
BundleRemover::empty_pre_remove,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.0;
|
||||||
|
|
||||||
|
self.location = new_location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
self
|
self
|
||||||
@ -2956,46 +2813,6 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
/// All components in the archetype must exist in world
|
|
||||||
unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers(
|
|
||||||
deferred_world: &mut DeferredWorld,
|
|
||||||
archetype: &Archetype,
|
|
||||||
entity: Entity,
|
|
||||||
bundle_info: &BundleInfo,
|
|
||||||
caller: MaybeLocation,
|
|
||||||
) {
|
|
||||||
let bundle_components_in_archetype = || {
|
|
||||||
bundle_info
|
|
||||||
.iter_explicit_components()
|
|
||||||
.filter(|component_id| archetype.contains(*component_id))
|
|
||||||
};
|
|
||||||
if archetype.has_replace_observer() {
|
|
||||||
deferred_world.trigger_observers(
|
|
||||||
ON_REPLACE,
|
|
||||||
entity,
|
|
||||||
bundle_components_in_archetype(),
|
|
||||||
caller,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
deferred_world.trigger_on_replace(
|
|
||||||
archetype,
|
|
||||||
entity,
|
|
||||||
bundle_components_in_archetype(),
|
|
||||||
caller,
|
|
||||||
RelationshipHookMode::Run,
|
|
||||||
);
|
|
||||||
if archetype.has_remove_observer() {
|
|
||||||
deferred_world.trigger_observers(
|
|
||||||
ON_REMOVE,
|
|
||||||
entity,
|
|
||||||
bundle_components_in_archetype(),
|
|
||||||
caller,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
deferred_world.trigger_on_remove(archetype, entity, bundle_components_in_archetype(), caller);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A view into a single entity and component in a world, which may either be vacant or occupied.
|
/// A view into a single entity and component in a world, which may either be vacant or occupied.
|
||||||
///
|
///
|
||||||
/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`].
|
/// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`].
|
||||||
@ -4443,7 +4260,7 @@ where
|
|||||||
/// # Safety
|
/// # Safety
|
||||||
///
|
///
|
||||||
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
|
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
|
||||||
/// [`BundleInfo`] used to construct [`BundleInserter`]
|
/// [`BundleInfo`](crate::bundle::BundleInfo) used to construct [`BundleInserter`]
|
||||||
/// - [`Entity`] must correspond to [`EntityLocation`]
|
/// - [`Entity`] must correspond to [`EntityLocation`]
|
||||||
unsafe fn insert_dynamic_bundle<
|
unsafe fn insert_dynamic_bundle<
|
||||||
'a,
|
'a,
|
||||||
@ -4491,50 +4308,6 @@ unsafe fn insert_dynamic_bundle<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves component data out of storage.
|
|
||||||
///
|
|
||||||
/// This function leaves the underlying memory unchanged, but the component behind
|
|
||||||
/// returned pointer is semantically owned by the caller and will not be dropped in its original location.
|
|
||||||
/// Caller is responsible to drop component data behind returned pointer.
|
|
||||||
///
|
|
||||||
/// # Safety
|
|
||||||
/// - `location.table_row` must be in bounds of column of component id `component_id`
|
|
||||||
/// - `component_id` must be valid
|
|
||||||
/// - `components` must come from the same world as `self`
|
|
||||||
/// - The relevant table row **must be removed** by the caller once all components are taken, without dropping the value
|
|
||||||
///
|
|
||||||
/// # Panics
|
|
||||||
/// Panics if the entity did not have the component.
|
|
||||||
#[inline]
|
|
||||||
pub(crate) unsafe fn take_component<'a>(
|
|
||||||
storages: &'a mut Storages,
|
|
||||||
components: &Components,
|
|
||||||
removed_components: &mut RemovedComponentEvents,
|
|
||||||
component_id: ComponentId,
|
|
||||||
entity: Entity,
|
|
||||||
location: EntityLocation,
|
|
||||||
) -> OwningPtr<'a> {
|
|
||||||
// SAFETY: caller promises component_id to be valid
|
|
||||||
let component_info = unsafe { components.get_info_unchecked(component_id) };
|
|
||||||
removed_components.send(component_id, entity);
|
|
||||||
match component_info.storage_type() {
|
|
||||||
StorageType::Table => {
|
|
||||||
let table = &mut storages.tables[location.table_id];
|
|
||||||
// SAFETY:
|
|
||||||
// - archetypes only store valid table_rows
|
|
||||||
// - index is in bounds as promised by caller
|
|
||||||
// - promote is safe because the caller promises to remove the table row without dropping it immediately afterwards
|
|
||||||
unsafe { table.take_component(component_id, location.table_row) }
|
|
||||||
}
|
|
||||||
StorageType::SparseSet => storages
|
|
||||||
.sparse_sets
|
|
||||||
.get_mut(component_id)
|
|
||||||
.unwrap()
|
|
||||||
.remove_and_forget(entity)
|
|
||||||
.unwrap(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Types that can be used to fetch components from an entity dynamically by
|
/// Types that can be used to fetch components from an entity dynamically by
|
||||||
/// [`ComponentId`]s.
|
/// [`ComponentId`]s.
|
||||||
///
|
///
|
||||||
|
Loading…
Reference in New Issue
Block a user