Misc. docs and renames for niche ECS internals (#16786)
## Objective Some structs and methods in the ECS internals have names that don't describe their purpose very well, and sometimes don't have docs either. Also, the function `remove_bundle_from_archetype` is a counterpart to `BundleInfo::add_bundle_to_archetype`, but isn't a method and is in a different file. ## Solution - Renamed the following structs and added docs: | Before | After | |----------------------|------------------------------| | `AddBundle` | `ArchetypeAfterBundleInsert` | | `InsertBundleResult` | `ArchetypeMoveType` | - Renamed the following methods: | Before | After | |---------------------------------------|----------------------------------------------| | `Edges::get_add_bundle` | `Edges::get_archetype_after_bundle_insert` | | `Edges::insert_add_bundle` | `Edges::cache_archetype_after_bundle_insert` | | `Edges::get_remove_bundle` | `Edges::get_archetype_after_bundle_remove` | | `Edges::insert_remove_bundle` | `Edges::cache_archetype_after_bundle_remove` | | `Edges::get_take_bundle` | `Edges::get_archetype_after_bundle_take` | | `Edges::insert_take_bundle` | `Edges::cache_archetype_after_bundle_take` | - Moved `remove_bundle_from_archetype` from `world/entity_ref.rs` to `BundleInfo`. I left the function in entity_ref in the first commit for comparison, look there for the diff of comments and whatnot. - Tidied up docs: - General grammar and spacing. - Made the usage of "insert" and "add" more consistent. - Removed references to information that isn't there. - Renamed `BundleInfo::add_bundle_to_archetype` to `BundleInfo::insert_bundle_into_archetype` for consistency.
This commit is contained in:
parent
ced6159d93
commit
d132239bb1
@ -109,7 +109,7 @@ impl ArchetypeId {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in [`AddBundle`] to track whether components in the bundle are newly
|
||||
/// Used in [`ArchetypeAfterBundleInsert`] to track whether components in the bundle are newly
|
||||
/// added or already existed in the entity's archetype.
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub(crate) enum ComponentStatus {
|
||||
@ -117,11 +117,12 @@ pub(crate) enum ComponentStatus {
|
||||
Existing,
|
||||
}
|
||||
|
||||
pub(crate) struct AddBundle {
|
||||
/// The target archetype after the bundle is added to the source archetype
|
||||
/// Used in [`Edges`] to cache the result of inserting a bundle into the source archetype.
|
||||
pub(crate) struct ArchetypeAfterBundleInsert {
|
||||
/// The target archetype after the bundle is inserted into the source archetype.
|
||||
pub archetype_id: ArchetypeId,
|
||||
/// For each component iterated in the same order as the source [`Bundle`](crate::bundle::Bundle),
|
||||
/// indicate if the component is newly added to the target archetype or if it already existed
|
||||
/// indicate if the component is newly added to the target archetype or if it already existed.
|
||||
pub bundle_status: Vec<ComponentStatus>,
|
||||
/// The set of additional required components that must be initialized immediately when adding this Bundle.
|
||||
///
|
||||
@ -134,7 +135,7 @@ pub(crate) struct AddBundle {
|
||||
pub existing: Vec<ComponentId>,
|
||||
}
|
||||
|
||||
impl AddBundle {
|
||||
impl ArchetypeAfterBundleInsert {
|
||||
pub(crate) fn iter_inserted(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
|
||||
self.added.iter().chain(self.existing.iter()).copied()
|
||||
}
|
||||
@ -149,10 +150,10 @@ impl AddBundle {
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// being inserted into 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"
|
||||
/// 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
|
||||
@ -160,7 +161,7 @@ pub(crate) trait BundleComponentStatus {
|
||||
unsafe fn get_status(&self, index: usize) -> ComponentStatus;
|
||||
}
|
||||
|
||||
impl BundleComponentStatus for AddBundle {
|
||||
impl BundleComponentStatus for ArchetypeAfterBundleInsert {
|
||||
#[inline]
|
||||
unsafe fn get_status(&self, index: usize) -> ComponentStatus {
|
||||
// SAFETY: caller has ensured index is a valid bundle index for this bundle
|
||||
@ -173,7 +174,7 @@ pub(crate) struct SpawnBundleStatus;
|
||||
impl BundleComponentStatus for SpawnBundleStatus {
|
||||
#[inline]
|
||||
unsafe fn get_status(&self, _index: usize) -> ComponentStatus {
|
||||
// Components added during a spawn call are always treated as added
|
||||
// Components inserted during a spawn call are always treated as added.
|
||||
ComponentStatus::Added
|
||||
}
|
||||
}
|
||||
@ -194,37 +195,36 @@ impl BundleComponentStatus for SpawnBundleStatus {
|
||||
/// [`World`]: crate::world::World
|
||||
#[derive(Default)]
|
||||
pub struct Edges {
|
||||
add_bundle: SparseArray<BundleId, AddBundle>,
|
||||
insert_bundle: SparseArray<BundleId, ArchetypeAfterBundleInsert>,
|
||||
remove_bundle: SparseArray<BundleId, Option<ArchetypeId>>,
|
||||
take_bundle: SparseArray<BundleId, Option<ArchetypeId>>,
|
||||
}
|
||||
|
||||
impl Edges {
|
||||
/// Checks the cache for the target archetype when adding a bundle to the
|
||||
/// source archetype. For more information, see [`EntityWorldMut::insert`].
|
||||
/// Checks the cache for the target archetype when inserting a bundle into the
|
||||
/// source archetype.
|
||||
///
|
||||
/// If this returns `None`, it means there has not been a transition from
|
||||
/// the source archetype via the provided bundle.
|
||||
///
|
||||
/// [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert
|
||||
#[inline]
|
||||
pub fn get_add_bundle(&self, bundle_id: BundleId) -> Option<ArchetypeId> {
|
||||
self.get_add_bundle_internal(bundle_id)
|
||||
pub fn get_archetype_after_bundle_insert(&self, bundle_id: BundleId) -> Option<ArchetypeId> {
|
||||
self.get_archetype_after_bundle_insert_internal(bundle_id)
|
||||
.map(|bundle| bundle.archetype_id)
|
||||
}
|
||||
|
||||
/// Internal version of `get_add_bundle` that fetches the full `AddBundle`.
|
||||
/// Internal version of `get_archetype_after_bundle_insert` that
|
||||
/// fetches the full `ArchetypeAfterBundleInsert`.
|
||||
#[inline]
|
||||
pub(crate) fn get_add_bundle_internal(&self, bundle_id: BundleId) -> Option<&AddBundle> {
|
||||
self.add_bundle.get(bundle_id)
|
||||
pub(crate) fn get_archetype_after_bundle_insert_internal(
|
||||
&self,
|
||||
bundle_id: BundleId,
|
||||
) -> Option<&ArchetypeAfterBundleInsert> {
|
||||
self.insert_bundle.get(bundle_id)
|
||||
}
|
||||
|
||||
/// Caches the target archetype when adding a bundle to the source archetype.
|
||||
/// For more information, see [`EntityWorldMut::insert`].
|
||||
///
|
||||
/// [`EntityWorldMut::insert`]: crate::world::EntityWorldMut::insert
|
||||
/// Caches the target archetype when inserting a bundle into the source archetype.
|
||||
#[inline]
|
||||
pub(crate) fn insert_add_bundle(
|
||||
pub(crate) fn cache_archetype_after_bundle_insert(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: ArchetypeId,
|
||||
@ -233,9 +233,9 @@ impl Edges {
|
||||
added: Vec<ComponentId>,
|
||||
existing: Vec<ComponentId>,
|
||||
) {
|
||||
self.add_bundle.insert(
|
||||
self.insert_bundle.insert(
|
||||
bundle_id,
|
||||
AddBundle {
|
||||
ArchetypeAfterBundleInsert {
|
||||
archetype_id,
|
||||
bundle_status,
|
||||
required_components,
|
||||
@ -245,27 +245,25 @@ impl Edges {
|
||||
);
|
||||
}
|
||||
|
||||
/// Checks the cache for the target archetype when removing a bundle to the
|
||||
/// source archetype. For more information, see [`EntityWorldMut::remove`].
|
||||
/// Checks the cache for the target archetype when removing a bundle from the
|
||||
/// source archetype.
|
||||
///
|
||||
/// If this returns `None`, it means there has not been a transition from
|
||||
/// the source archetype via the provided bundle.
|
||||
///
|
||||
/// If this returns `Some(None)`, it means that the bundle cannot be removed
|
||||
/// from the source archetype.
|
||||
///
|
||||
/// [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove
|
||||
#[inline]
|
||||
pub fn get_remove_bundle(&self, bundle_id: BundleId) -> Option<Option<ArchetypeId>> {
|
||||
pub fn get_archetype_after_bundle_remove(
|
||||
&self,
|
||||
bundle_id: BundleId,
|
||||
) -> Option<Option<ArchetypeId>> {
|
||||
self.remove_bundle.get(bundle_id).cloned()
|
||||
}
|
||||
|
||||
/// Caches the target archetype when removing a bundle to the source archetype.
|
||||
/// For more information, see [`EntityWorldMut::remove`].
|
||||
///
|
||||
/// [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove
|
||||
/// Caches the target archetype when removing a bundle from the source archetype.
|
||||
#[inline]
|
||||
pub(crate) fn insert_remove_bundle(
|
||||
pub(crate) fn cache_archetype_after_bundle_remove(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: Option<ArchetypeId>,
|
||||
@ -273,24 +271,31 @@ impl Edges {
|
||||
self.remove_bundle.insert(bundle_id, archetype_id);
|
||||
}
|
||||
|
||||
/// Checks the cache for the target archetype when removing a bundle to the
|
||||
/// source archetype. For more information, see [`EntityWorldMut::remove`].
|
||||
/// Checks the cache for the target archetype when taking a bundle from the
|
||||
/// source archetype.
|
||||
///
|
||||
/// Unlike `remove`, `take` will only succeed if the source archetype
|
||||
/// contains all of the components in the bundle.
|
||||
///
|
||||
/// If this returns `None`, it means there has not been a transition from
|
||||
/// the source archetype via the provided bundle.
|
||||
///
|
||||
/// [`EntityWorldMut::remove`]: crate::world::EntityWorldMut::remove
|
||||
/// If this returns `Some(None)`, it means that the bundle cannot be taken
|
||||
/// from the source archetype.
|
||||
#[inline]
|
||||
pub fn get_take_bundle(&self, bundle_id: BundleId) -> Option<Option<ArchetypeId>> {
|
||||
pub fn get_archetype_after_bundle_take(
|
||||
&self,
|
||||
bundle_id: BundleId,
|
||||
) -> Option<Option<ArchetypeId>> {
|
||||
self.take_bundle.get(bundle_id).cloned()
|
||||
}
|
||||
|
||||
/// Caches the target archetype when removing a bundle to the source archetype.
|
||||
/// For more information, see [`EntityWorldMut::take`].
|
||||
/// Caches the target archetype when taking a bundle from the source archetype.
|
||||
///
|
||||
/// [`EntityWorldMut::take`]: crate::world::EntityWorldMut::take
|
||||
/// Unlike `remove`, `take` will only succeed if the source archetype
|
||||
/// contains all of the components in the bundle.
|
||||
#[inline]
|
||||
pub(crate) fn insert_take_bundle(
|
||||
pub(crate) fn cache_archetype_after_bundle_take(
|
||||
&mut self,
|
||||
bundle_id: BundleId,
|
||||
archetype_id: Option<ArchetypeId>,
|
||||
@ -577,11 +582,11 @@ impl Archetype {
|
||||
self.entities.reserve(additional);
|
||||
}
|
||||
|
||||
/// Removes the entity at `index` by swapping it out. Returns the table row the entity is stored
|
||||
/// Removes the entity at `row` by swapping it out. Returns the table row the entity is stored
|
||||
/// in.
|
||||
///
|
||||
/// # Panics
|
||||
/// This function will panic if `index >= self.len()`
|
||||
/// This function will panic if `row >= self.entities.len()`
|
||||
#[inline]
|
||||
pub(crate) fn swap_remove(&mut self, row: ArchetypeRow) -> ArchetypeSwapRemoveResult {
|
||||
let is_last = row.index() == self.entities.len() - 1;
|
||||
|
@ -6,8 +6,8 @@ pub use bevy_ecs_macros::Bundle;
|
||||
|
||||
use crate::{
|
||||
archetype::{
|
||||
AddBundle, Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
|
||||
SpawnBundleStatus,
|
||||
Archetype, ArchetypeAfterBundleInsert, ArchetypeId, Archetypes, BundleComponentStatus,
|
||||
ComponentStatus, SpawnBundleStatus,
|
||||
},
|
||||
component::{
|
||||
Component, ComponentId, Components, RequiredComponentConstructor, RequiredComponents,
|
||||
@ -488,13 +488,16 @@ impl BundleInfo {
|
||||
/// # 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)
|
||||
/// 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`]
|
||||
/// should be used, which will report `Added` vs `Mutated` status based on the current archetype's structure.
|
||||
/// should be `Existing`. If the original archetype does not have `ComponentA`, the status should be `Added`.
|
||||
///
|
||||
/// When "inserting" a bundle into an existing entity, [`ArchetypeAfterBundleInsert`]
|
||||
/// should be used, which will report `Added` vs `Existing` 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`] in the archetype graph, which requires
|
||||
/// to look up the [`ArchetypeAfterBundleInsert`] 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
|
||||
@ -634,12 +637,15 @@ impl BundleInfo {
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a bundle to the given archetype and returns the resulting archetype. This could be the
|
||||
/// same [`ArchetypeId`], in the event that adding the given bundle does not result in an
|
||||
/// [`Archetype`] change. Results are cached in the [`Archetype`] graph to avoid redundant work.
|
||||
/// Inserts a bundle into the given archetype and returns the resulting archetype.
|
||||
/// This could be the same [`ArchetypeId`], in the event that inserting the given bundle
|
||||
/// does not result in an [`Archetype`] change.
|
||||
///
|
||||
/// Results are cached in the [`Archetype`] graph to avoid redundant work.
|
||||
///
|
||||
/// # Safety
|
||||
/// `components` must be the same components as passed in [`Self::new`]
|
||||
pub(crate) unsafe fn add_bundle_to_archetype(
|
||||
pub(crate) unsafe fn insert_bundle_into_archetype(
|
||||
&self,
|
||||
archetypes: &mut Archetypes,
|
||||
storages: &mut Storages,
|
||||
@ -647,8 +653,11 @@ impl BundleInfo {
|
||||
observers: &Observers,
|
||||
archetype_id: ArchetypeId,
|
||||
) -> ArchetypeId {
|
||||
if let Some(add_bundle_id) = archetypes[archetype_id].edges().get_add_bundle(self.id) {
|
||||
return add_bundle_id;
|
||||
if let Some(archetype_after_insert_id) = archetypes[archetype_id]
|
||||
.edges()
|
||||
.get_archetype_after_bundle_insert(self.id)
|
||||
{
|
||||
return archetype_after_insert_id;
|
||||
}
|
||||
let mut new_table_components = Vec::new();
|
||||
let mut new_sparse_set_components = Vec::new();
|
||||
@ -693,8 +702,8 @@ impl BundleInfo {
|
||||
|
||||
if new_table_components.is_empty() && new_sparse_set_components.is_empty() {
|
||||
let edges = current_archetype.edges_mut();
|
||||
// the archetype does not change when we add this bundle
|
||||
edges.insert_add_bundle(
|
||||
// The archetype does not change when we insert this bundle.
|
||||
edges.cache_archetype_after_bundle_insert(
|
||||
self.id,
|
||||
archetype_id,
|
||||
bundle_status,
|
||||
@ -707,16 +716,16 @@ impl BundleInfo {
|
||||
let table_id;
|
||||
let table_components;
|
||||
let sparse_set_components;
|
||||
// the archetype changes when we add this bundle. prepare the new archetype and storages
|
||||
// The archetype changes when we insert this bundle. Prepare the new archetype and storages.
|
||||
{
|
||||
let current_archetype = &archetypes[archetype_id];
|
||||
table_components = if new_table_components.is_empty() {
|
||||
// if there are no new table components, we can keep using this table
|
||||
// If there are no new table components, we can keep using this table.
|
||||
table_id = current_archetype.table_id();
|
||||
current_archetype.table_components().collect()
|
||||
} else {
|
||||
new_table_components.extend(current_archetype.table_components());
|
||||
// sort to ignore order while hashing
|
||||
// Sort to ignore order while hashing.
|
||||
new_table_components.sort_unstable();
|
||||
// SAFETY: all component ids in `new_table_components` exist
|
||||
table_id = unsafe {
|
||||
@ -732,7 +741,7 @@ impl BundleInfo {
|
||||
current_archetype.sparse_set_components().collect()
|
||||
} else {
|
||||
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
||||
// sort to ignore order while hashing
|
||||
// Sort to ignore order while hashing.
|
||||
new_sparse_set_components.sort_unstable();
|
||||
new_sparse_set_components
|
||||
};
|
||||
@ -745,36 +754,156 @@ impl BundleInfo {
|
||||
table_components,
|
||||
sparse_set_components,
|
||||
);
|
||||
// add an edge from the old archetype to the new archetype
|
||||
archetypes[archetype_id].edges_mut().insert_add_bundle(
|
||||
self.id,
|
||||
new_archetype_id,
|
||||
bundle_status,
|
||||
added_required_components,
|
||||
added,
|
||||
existing,
|
||||
);
|
||||
// Add an edge from the old archetype to the new archetype.
|
||||
archetypes[archetype_id]
|
||||
.edges_mut()
|
||||
.cache_archetype_after_bundle_insert(
|
||||
self.id,
|
||||
new_archetype_id,
|
||||
bundle_status,
|
||||
added_required_components,
|
||||
added,
|
||||
existing,
|
||||
);
|
||||
new_archetype_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a bundle from the given archetype and returns the resulting archetype
|
||||
/// (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<ArchetypeId> {
|
||||
// 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 = if let Some(result) = archetype_after_remove_result {
|
||||
// This bundle removal result is cached. Just return that!
|
||||
result
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = archetypes.get_id_or_insert(
|
||||
components,
|
||||
observers,
|
||||
next_table_id,
|
||||
next_table_components,
|
||||
next_sparse_set_components,
|
||||
);
|
||||
Some(new_archetype_id)
|
||||
};
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// SAFETY: We have exclusive world access so our pointers can't be invalidated externally
|
||||
pub(crate) struct BundleInserter<'w> {
|
||||
world: UnsafeWorldCell<'w>,
|
||||
bundle_info: ConstNonNull<BundleInfo>,
|
||||
add_bundle: ConstNonNull<AddBundle>,
|
||||
archetype_after_insert: ConstNonNull<ArchetypeAfterBundleInsert>,
|
||||
table: NonNull<Table>,
|
||||
archetype: NonNull<Archetype>,
|
||||
result: InsertBundleResult,
|
||||
archetype_move_type: ArchetypeMoveType,
|
||||
change_tick: Tick,
|
||||
}
|
||||
|
||||
pub(crate) enum InsertBundleResult {
|
||||
/// The type of archetype move (or lack thereof) that will result from a bundle
|
||||
/// being inserted into an entity.
|
||||
pub(crate) enum ArchetypeMoveType {
|
||||
/// If the entity already has all of the components that are being inserted,
|
||||
/// its archetype won't change.
|
||||
SameArchetype,
|
||||
NewArchetypeSameTable {
|
||||
new_archetype: NonNull<Archetype>,
|
||||
},
|
||||
/// If only [`sparse set`](StorageType::SparseSet) components are being added,
|
||||
/// the entity's archetype will change while keeping the same table.
|
||||
NewArchetypeSameTable { new_archetype: NonNull<Archetype> },
|
||||
/// If any [`table-stored`](StorageType::Table) components are being added,
|
||||
/// both the entity's archetype and table will change.
|
||||
NewArchetypeNewTable {
|
||||
new_archetype: NonNull<Archetype>,
|
||||
new_table: NonNull<Table>,
|
||||
@ -809,7 +938,7 @@ impl<'w> BundleInserter<'w> {
|
||||
// SAFETY: We will not make any accesses to the command queue, component or resource data of this world
|
||||
let bundle_info = world.bundles.get_unchecked(bundle_id);
|
||||
let bundle_id = bundle_info.id();
|
||||
let new_archetype_id = bundle_info.add_bundle_to_archetype(
|
||||
let new_archetype_id = bundle_info.insert_bundle_into_archetype(
|
||||
&mut world.archetypes,
|
||||
&mut world.storages,
|
||||
&world.components,
|
||||
@ -818,32 +947,32 @@ impl<'w> BundleInserter<'w> {
|
||||
);
|
||||
if new_archetype_id == archetype_id {
|
||||
let archetype = &mut world.archetypes[archetype_id];
|
||||
// SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype
|
||||
let add_bundle = unsafe {
|
||||
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
|
||||
let archetype_after_insert = unsafe {
|
||||
archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(bundle_id)
|
||||
.get_archetype_after_bundle_insert_internal(bundle_id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
let table_id = archetype.table_id();
|
||||
let table = &mut world.storages.tables[table_id];
|
||||
Self {
|
||||
add_bundle: add_bundle.into(),
|
||||
archetype_after_insert: archetype_after_insert.into(),
|
||||
archetype: archetype.into(),
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
result: InsertBundleResult::SameArchetype,
|
||||
archetype_move_type: ArchetypeMoveType::SameArchetype,
|
||||
change_tick,
|
||||
world: world.as_unsafe_world_cell(),
|
||||
}
|
||||
} else {
|
||||
let (archetype, new_archetype) =
|
||||
world.archetypes.get_2_mut(archetype_id, new_archetype_id);
|
||||
// SAFETY: The edge is assured to be initialized when we called add_bundle_to_archetype
|
||||
let add_bundle = unsafe {
|
||||
// SAFETY: The edge is assured to be initialized when we called insert_bundle_into_archetype
|
||||
let archetype_after_insert = unsafe {
|
||||
archetype
|
||||
.edges()
|
||||
.get_add_bundle_internal(bundle_id)
|
||||
.get_archetype_after_bundle_insert_internal(bundle_id)
|
||||
.debug_checked_unwrap()
|
||||
};
|
||||
let table_id = archetype.table_id();
|
||||
@ -851,11 +980,11 @@ impl<'w> BundleInserter<'w> {
|
||||
if table_id == new_table_id {
|
||||
let table = &mut world.storages.tables[table_id];
|
||||
Self {
|
||||
add_bundle: add_bundle.into(),
|
||||
archetype_after_insert: archetype_after_insert.into(),
|
||||
archetype: archetype.into(),
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
result: InsertBundleResult::NewArchetypeSameTable {
|
||||
archetype_move_type: ArchetypeMoveType::NewArchetypeSameTable {
|
||||
new_archetype: new_archetype.into(),
|
||||
},
|
||||
change_tick,
|
||||
@ -864,11 +993,11 @@ impl<'w> BundleInserter<'w> {
|
||||
} else {
|
||||
let (table, new_table) = world.storages.tables.get_2_mut(table_id, new_table_id);
|
||||
Self {
|
||||
add_bundle: add_bundle.into(),
|
||||
archetype_after_insert: archetype_after_insert.into(),
|
||||
archetype: archetype.into(),
|
||||
bundle_info: bundle_info.into(),
|
||||
table: table.into(),
|
||||
result: InsertBundleResult::NewArchetypeNewTable {
|
||||
archetype_move_type: ArchetypeMoveType::NewArchetypeNewTable {
|
||||
new_archetype: new_archetype.into(),
|
||||
new_table: new_table.into(),
|
||||
},
|
||||
@ -892,7 +1021,7 @@ impl<'w> BundleInserter<'w> {
|
||||
#[cfg(feature = "track_change_detection")] caller: &'static Location<'static>,
|
||||
) -> EntityLocation {
|
||||
let bundle_info = self.bundle_info.as_ref();
|
||||
let add_bundle = self.add_bundle.as_ref();
|
||||
let archetype_after_insert = self.archetype_after_insert.as_ref();
|
||||
let table = self.table.as_mut();
|
||||
let archetype = self.archetype.as_ref();
|
||||
|
||||
@ -907,10 +1036,14 @@ impl<'w> BundleInserter<'w> {
|
||||
deferred_world.trigger_observers(
|
||||
ON_REPLACE,
|
||||
entity,
|
||||
add_bundle.iter_existing(),
|
||||
archetype_after_insert.iter_existing(),
|
||||
);
|
||||
}
|
||||
deferred_world.trigger_on_replace(archetype, entity, add_bundle.iter_existing());
|
||||
deferred_world.trigger_on_replace(
|
||||
archetype,
|
||||
entity,
|
||||
archetype_after_insert.iter_existing(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -918,8 +1051,8 @@ impl<'w> BundleInserter<'w> {
|
||||
// so this reference can only be promoted from shared to &mut down here, after they have been ran
|
||||
let archetype = self.archetype.as_mut();
|
||||
|
||||
let (new_archetype, new_location) = match &mut self.result {
|
||||
InsertBundleResult::SameArchetype => {
|
||||
let (new_archetype, new_location) = match &mut self.archetype_move_type {
|
||||
ArchetypeMoveType::SameArchetype => {
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
let sparse_sets = {
|
||||
let world = self.world.world_mut();
|
||||
@ -929,8 +1062,8 @@ impl<'w> BundleInserter<'w> {
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
add_bundle.required_components.iter(),
|
||||
archetype_after_insert,
|
||||
archetype_after_insert.required_components.iter(),
|
||||
entity,
|
||||
location.table_row,
|
||||
self.change_tick,
|
||||
@ -942,7 +1075,7 @@ impl<'w> BundleInserter<'w> {
|
||||
|
||||
(archetype, location)
|
||||
}
|
||||
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
|
||||
ArchetypeMoveType::NewArchetypeSameTable { new_archetype } => {
|
||||
let new_archetype = new_archetype.as_mut();
|
||||
|
||||
// SAFETY: Mutable references do not alias and will be dropped after this block
|
||||
@ -971,8 +1104,8 @@ impl<'w> BundleInserter<'w> {
|
||||
bundle_info.write_components(
|
||||
table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
add_bundle.required_components.iter(),
|
||||
archetype_after_insert,
|
||||
archetype_after_insert.required_components.iter(),
|
||||
entity,
|
||||
result.table_row,
|
||||
self.change_tick,
|
||||
@ -984,7 +1117,7 @@ impl<'w> BundleInserter<'w> {
|
||||
|
||||
(new_archetype, new_location)
|
||||
}
|
||||
InsertBundleResult::NewArchetypeNewTable {
|
||||
ArchetypeMoveType::NewArchetypeNewTable {
|
||||
new_archetype,
|
||||
new_table,
|
||||
} => {
|
||||
@ -1022,7 +1155,7 @@ impl<'w> BundleInserter<'w> {
|
||||
let new_location = new_archetype.allocate(entity, move_result.new_row);
|
||||
entities.set(entity.index(), new_location);
|
||||
|
||||
// if an entity was moved into this entity's table spot, update its table row
|
||||
// If an entity was moved into this entity's table spot, update its table row.
|
||||
if let Some(swapped_entity) = move_result.swapped_entity {
|
||||
let swapped_location =
|
||||
// SAFETY: If the swap was successful, swapped_entity must be valid.
|
||||
@ -1054,8 +1187,8 @@ impl<'w> BundleInserter<'w> {
|
||||
bundle_info.write_components(
|
||||
new_table,
|
||||
sparse_sets,
|
||||
add_bundle,
|
||||
add_bundle.required_components.iter(),
|
||||
archetype_after_insert,
|
||||
archetype_after_insert.required_components.iter(),
|
||||
entity,
|
||||
move_result.new_row,
|
||||
self.change_tick,
|
||||
@ -1076,39 +1209,47 @@ impl<'w> BundleInserter<'w> {
|
||||
// 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(new_archetype, entity, add_bundle.iter_added());
|
||||
deferred_world.trigger_on_add(
|
||||
new_archetype,
|
||||
entity,
|
||||
archetype_after_insert.iter_added(),
|
||||
);
|
||||
if new_archetype.has_add_observer() {
|
||||
deferred_world.trigger_observers(ON_ADD, entity, add_bundle.iter_added());
|
||||
deferred_world.trigger_observers(
|
||||
ON_ADD,
|
||||
entity,
|
||||
archetype_after_insert.iter_added(),
|
||||
);
|
||||
}
|
||||
match insert_mode {
|
||||
InsertMode::Replace => {
|
||||
// insert triggers for both new and existing components if we're replacing them
|
||||
// Insert triggers for both new and existing components if we're replacing them.
|
||||
deferred_world.trigger_on_insert(
|
||||
new_archetype,
|
||||
entity,
|
||||
add_bundle.iter_inserted(),
|
||||
archetype_after_insert.iter_inserted(),
|
||||
);
|
||||
if new_archetype.has_insert_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_INSERT,
|
||||
entity,
|
||||
add_bundle.iter_inserted(),
|
||||
archetype_after_insert.iter_inserted(),
|
||||
);
|
||||
}
|
||||
}
|
||||
InsertMode::Keep => {
|
||||
// insert triggers only for new components if we're not replacing them (since
|
||||
// Insert triggers only for new components if we're not replacing them (since
|
||||
// nothing is actually inserted).
|
||||
deferred_world.trigger_on_insert(
|
||||
new_archetype,
|
||||
entity,
|
||||
add_bundle.iter_added(),
|
||||
archetype_after_insert.iter_added(),
|
||||
);
|
||||
if new_archetype.has_insert_observer() {
|
||||
deferred_world.trigger_observers(
|
||||
ON_INSERT,
|
||||
entity,
|
||||
add_bundle.iter_added(),
|
||||
archetype_after_insert.iter_added(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1155,7 +1296,7 @@ impl<'w> BundleSpawner<'w> {
|
||||
change_tick: Tick,
|
||||
) -> Self {
|
||||
let bundle_info = world.bundles.get_unchecked(bundle_id);
|
||||
let new_archetype_id = bundle_info.add_bundle_to_archetype(
|
||||
let new_archetype_id = bundle_info.insert_bundle_into_archetype(
|
||||
&mut world.archetypes,
|
||||
&mut world.storages,
|
||||
&world.components,
|
||||
@ -1482,6 +1623,21 @@ fn initialize_dynamic_bundle(
|
||||
(id, storage_types)
|
||||
}
|
||||
|
||||
fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, 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 crate as bevy_ecs;
|
||||
@ -1678,4 +1834,25 @@ mod tests {
|
||||
assert!(entity.contains::<A>());
|
||||
assert_eq!(entity.get(), Some(&V("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]);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use crate::{
|
||||
component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType},
|
||||
entity::{Entities, Entity, EntityLocation},
|
||||
event::Event,
|
||||
observer::{Observer, Observers},
|
||||
observer::Observer,
|
||||
query::{Access, ReadOnlyQueryData},
|
||||
removal_detection::RemovedComponentEvents,
|
||||
storage::Storages,
|
||||
@ -1468,13 +1468,12 @@ impl<'w> EntityWorldMut<'w> {
|
||||
// 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 {
|
||||
remove_bundle_from_archetype(
|
||||
bundle_info.remove_bundle_from_archetype(
|
||||
&mut world.archetypes,
|
||||
storages,
|
||||
components,
|
||||
&world.observers,
|
||||
old_location.archetype_id,
|
||||
bundle_info,
|
||||
false,
|
||||
)?
|
||||
};
|
||||
@ -1650,17 +1649,17 @@ impl<'w> EntityWorldMut<'w> {
|
||||
|
||||
// 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 = remove_bundle_from_archetype(
|
||||
&mut world.archetypes,
|
||||
&mut world.storages,
|
||||
&world.components,
|
||||
&world.observers,
|
||||
location.archetype_id,
|
||||
bundle_info,
|
||||
// components from the bundle that are not present on the entity are ignored
|
||||
true,
|
||||
)
|
||||
.expect("intersections should always return a result");
|
||||
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;
|
||||
@ -3271,126 +3270,6 @@ unsafe fn insert_dynamic_bundle<
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the
|
||||
/// removal was invalid). in the event that adding 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
|
||||
unsafe fn remove_bundle_from_archetype(
|
||||
archetypes: &mut Archetypes,
|
||||
storages: &mut Storages,
|
||||
components: &Components,
|
||||
observers: &Observers,
|
||||
archetype_id: ArchetypeId,
|
||||
bundle_info: &BundleInfo,
|
||||
intersection: bool,
|
||||
) -> Option<ArchetypeId> {
|
||||
// check the archetype graph to see if the Bundle has been removed from this archetype in the
|
||||
// past
|
||||
let remove_bundle_result = {
|
||||
let edges = archetypes[archetype_id].edges();
|
||||
if intersection {
|
||||
edges.get_remove_bundle(bundle_info.id())
|
||||
} else {
|
||||
edges.get_take_bundle(bundle_info.id())
|
||||
}
|
||||
};
|
||||
let result = if let Some(result) = remove_bundle_result {
|
||||
// this Bundle removal result is cached. just return that!
|
||||
result
|
||||
} 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 bundle_info.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()
|
||||
.insert_take_bundle(bundle_info.id(), None);
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = archetypes.get_id_or_insert(
|
||||
components,
|
||||
observers,
|
||||
next_table_id,
|
||||
next_table_components,
|
||||
next_sparse_set_components,
|
||||
);
|
||||
Some(new_archetype_id)
|
||||
};
|
||||
let current_archetype = &mut archetypes[archetype_id];
|
||||
// cache the result in an edge
|
||||
if intersection {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.insert_remove_bundle(bundle_info.id(), result);
|
||||
} else {
|
||||
current_archetype
|
||||
.edges_mut()
|
||||
.insert_take_bundle(bundle_info.id(), result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn sorted_remove<T: Eq + Ord + Copy>(source: &mut Vec<T>, 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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Moves component data out of storage.
|
||||
///
|
||||
/// This function leaves the underlying memory unchanged, but the component behind
|
||||
@ -3700,27 +3579,6 @@ mod tests {
|
||||
|
||||
use super::{EntityMutExcept, EntityRefExcept};
|
||||
|
||||
#[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]);
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy, Debug, PartialEq)]
|
||||
struct TestComponent(u32);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user