diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index 5b9cefbdf0..40ffad37e3 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -4,6 +4,7 @@ mod impls; mod insert; +mod remove; #[cfg(test)] mod tests; @@ -62,8 +63,8 @@ pub use bevy_ecs_macros::Bundle; use crate::{ archetype::{ - Archetype, ArchetypeCreated, ArchetypeId, Archetypes, BundleComponentStatus, - ComponentStatus, SpawnBundleStatus, + Archetype, ArchetypeCreated, ArchetypeId, BundleComponentStatus, ComponentStatus, + SpawnBundleStatus, }, change_detection::MaybeLocation, component::{ @@ -71,8 +72,7 @@ use crate::{ RequiredComponents, StorageType, Tick, }, entity::{Entities, Entity, EntityLocation}, - lifecycle::{ADD, INSERT, REMOVE, REPLACE}, - observer::Observers, + lifecycle::{ADD, INSERT}, prelude::World, query::DebugCheckedUnwrap, relationship::RelationshipHookMode, @@ -591,118 +591,6 @@ impl BundleInfo { } } } - - /// Removes a bundle from the given archetype and returns the resulting archetype and whether a new archetype was created. - /// (or `None` if the removal was invalid). - /// This could be the same [`ArchetypeId`], in the event that removing the given bundle - /// does not result in an [`Archetype`] change. - /// - /// Results are cached in the [`Archetype`] graph to avoid redundant work. - /// - /// If `intersection` is false, attempting to remove a bundle with components not contained in the - /// current archetype will fail, returning `None`. - /// - /// If `intersection` is true, components in the bundle but not in the current archetype - /// will be ignored. - /// - /// # Safety - /// `archetype_id` must exist and components in `bundle_info` must exist - pub(crate) unsafe fn remove_bundle_from_archetype( - &self, - archetypes: &mut Archetypes, - storages: &mut Storages, - components: &Components, - observers: &Observers, - archetype_id: ArchetypeId, - intersection: bool, - ) -> (Option, bool) { - // Check the archetype graph to see if the bundle has been - // removed from this archetype in the past. - let archetype_after_remove_result = { - let edges = archetypes[archetype_id].edges(); - if intersection { - edges.get_archetype_after_bundle_remove(self.id()) - } else { - edges.get_archetype_after_bundle_take(self.id()) - } - }; - let (result, is_new_created) = if let Some(result) = archetype_after_remove_result { - // This bundle removal result is cached. Just return that! - (result, false) - } else { - let mut next_table_components; - let mut next_sparse_set_components; - let next_table_id; - { - let current_archetype = &mut archetypes[archetype_id]; - let mut removed_table_components = Vec::new(); - let mut removed_sparse_set_components = Vec::new(); - for component_id in self.iter_explicit_components() { - if current_archetype.contains(component_id) { - // SAFETY: bundle components were already initialized by bundles.get_info - let component_info = unsafe { components.get_info_unchecked(component_id) }; - match component_info.storage_type() { - StorageType::Table => removed_table_components.push(component_id), - StorageType::SparseSet => { - removed_sparse_set_components.push(component_id); - } - } - } else if !intersection { - // A component in the bundle was not present in the entity's archetype, so this - // removal is invalid. Cache the result in the archetype graph. - current_archetype - .edges_mut() - .cache_archetype_after_bundle_take(self.id(), None); - return (None, false); - } - } - - // Sort removed components so we can do an efficient "sorted remove". - // Archetype components are already sorted. - removed_table_components.sort_unstable(); - removed_sparse_set_components.sort_unstable(); - next_table_components = current_archetype.table_components().collect(); - next_sparse_set_components = current_archetype.sparse_set_components().collect(); - sorted_remove(&mut next_table_components, &removed_table_components); - sorted_remove( - &mut next_sparse_set_components, - &removed_sparse_set_components, - ); - - next_table_id = if removed_table_components.is_empty() { - current_archetype.table_id() - } else { - // SAFETY: all components in next_table_components exist - unsafe { - storages - .tables - .get_id_or_insert(&next_table_components, components) - } - }; - } - - let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert( - components, - observers, - next_table_id, - next_table_components, - next_sparse_set_components, - ); - (Some(new_archetype_id), is_new_created) - }; - let current_archetype = &mut archetypes[archetype_id]; - // Cache the result in an edge. - if intersection { - current_archetype - .edges_mut() - .cache_archetype_after_bundle_remove(self.id(), result); - } else { - current_archetype - .edges_mut() - .cache_archetype_after_bundle_take(self.id(), result); - } - (result, is_new_created) - } } /// The type of archetype move (or lack thereof) that will result from a bundle @@ -722,284 +610,6 @@ pub(crate) enum ArchetypeMoveType { }, } -// 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, - old_and_new_table: Option<(NonNull, NonNull
)>, - old_archetype: NonNull, - new_archetype: NonNull, -} - -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( - world: &'w mut World, - archetype_id: ArchetypeId, - require_all: bool, - ) -> Option { - // 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::(&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 { - let bundle_info = world.bundles.get_unchecked(bundle_id); - // SAFETY: Caller ensures archetype and bundle ids are correct. - let (new_archetype_id, is_new_created) = unsafe { - bundle_info.remove_bundle_from_archetype( - &mut world.archetypes, - &mut world.storages, - &world.components, - &world.observers, - archetype_id, - !require_all, - ) - }; - let new_archetype_id = new_archetype_id?; - - 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())) - }; - - let remover = 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(), - }; - if is_new_created { - remover - .world - .into_deferred() - .trigger(ArchetypeCreated(new_archetype_id)); - } - Some(remover) - } - - /// 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( - &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( - REPLACE, - Some(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( - REMOVE, - Some(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.write(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(), - Some(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(), - Some(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(), Some(new_location)); - } - - (new_location, pre_remove_result) - } -} - // SAFETY: We have exclusive world access so our pointers can't be invalidated externally pub(crate) struct BundleSpawner<'w> { world: UnsafeWorldCell<'w>, @@ -1391,18 +1001,3 @@ fn initialize_dynamic_bundle( (id, storage_types) } - -fn sorted_remove(source: &mut Vec, remove: &[T]) { - let mut remove_index = 0; - source.retain(|value| { - while remove_index < remove.len() && *value > remove[remove_index] { - remove_index += 1; - } - - if remove_index < remove.len() { - *value != remove[remove_index] - } else { - true - } - }); -} diff --git a/crates/bevy_ecs/src/bundle/remove.rs b/crates/bevy_ecs/src/bundle/remove.rs new file mode 100644 index 0000000000..d25edeb553 --- /dev/null +++ b/crates/bevy_ecs/src/bundle/remove.rs @@ -0,0 +1,449 @@ +use alloc::vec::Vec; +use bevy_ptr::ConstNonNull; +use core::ptr::NonNull; + +use crate::{ + archetype::{Archetype, ArchetypeCreated, ArchetypeId, Archetypes}, + bundle::{Bundle, BundleId, BundleInfo}, + change_detection::MaybeLocation, + component::{ComponentId, Components, ComponentsRegistrator, StorageType}, + entity::{Entity, EntityLocation}, + lifecycle::{REMOVE, REPLACE}, + observer::Observers, + relationship::RelationshipHookMode, + storage::{SparseSets, Storages, Table}, + world::{unsafe_world_cell::UnsafeWorldCell, World}, +}; + +// 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, + old_and_new_table: Option<(NonNull
, NonNull
)>, + old_archetype: NonNull, + new_archetype: NonNull, +} + +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( + world: &'w mut World, + archetype_id: ArchetypeId, + require_all: bool, + ) -> Option { + // 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::(&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 { + let bundle_info = world.bundles.get_unchecked(bundle_id); + // SAFETY: Caller ensures archetype and bundle ids are correct. + let (new_archetype_id, is_new_created) = unsafe { + bundle_info.remove_bundle_from_archetype( + &mut world.archetypes, + &mut world.storages, + &world.components, + &world.observers, + archetype_id, + !require_all, + ) + }; + let new_archetype_id = new_archetype_id?; + + 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())) + }; + + let remover = 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(), + }; + if is_new_created { + remover + .world + .into_deferred() + .trigger(ArchetypeCreated(new_archetype_id)); + } + Some(remover) + } + + /// 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( + &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( + REPLACE, + Some(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( + REMOVE, + Some(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.write(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(), + Some(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(), + Some(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(), Some(new_location)); + } + + (new_location, pre_remove_result) + } +} + +impl BundleInfo { + /// Removes a bundle from the given archetype and returns the resulting archetype and whether a new archetype was created. + /// (or `None` if the removal was invalid). + /// This could be the same [`ArchetypeId`], in the event that removing the given bundle + /// does not result in an [`Archetype`] change. + /// + /// Results are cached in the [`Archetype`] graph to avoid redundant work. + /// + /// If `intersection` is false, attempting to remove a bundle with components not contained in the + /// current archetype will fail, returning `None`. + /// + /// If `intersection` is true, components in the bundle but not in the current archetype + /// will be ignored. + /// + /// # Safety + /// `archetype_id` must exist and components in `bundle_info` must exist + pub(crate) unsafe fn remove_bundle_from_archetype( + &self, + archetypes: &mut Archetypes, + storages: &mut Storages, + components: &Components, + observers: &Observers, + archetype_id: ArchetypeId, + intersection: bool, + ) -> (Option, bool) { + // Check the archetype graph to see if the bundle has been + // removed from this archetype in the past. + let archetype_after_remove_result = { + let edges = archetypes[archetype_id].edges(); + if intersection { + edges.get_archetype_after_bundle_remove(self.id()) + } else { + edges.get_archetype_after_bundle_take(self.id()) + } + }; + let (result, is_new_created) = if let Some(result) = archetype_after_remove_result { + // This bundle removal result is cached. Just return that! + (result, false) + } else { + let mut next_table_components; + let mut next_sparse_set_components; + let next_table_id; + { + let current_archetype = &mut archetypes[archetype_id]; + let mut removed_table_components = Vec::new(); + let mut removed_sparse_set_components = Vec::new(); + for component_id in self.iter_explicit_components() { + if current_archetype.contains(component_id) { + // SAFETY: bundle components were already initialized by bundles.get_info + let component_info = unsafe { components.get_info_unchecked(component_id) }; + match component_info.storage_type() { + StorageType::Table => removed_table_components.push(component_id), + StorageType::SparseSet => { + removed_sparse_set_components.push(component_id); + } + } + } else if !intersection { + // A component in the bundle was not present in the entity's archetype, so this + // removal is invalid. Cache the result in the archetype graph. + current_archetype + .edges_mut() + .cache_archetype_after_bundle_take(self.id(), None); + return (None, false); + } + } + + // Sort removed components so we can do an efficient "sorted remove". + // Archetype components are already sorted. + removed_table_components.sort_unstable(); + removed_sparse_set_components.sort_unstable(); + next_table_components = current_archetype.table_components().collect(); + next_sparse_set_components = current_archetype.sparse_set_components().collect(); + sorted_remove(&mut next_table_components, &removed_table_components); + sorted_remove( + &mut next_sparse_set_components, + &removed_sparse_set_components, + ); + + next_table_id = if removed_table_components.is_empty() { + current_archetype.table_id() + } else { + // SAFETY: all components in next_table_components exist + unsafe { + storages + .tables + .get_id_or_insert(&next_table_components, components) + } + }; + } + + let (new_archetype_id, is_new_created) = archetypes.get_id_or_insert( + components, + observers, + next_table_id, + next_table_components, + next_sparse_set_components, + ); + (Some(new_archetype_id), is_new_created) + }; + let current_archetype = &mut archetypes[archetype_id]; + // Cache the result in an edge. + if intersection { + current_archetype + .edges_mut() + .cache_archetype_after_bundle_remove(self.id(), result); + } else { + current_archetype + .edges_mut() + .cache_archetype_after_bundle_take(self.id(), result); + } + (result, is_new_created) + } +} + +fn sorted_remove(source: &mut Vec, remove: &[T]) { + let mut remove_index = 0; + source.retain(|value| { + while remove_index < remove.len() && *value > remove[remove_index] { + remove_index += 1; + } + + if remove_index < remove.len() { + *value != remove[remove_index] + } else { + true + } + }); +} + +#[cfg(test)] +mod tests { + use alloc::vec; + + #[test] + fn sorted_remove() { + let mut a = vec![1, 2, 3, 4, 5, 6, 7]; + let b = vec![1, 2, 3, 5, 7]; + super::sorted_remove(&mut a, &b); + + assert_eq!(a, vec![4, 6]); + + let mut a = vec![1]; + let b = vec![1]; + super::sorted_remove(&mut a, &b); + + assert_eq!(a, vec![]); + + let mut a = vec![1]; + let b = vec![2]; + super::sorted_remove(&mut a, &b); + + assert_eq!(a, vec![1]); + } +} diff --git a/crates/bevy_ecs/src/bundle/tests.rs b/crates/bevy_ecs/src/bundle/tests.rs index ec73127eb3..875e604f12 100644 --- a/crates/bevy_ecs/src/bundle/tests.rs +++ b/crates/bevy_ecs/src/bundle/tests.rs @@ -1,7 +1,6 @@ use crate::{ archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld, }; -use alloc::vec; #[derive(Component)] struct A; @@ -237,27 +236,6 @@ fn sparse_set_insert_if_new() { assert_eq!(entity.get(), Some(&SparseV("one"))); } -#[test] -fn sorted_remove() { - let mut a = vec![1, 2, 3, 4, 5, 6, 7]; - let b = vec![1, 2, 3, 5, 7]; - super::sorted_remove(&mut a, &b); - - assert_eq!(a, vec![4, 6]); - - let mut a = vec![1]; - let b = vec![1]; - super::sorted_remove(&mut a, &b); - - assert_eq!(a, vec![]); - - let mut a = vec![1]; - let b = vec![2]; - super::sorted_remove(&mut a, &b); - - assert_eq!(a, vec![1]); -} - #[test] fn new_archetype_created() { let mut world = World::new();