Newtype ArchetypeRow and TableRow (#4878)

# Objective
Prevent future unsoundness that was seen in #6623.

## Solution
Newtype both indexes in `Archetype` and `Table` as `ArchetypeRow` and `TableRow`. This avoids weird numerical manipulation on the indices, and can be stored and treated opaquely. Also enforces the source and destination of where these indices at a type level.

---

## Changelog
Changed: `Archetype` indices and `Table` rows have been newtyped as `ArchetypeRow` and `TableRow`.
This commit is contained in:
James Liu 2022-12-06 01:38:21 +00:00
parent a3f203b504
commit 530be10e72
13 changed files with 310 additions and 213 deletions

View file

@ -283,7 +283,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
unsafe fn fetch<'__w>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: usize
_table_row: #path::storage::TableRow,
) -> <Self as #path::query::WorldQuery>::Item<'__w> {
Self::Item {
#(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)*
@ -296,7 +296,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
unsafe fn filter_fetch<'__w>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: #path::entity::Entity,
_table_row: usize
_table_row: #path::storage::TableRow,
) -> bool {
true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))*
}

View file

@ -23,7 +23,7 @@ use crate::{
bundle::BundleId,
component::{ComponentId, StorageType},
entity::{Entity, EntityLocation},
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId},
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
};
use std::{
collections::HashMap,
@ -31,6 +31,33 @@ use std::{
ops::{Index, IndexMut},
};
/// An opaque location within a [`Archetype`].
///
/// This can be used in conjunction with [`ArchetypeId`] to find the exact location
/// of an [`Entity`] within a [`World`]. An entity's archetype and index can be
/// retrieved via [`Entities::get`].
///
/// [`World`]: crate::world::World
/// [`Entities::get`]: crate::entity::Entities
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
#[repr(transparent)]
pub struct ArchetypeRow(usize);
impl ArchetypeRow {
pub const INVALID: ArchetypeRow = ArchetypeRow(usize::MAX);
/// Creates a `ArchetypeRow`.
pub const fn new(index: usize) -> Self {
Self(index)
}
/// Gets the index of the row.
#[inline]
pub const fn index(self) -> usize {
self.0
}
}
/// An opaque unique ID for a single [`Archetype`] within a [`World`].
///
/// Archetype IDs are only valid for a given World, and are not globally unique.
@ -226,7 +253,7 @@ impl Edges {
/// Metadata about an [`Entity`] in a [`Archetype`].
pub struct ArchetypeEntity {
entity: Entity,
table_row: usize,
table_row: TableRow,
}
impl ArchetypeEntity {
@ -240,14 +267,14 @@ impl ArchetypeEntity {
///
/// [`Table`]: crate::storage::Table
#[inline]
pub const fn table_row(&self) -> usize {
pub const fn table_row(&self) -> TableRow {
self.table_row
}
}
pub(crate) struct ArchetypeSwapRemoveResult {
pub(crate) swapped_entity: Option<Entity>,
pub(crate) table_row: usize,
pub(crate) table_row: TableRow,
}
/// Internal metadata for a [`Component`] within a given [`Archetype`].
@ -380,18 +407,18 @@ impl Archetype {
/// Fetches the row in the [`Table`] where the components for the entity at `index`
/// is stored.
///
/// An entity's archetype index can be fetched from [`EntityLocation::index`], which
/// An entity's archetype index can be fetched from [`EntityLocation::archetype_row`], which
/// can be retrieved from [`Entities::get`].
///
/// # Panics
/// This function will panic if `index >= self.len()`.
///
/// [`Table`]: crate::storage::Table
/// [`EntityLocation`]: crate::entity::EntityLocation::index
/// [`EntityLocation`]: crate::entity::EntityLocation::archetype_row
/// [`Entities::get`]: crate::entity::Entities::get
#[inline]
pub fn entity_table_row(&self, index: usize) -> usize {
self.entities[index].table_row
pub fn entity_table_row(&self, index: ArchetypeRow) -> TableRow {
self.entities[index.0].table_row
}
/// Updates if the components for the entity at `index` can be found
@ -400,8 +427,8 @@ impl Archetype {
/// # Panics
/// This function will panic if `index >= self.len()`.
#[inline]
pub(crate) fn set_entity_table_row(&mut self, index: usize, table_row: usize) {
self.entities[index].table_row = table_row;
pub(crate) fn set_entity_table_row(&mut self, index: ArchetypeRow, table_row: TableRow) {
self.entities[index.0].table_row = table_row;
}
/// Allocates an entity to the archetype.
@ -409,12 +436,16 @@ impl Archetype {
/// # Safety
/// valid component values must be immediately written to the relevant storages
/// `table_row` must be valid
pub(crate) unsafe fn allocate(&mut self, entity: Entity, table_row: usize) -> EntityLocation {
pub(crate) unsafe fn allocate(
&mut self,
entity: Entity,
table_row: TableRow,
) -> EntityLocation {
self.entities.push(ArchetypeEntity { entity, table_row });
EntityLocation {
archetype_id: self.id,
index: self.entities.len() - 1,
archetype_row: ArchetypeRow(self.entities.len() - 1),
}
}
@ -427,14 +458,14 @@ impl Archetype {
///
/// # Panics
/// This function will panic if `index >= self.len()`
pub(crate) fn swap_remove(&mut self, index: usize) -> ArchetypeSwapRemoveResult {
let is_last = index == self.entities.len() - 1;
let entity = self.entities.swap_remove(index);
pub(crate) fn swap_remove(&mut self, index: ArchetypeRow) -> ArchetypeSwapRemoveResult {
let is_last = index.0 == self.entities.len() - 1;
let entity = self.entities.swap_remove(index.0);
ArchetypeSwapRemoveResult {
swapped_entity: if is_last {
None
} else {
Some(self.entities[index].entity)
Some(self.entities[index.0].entity)
},
table_row: entity.table_row,
}

View file

@ -6,12 +6,12 @@ pub use bevy_ecs_macros::Bundle;
use crate::{
archetype::{
Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
Archetype, ArchetypeId, ArchetypeRow, Archetypes, BundleComponentStatus, ComponentStatus,
SpawnBundleStatus,
},
component::{Component, ComponentId, Components, StorageType, Tick},
entity::{Entities, Entity, EntityLocation},
storage::{SparseSetIndex, SparseSets, Storages, Table},
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::OwningPtr;
@ -379,7 +379,7 @@ impl BundleInfo {
sparse_sets: &mut SparseSets,
bundle_component_status: &S,
entity: Entity,
table_row: usize,
table_row: TableRow,
change_tick: u32,
bundle: T,
) {
@ -518,17 +518,17 @@ pub(crate) enum InsertBundleResult<'a> {
impl<'a, 'b> BundleInserter<'a, 'b> {
/// # Safety
/// `entity` must currently exist in the source archetype for this inserter. `archetype_index`
/// `entity` must currently exist in the source archetype for this inserter. `archetype_row`
/// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type
#[inline]
pub unsafe fn insert<T: Bundle>(
&mut self,
entity: Entity,
archetype_index: usize,
archetype_row: ArchetypeRow,
bundle: T,
) -> EntityLocation {
let location = EntityLocation {
index: archetype_index,
archetype_row,
archetype_id: self.archetype.id(),
};
match &mut self.result {
@ -544,14 +544,14 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
self.sparse_sets,
add_bundle,
entity,
self.archetype.entity_table_row(archetype_index),
self.archetype.entity_table_row(archetype_row),
self.change_tick,
bundle,
);
location
}
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
let result = self.archetype.swap_remove(location.index);
let result = self.archetype.swap_remove(location.archetype_row);
if let Some(swapped_entity) = result.swapped_entity {
self.entities.set(swapped_entity.index(), location);
}
@ -579,7 +579,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
new_archetype,
new_table,
} => {
let result = self.archetype.swap_remove(location.index);
let result = self.archetype.swap_remove(location.archetype_row);
if let Some(swapped_entity) = result.swapped_entity {
self.entities.set(swapped_entity.index(), location);
}
@ -607,7 +607,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
};
swapped_archetype
.set_entity_table_row(swapped_location.index, result.table_row);
.set_entity_table_row(swapped_location.archetype_row, result.table_row);
}
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)

View file

@ -34,7 +34,10 @@ mod map_entities;
pub use map_entities::*;
use crate::{archetype::ArchetypeId, storage::SparseSetIndex};
use crate::{
archetype::{ArchetypeId, ArchetypeRow},
storage::SparseSetIndex,
};
use serde::{Deserialize, Serialize};
use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering};
@ -727,7 +730,7 @@ impl EntityMeta {
generation: 0,
location: EntityLocation {
archetype_id: ArchetypeId::INVALID,
index: usize::MAX, // dummy value, to be filled in
archetype_row: ArchetypeRow::INVALID, // dummy value, to be filled in
},
};
}
@ -740,7 +743,7 @@ pub struct EntityLocation {
pub archetype_id: ArchetypeId,
/// The index of the entity in the archetype
pub index: usize,
pub archetype_row: ArchetypeRow,
}
#[cfg(test)]

View file

@ -4,7 +4,7 @@ use crate::{
component::{Component, ComponentId, ComponentStorage, ComponentTicks, StorageType, Tick},
entity::Entity,
query::{Access, DebugCheckedUnwrap, FilteredAccess},
storage::{ComponentSparseSet, Table},
storage::{ComponentSparseSet, Table, TableRow},
world::{Mut, World},
};
use bevy_ecs_macros::all_tuples;
@ -342,7 +342,7 @@ pub unsafe trait WorldQuery {
/// # Safety
/// While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure
/// that the returned value is not used in any way that would cause two `QueryItem<Self>` for the same
/// `archetype_index` or `table_row` to be alive at the same time.
/// `archetype_row` or `table_row` to be alive at the same time.
unsafe fn clone_fetch<'w>(fetch: &Self::Fetch<'w>) -> Self::Fetch<'w>;
/// Returns true if (and only if) every table of every archetype matched by this fetch contains
@ -395,7 +395,7 @@ pub unsafe trait WorldQuery {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize,
table_row: TableRow,
) -> Self::Item<'w>;
/// # Safety
@ -404,7 +404,11 @@ pub unsafe trait WorldQuery {
/// `table_row` must be in the range of the current table and archetype.
#[allow(unused_variables)]
#[inline(always)]
unsafe fn filter_fetch(fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: usize) -> bool {
unsafe fn filter_fetch(
fetch: &mut Self::Fetch<'_>,
entity: Entity,
table_row: TableRow,
) -> bool {
true
}
@ -484,7 +488,7 @@ unsafe impl WorldQuery for Entity {
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
entity: Entity,
_table_row: usize,
_table_row: TableRow,
) -> Self::Item<'w> {
entity
}
@ -595,13 +599,13 @@ unsafe impl<T: Component> WorldQuery for &T {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => fetch
.table_components
.debug_checked_unwrap()
.get(table_row)
.get(table_row.index())
.deref(),
StorageType::SparseSet => fetch
.sparse_set
@ -743,17 +747,17 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap();
Mut {
value: table_components.get(table_row).deref_mut(),
value: table_components.get(table_row.index()).deref_mut(),
ticks: Ticks {
added: added_ticks.get(table_row).deref_mut(),
changed: changed_ticks.get(table_row).deref_mut(),
added: added_ticks.get(table_row.index()).deref_mut(),
changed: changed_ticks.get(table_row.index()).deref_mut(),
change_tick: fetch.change_tick,
last_change_tick: fetch.last_change_tick,
},
@ -872,7 +876,7 @@ unsafe impl<T: WorldQuery> WorldQuery for Option<T> {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize,
table_row: TableRow,
) -> Self::Item<'w> {
fetch
.matches
@ -1082,7 +1086,7 @@ unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize,
table_row: TableRow,
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => ChangeTrackers {
@ -1091,12 +1095,12 @@ unsafe impl<T: Component> WorldQuery for ChangeTrackers<T> {
added: fetch
.table_added
.debug_checked_unwrap()
.get(table_row)
.get(table_row.index())
.read(),
changed: fetch
.table_changed
.debug_checked_unwrap()
.get(table_row)
.get(table_row.index())
.read(),
}
},
@ -1210,7 +1214,7 @@ macro_rules! impl_tuple_fetch {
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize
_table_row: TableRow
) -> Self::Item<'w> {
let ($($name,)*) = _fetch;
($($name::fetch($name, _entity, _table_row),)*)
@ -1220,7 +1224,7 @@ macro_rules! impl_tuple_fetch {
unsafe fn filter_fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize
_table_row: TableRow
) -> bool {
let ($($name,)*) = _fetch;
true $(&& $name::filter_fetch($name, _entity, _table_row))*
@ -1329,7 +1333,7 @@ macro_rules! impl_anytuple_fetch {
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize
_table_row: TableRow
) -> Self::Item<'w> {
let ($($name,)*) = _fetch;
($(
@ -1443,7 +1447,7 @@ unsafe impl<Q: WorldQuery> WorldQuery for NopWorldQuery<Q> {
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize,
_table_row: TableRow,
) -> Self::Item<'w> {
}

View file

@ -3,7 +3,7 @@ use crate::{
component::{Component, ComponentId, ComponentStorage, StorageType, Tick},
entity::Entity,
query::{Access, DebugCheckedUnwrap, FilteredAccess, WorldQuery},
storage::{Column, ComponentSparseSet, Table},
storage::{Column, ComponentSparseSet, Table, TableRow},
world::World,
};
use bevy_ecs_macros::all_tuples;
@ -85,7 +85,7 @@ unsafe impl<T: Component> WorldQuery for With<T> {
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize,
_table_row: TableRow,
) -> Self::Item<'w> {
}
@ -187,7 +187,7 @@ unsafe impl<T: Component> WorldQuery for Without<T> {
unsafe fn fetch<'w>(
_fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize,
_table_row: TableRow,
) -> Self::Item<'w> {
}
@ -330,7 +330,7 @@ macro_rules! impl_query_filter_tuple {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
_entity: Entity,
_table_row: usize
_table_row: TableRow
) -> Self::Item<'w> {
let ($($filter,)*) = fetch;
false $(|| ($filter.matches && $filter::filter_fetch(&mut $filter.fetch, _entity, _table_row)))*
@ -340,7 +340,7 @@ macro_rules! impl_query_filter_tuple {
unsafe fn filter_fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize
table_row: TableRow
) -> bool {
Self::fetch(fetch, entity, table_row)
}
@ -500,14 +500,14 @@ macro_rules! impl_tick_filter {
unsafe fn fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize
table_row: TableRow
) -> Self::Item<'w> {
match T::Storage::STORAGE_TYPE {
StorageType::Table => {
fetch
.table_ticks
.debug_checked_unwrap()
.get(table_row)
.get(table_row.index())
.deref()
.is_older_than(fetch.last_change_tick, fetch.change_tick)
}
@ -527,7 +527,7 @@ macro_rules! impl_tick_filter {
unsafe fn filter_fetch<'w>(
fetch: &mut Self::Fetch<'w>,
entity: Entity,
table_row: usize
table_row: TableRow
) -> bool {
Self::fetch(fetch, entity, table_row)
}

View file

@ -3,7 +3,7 @@ use crate::{
entity::{Entities, Entity},
prelude::World,
query::{ArchetypeFilter, DebugCheckedUnwrap, QueryState, WorldQuery},
storage::{TableId, Tables},
storage::{TableId, TableRow, Tables},
};
use std::{borrow::Borrow, iter::FusedIterator, marker::PhantomData, mem::MaybeUninit};
@ -170,11 +170,11 @@ where
table,
);
let table_row = archetype.entity_table_row(location.index);
let table_row = archetype.entity_table_row(location.archetype_row);
// SAFETY: set_archetype was called prior.
// `location.index` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
// `location.archetype_row` is an archetype index row in range of the current archetype, because if it was not, the match above would have `continue`d
if F::filter_fetch(&mut self.filter, entity, table_row) {
// SAFETY: set_archetype was called prior, `location.index` is an archetype index in range of the current archetype
// SAFETY: set_archetype was called prior, `location.archetype_row` is an archetype index in range of the current archetype
return Some(Q::fetch(&mut self.fetch, entity, table_row));
}
}
@ -464,7 +464,7 @@ struct QueryIterationCursor<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> {
// length of the table table or length of the archetype, depending on whether both `Q`'s and `F`'s fetches are dense
current_len: usize,
// either table row or archetype index, depending on whether both `Q`'s and `F`'s fetches are dense
current_index: usize,
current_row: usize,
phantom: PhantomData<Q>,
}
@ -474,7 +474,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
/// # Safety
/// While calling this method on its own cannot cause UB it is marked `unsafe` as the caller must ensure
/// that the returned value is not used in any way that would cause two `QueryItem<Q>` for the same
/// `archetype_index` or `table_row` to be alive at the same time.
/// `archetype_row` or `table_row` to be alive at the same time.
unsafe fn clone_cursor(&self) -> Self {
Self {
table_id_iter: self.table_id_iter.clone(),
@ -485,7 +485,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
fetch: Q::clone_fetch(&self.fetch),
filter: F::clone_fetch(&self.filter),
current_len: self.current_len,
current_index: self.current_index,
current_row: self.current_row,
phantom: PhantomData,
}
}
@ -533,7 +533,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
table_id_iter: query_state.matched_table_ids.iter(),
archetype_id_iter: query_state.matched_archetype_ids.iter(),
current_len: 0,
current_index: 0,
current_row: 0,
phantom: PhantomData,
}
}
@ -541,11 +541,11 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
/// retrieve item returned from most recent `next` call again.
#[inline]
unsafe fn peek_last(&mut self) -> Option<Q::Item<'w>> {
if self.current_index > 0 {
let index = self.current_index - 1;
if self.current_row > 0 {
let index = self.current_row - 1;
if Self::IS_DENSE {
let entity = self.table_entities.get_unchecked(index);
Some(Q::fetch(&mut self.fetch, *entity, index))
Some(Q::fetch(&mut self.fetch, *entity, TableRow::new(index)))
} else {
let archetype_entity = self.archetype_entities.get_unchecked(index);
Some(Q::fetch(
@ -571,7 +571,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
let ids = self.archetype_id_iter.clone();
ids.map(|id| archetypes[*id].len()).sum()
};
remaining_matched + self.current_len - self.current_index
remaining_matched + self.current_len - self.current_row
}
// NOTE: If you are changing query iteration code, remember to update the following places, where relevant:
@ -590,7 +590,7 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
if Self::IS_DENSE {
loop {
// we are on the beginning of the query, or finished processing a table, so skip to the next
if self.current_index == self.current_len {
if self.current_row == self.current_len {
let table_id = self.table_id_iter.next()?;
let table = tables.get(*table_id).debug_checked_unwrap();
// SAFETY: `table` is from the world that `fetch/filter` were created for,
@ -599,28 +599,29 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
F::set_table(&mut self.filter, &query_state.filter_state, table);
self.table_entities = table.entities();
self.current_len = table.entity_count();
self.current_index = 0;
self.current_row = 0;
continue;
}
// SAFETY: set_table was called prior.
// `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed.
let entity = self.table_entities.get_unchecked(self.current_index);
if !F::filter_fetch(&mut self.filter, *entity, self.current_index) {
self.current_index += 1;
// `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed.
let entity = self.table_entities.get_unchecked(self.current_row);
let row = TableRow::new(self.current_row);
if !F::filter_fetch(&mut self.filter, *entity, row) {
self.current_row += 1;
continue;
}
// SAFETY: set_table was called prior.
// `current_index` is a table row in range of the current table, because if it was not, then the if above would have been executed.
let item = Q::fetch(&mut self.fetch, *entity, self.current_index);
// `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed.
let item = Q::fetch(&mut self.fetch, *entity, row);
self.current_index += 1;
self.current_row += 1;
return Some(item);
}
} else {
loop {
if self.current_index == self.current_len {
if self.current_row == self.current_len {
let archetype_id = self.archetype_id_iter.next()?;
let archetype = archetypes.get(*archetype_id).debug_checked_unwrap();
// SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for,
@ -635,30 +636,30 @@ impl<'w, 's, Q: WorldQuery, F: ReadOnlyWorldQuery> QueryIterationCursor<'w, 's,
);
self.archetype_entities = archetype.entities();
self.current_len = archetype.len();
self.current_index = 0;
self.current_row = 0;
continue;
}
// SAFETY: set_archetype was called prior.
// `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
let archetype_entity = self.archetype_entities.get_unchecked(self.current_index);
// `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
let archetype_entity = self.archetype_entities.get_unchecked(self.current_row);
if !F::filter_fetch(
&mut self.filter,
archetype_entity.entity(),
archetype_entity.table_row(),
) {
self.current_index += 1;
self.current_row += 1;
continue;
}
// SAFETY: set_archetype was called prior, `current_index` is an archetype index in range of the current archetype
// `current_index` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
// SAFETY: set_archetype was called prior, `current_row` is an archetype index in range of the current archetype
// `current_row` is an archetype index row in range of the current archetype, because if it was not, then the if above would have been executed.
let item = Q::fetch(
&mut self.fetch,
archetype_entity.entity(),
archetype_entity.table_row(),
);
self.current_index += 1;
self.current_row += 1;
return Some(item);
}
}

View file

@ -6,7 +6,7 @@ use crate::{
query::{
Access, DebugCheckedUnwrap, FilteredAccess, QueryCombinationIter, QueryIter, WorldQuery,
},
storage::TableId,
storage::{TableId, TableRow},
world::{World, WorldId},
};
use bevy_tasks::ComputeTaskPool;
@ -408,7 +408,7 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
let mut fetch = Q::init_fetch(world, &self.fetch_state, last_change_tick, change_tick);
let mut filter = F::init_fetch(world, &self.filter_state, last_change_tick, change_tick);
let table_row = archetype.entity_table_row(location.index);
let table_row = archetype.entity_table_row(location.archetype_row);
let table = world
.storages()
.tables
@ -928,6 +928,7 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
let entities = table.entities();
for row in 0..table.entity_count() {
let entity = entities.get_unchecked(row);
let row = TableRow::new(row);
if !F::filter_fetch(&mut filter, *entity, row) {
continue;
}
@ -1022,6 +1023,7 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
F::set_table(&mut filter, &self.filter_state, table);
for row in offset..offset + len {
let entity = entities.get_unchecked(row);
let row = TableRow::new(row);
if !F::filter_fetch(&mut filter, *entity, row) {
continue;
}
@ -1074,8 +1076,8 @@ impl<Q: WorldQuery, F: ReadOnlyWorldQuery> QueryState<Q, F> {
F::set_archetype(&mut filter, &self.filter_state, archetype, table);
let entities = archetype.entities();
for archetype_index in offset..offset + len {
let archetype_entity = entities.get_unchecked(archetype_index);
for archetype_row in offset..offset + len {
let archetype_entity = entities.get_unchecked(archetype_row);
if !F::filter_fetch(
&mut filter,
archetype_entity.entity(),

View file

@ -1,6 +1,6 @@
use crate::archetype::ArchetypeComponentId;
use crate::component::{ComponentId, ComponentTicks, Components, TickCells};
use crate::storage::{Column, SparseSet};
use crate::storage::{Column, SparseSet, TableRow};
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
/// The type-erased backing storage and metadata for a single resource within a [`World`].
@ -12,6 +12,9 @@ pub struct ResourceData {
}
impl ResourceData {
/// The only row in the underlying column.
const ROW: TableRow = TableRow::new(0);
/// Returns true if the resource is populated.
#[inline]
pub fn is_present(&self) -> bool {
@ -27,18 +30,18 @@ impl ResourceData {
/// Gets a read-only pointer to the underlying resource, if available.
#[inline]
pub fn get_data(&self) -> Option<Ptr<'_>> {
self.column.get_data(0)
self.column.get_data(Self::ROW)
}
/// Gets a read-only reference to the change ticks of the underlying resource, if available.
#[inline]
pub fn get_ticks(&self) -> Option<ComponentTicks> {
self.column.get_ticks(0)
self.column.get_ticks(Self::ROW)
}
#[inline]
pub(crate) fn get_with_ticks(&self) -> Option<(Ptr<'_>, TickCells<'_>)> {
self.column.get(0)
self.column.get(Self::ROW)
}
/// Inserts a value into the resource. If a value is already present
@ -54,7 +57,7 @@ impl ResourceData {
#[inline]
pub(crate) unsafe fn insert(&mut self, value: OwningPtr<'_>, change_tick: u32) {
if self.is_present() {
self.column.replace(0, value, change_tick);
self.column.replace(Self::ROW, value, change_tick);
} else {
self.column.push(value, ComponentTicks::new(change_tick));
}
@ -77,9 +80,12 @@ impl ResourceData {
change_ticks: ComponentTicks,
) {
if self.is_present() {
self.column.replace_untracked(0, value);
*self.column.get_added_ticks_unchecked(0).deref_mut() = change_ticks.added;
*self.column.get_changed_ticks_unchecked(0).deref_mut() = change_ticks.changed;
self.column.replace_untracked(Self::ROW, value);
*self.column.get_added_ticks_unchecked(Self::ROW).deref_mut() = change_ticks.added;
*self
.column
.get_changed_ticks_unchecked(Self::ROW)
.deref_mut() = change_ticks.changed;
} else {
self.column.push(value, change_ticks);
}
@ -97,7 +103,7 @@ impl ResourceData {
#[inline]
#[must_use = "The returned pointer to the removed component should be used or dropped"]
pub(crate) unsafe fn remove(&mut self) -> Option<(OwningPtr<'_>, ComponentTicks)> {
self.column.swap_remove_and_forget(0)
self.column.swap_remove_and_forget(Self::ROW)
}
/// Removes a value from the resource, if present, and drops it.

View file

@ -1,7 +1,7 @@
use crate::{
component::{ComponentId, ComponentInfo, ComponentTicks, Tick, TickCells},
entity::Entity,
storage::Column,
storage::{Column, TableRow},
};
use bevy_ptr::{OwningPtr, Ptr};
use std::{cell::UnsafeCell, hash::Hash, marker::PhantomData};
@ -147,7 +147,8 @@ impl ComponentSparseSet {
if let Some(&dense_index) = self.sparse.get(entity.index()) {
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index as usize]);
self.dense.replace(dense_index as usize, value, change_tick);
self.dense
.replace(TableRow::new(dense_index as usize), value, change_tick);
} else {
let dense_index = self.dense.len();
self.dense.push(value, ComponentTicks::new(change_tick));
@ -180,19 +181,19 @@ impl ComponentSparseSet {
#[inline]
pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> {
self.sparse.get(entity.index()).map(|dense_index| {
let dense_index = *dense_index as usize;
let dense_index = (*dense_index) as usize;
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.get_data_unchecked(dense_index) }
unsafe { self.dense.get_data_unchecked(TableRow::new(dense_index)) }
})
}
#[inline]
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> {
let dense_index = *self.sparse.get(entity.index())? as usize;
let dense_index = TableRow::new(*self.sparse.get(entity.index())? as usize);
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
assert_eq!(entity, self.entities[dense_index.index()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe {
Some((
@ -211,7 +212,12 @@ impl ComponentSparseSet {
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_added_ticks_unchecked(dense_index)) }
unsafe {
Some(
self.dense
.get_added_ticks_unchecked(TableRow::new(dense_index)),
)
}
}
#[inline]
@ -220,7 +226,12 @@ impl ComponentSparseSet {
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_changed_ticks_unchecked(dense_index)) }
unsafe {
Some(
self.dense
.get_changed_ticks_unchecked(TableRow::new(dense_index)),
)
}
}
#[inline]
@ -229,7 +240,7 @@ impl ComponentSparseSet {
#[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]);
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) }
unsafe { Some(self.dense.get_ticks_unchecked(TableRow::new(dense_index))) }
}
/// Removes the `entity` from this sparse set and returns a pointer to the associated value (if
@ -243,7 +254,10 @@ impl ComponentSparseSet {
self.entities.swap_remove(dense_index);
let is_last = dense_index == self.dense.len() - 1;
// SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid
let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
let (value, _) = unsafe {
self.dense
.swap_remove_and_forget_unchecked(TableRow::new(dense_index))
};
if !is_last {
let swapped_entity = self.entities[dense_index];
#[cfg(not(debug_assertions))]
@ -264,7 +278,7 @@ impl ComponentSparseSet {
self.entities.swap_remove(dense_index);
let is_last = dense_index == self.dense.len() - 1;
// SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.swap_remove_unchecked(dense_index) }
unsafe { self.dense.swap_remove_unchecked(TableRow::new(dense_index)) }
if !is_last {
let swapped_entity = self.entities[dense_index];
#[cfg(not(debug_assertions))]

View file

@ -32,6 +32,39 @@ impl TableId {
}
}
/// A opaque newtype for rows in [`Table`]s. Specifies a single row in a specific table.
///
/// Values of this type are retreivable from [`Archetype::entity_table_row`] and can be
/// used alongside [`Archetype::table_id`] to fetch the exact table and row where an
/// [`Entity`]'s
///
/// Values of this type are only valid so long as entities have not moved around.
/// Adding and removing components from an entity, or despawning it will invalidate
/// potentially any table row in the table the entity was previously stored in. Users
/// should *always* fetch the approripate row from the entity's [`Archetype`] before
/// fetching the entity's components.
///
/// [`Archetype`]: crate::archetype::Archetype
/// [`Archetype::entity_table_row`]: crate::archetype::Archetype::entity_table_row
/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id
/// [`Entity`]: crate::entity::Entity
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TableRow(usize);
impl TableRow {
/// Creates a `TableRow`.
#[inline]
pub const fn new(index: usize) -> Self {
Self(index)
}
/// Gets the index of the row.
#[inline]
pub const fn index(self) -> usize {
self.0
}
}
#[derive(Debug)]
pub struct Column {
data: BlobVec,
@ -62,11 +95,11 @@ impl Column {
/// # Safety
/// Assumes data has already been allocated for the given row.
#[inline]
pub(crate) unsafe fn initialize(&mut self, row: usize, data: OwningPtr<'_>, tick: Tick) {
debug_assert!(row < self.len());
self.data.initialize_unchecked(row, data);
*self.added_ticks.get_unchecked_mut(row).get_mut() = tick;
*self.changed_ticks.get_unchecked_mut(row).get_mut() = tick;
pub(crate) unsafe fn initialize(&mut self, row: TableRow, data: OwningPtr<'_>, tick: Tick) {
debug_assert!(row.index() < self.len());
self.data.initialize_unchecked(row.index(), data);
*self.added_ticks.get_unchecked_mut(row.index()).get_mut() = tick;
*self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = tick;
}
/// Writes component data to the column at given row.
@ -75,11 +108,11 @@ impl Column {
/// # Safety
/// Assumes data has already been allocated for the given row.
#[inline]
pub(crate) unsafe fn replace(&mut self, row: usize, data: OwningPtr<'_>, change_tick: u32) {
debug_assert!(row < self.len());
self.data.replace_unchecked(row, data);
pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: u32) {
debug_assert!(row.index() < self.len());
self.data.replace_unchecked(row.index(), data);
self.changed_ticks
.get_unchecked_mut(row)
.get_unchecked_mut(row.index())
.get_mut()
.set_changed(change_tick);
}
@ -91,9 +124,9 @@ impl Column {
/// # Safety
/// Assumes data has already been allocated for the given row.
#[inline]
pub(crate) unsafe fn replace_untracked(&mut self, row: usize, data: OwningPtr<'_>) {
debug_assert!(row < self.len());
self.data.replace_unchecked(row, data);
pub(crate) unsafe fn replace_untracked(&mut self, row: TableRow, data: OwningPtr<'_>) {
debug_assert!(row.index() < self.len());
self.data.replace_unchecked(row.index(), data);
}
#[inline]
@ -109,23 +142,23 @@ impl Column {
/// # Safety
/// index must be in-bounds
#[inline]
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) {
self.data.swap_remove_and_drop_unchecked(row);
self.added_ticks.swap_remove(row);
self.changed_ticks.swap_remove(row);
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) {
self.data.swap_remove_and_drop_unchecked(row.index());
self.added_ticks.swap_remove(row.index());
self.changed_ticks.swap_remove(row.index());
}
#[inline]
#[must_use = "The returned pointer should be used to drop the removed component"]
pub(crate) fn swap_remove_and_forget(
&mut self,
row: usize,
row: TableRow,
) -> Option<(OwningPtr<'_>, ComponentTicks)> {
(row < self.data.len()).then(|| {
(row.index() < self.data.len()).then(|| {
// SAFETY: The row was length checked before this.
let data = unsafe { self.data.swap_remove_and_forget_unchecked(row) };
let added = self.added_ticks.swap_remove(row).into_inner();
let changed = self.changed_ticks.swap_remove(row).into_inner();
let data = unsafe { self.data.swap_remove_and_forget_unchecked(row.index()) };
let added = self.added_ticks.swap_remove(row.index()).into_inner();
let changed = self.changed_ticks.swap_remove(row.index()).into_inner();
(data, ComponentTicks { added, changed })
})
}
@ -136,11 +169,11 @@ impl Column {
#[must_use = "The returned pointer should be used to dropped the removed component"]
pub(crate) unsafe fn swap_remove_and_forget_unchecked(
&mut self,
row: usize,
row: TableRow,
) -> (OwningPtr<'_>, ComponentTicks) {
let data = self.data.swap_remove_and_forget_unchecked(row);
let added = self.added_ticks.swap_remove(row).into_inner();
let changed = self.changed_ticks.swap_remove(row).into_inner();
let data = self.data.swap_remove_and_forget_unchecked(row.index());
let added = self.added_ticks.swap_remove(row.index()).into_inner();
let changed = self.changed_ticks.swap_remove(row.index()).into_inner();
(data, ComponentTicks { added, changed })
}
@ -159,14 +192,16 @@ impl Column {
pub(crate) unsafe fn initialize_from_unchecked(
&mut self,
other: &mut Column,
src_row: usize,
dst_row: usize,
src_row: TableRow,
dst_row: TableRow,
) {
debug_assert!(self.data.layout() == other.data.layout());
let ptr = self.data.get_unchecked_mut(dst_row);
other.data.swap_remove_unchecked(src_row, ptr);
*self.added_ticks.get_unchecked_mut(dst_row) = other.added_ticks.swap_remove(src_row);
*self.changed_ticks.get_unchecked_mut(dst_row) = other.changed_ticks.swap_remove(src_row);
let ptr = self.data.get_unchecked_mut(dst_row.index());
other.data.swap_remove_unchecked(src_row.index(), ptr);
*self.added_ticks.get_unchecked_mut(dst_row.index()) =
other.added_ticks.swap_remove(src_row.index());
*self.changed_ticks.get_unchecked_mut(dst_row.index()) =
other.changed_ticks.swap_remove(src_row.index());
}
// # Safety
@ -206,66 +241,66 @@ impl Column {
}
#[inline]
pub fn get(&self, row: usize) -> Option<(Ptr<'_>, TickCells<'_>)> {
(row < self.data.len())
pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> {
(row.index() < self.data.len())
// SAFETY: The row is length checked before fetching the pointer. This is being
// accessed through a read-only reference to the column.
.then(|| unsafe {
(
self.data.get_unchecked(row),
self.data.get_unchecked(row.index()),
TickCells {
added: self.added_ticks.get_unchecked(row),
changed: self.changed_ticks.get_unchecked(row),
added: self.added_ticks.get_unchecked(row.index()),
changed: self.changed_ticks.get_unchecked(row.index()),
},
)
})
}
#[inline]
pub fn get_data(&self, row: usize) -> Option<Ptr<'_>> {
pub fn get_data(&self, row: TableRow) -> Option<Ptr<'_>> {
// SAFETY: The row is length checked before fetching the pointer. This is being
// accessed through a read-only reference to the column.
(row < self.data.len()).then(|| unsafe { self.data.get_unchecked(row) })
(row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked(row.index()) })
}
/// # Safety
/// - index must be in-bounds
/// - no other reference to the data of the same row can exist at the same time
#[inline]
pub unsafe fn get_data_unchecked(&self, row: usize) -> Ptr<'_> {
debug_assert!(row < self.data.len());
self.data.get_unchecked(row)
pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> {
debug_assert!(row.index() < self.data.len());
self.data.get_unchecked(row.index())
}
#[inline]
pub fn get_data_mut(&mut self, row: usize) -> Option<PtrMut<'_>> {
pub fn get_data_mut(&mut self, row: TableRow) -> Option<PtrMut<'_>> {
// SAFETY: The row is length checked before fetching the pointer. This is being
// accessed through an exclusive reference to the column.
(row < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row) })
(row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row.index()) })
}
/// # Safety
/// - index must be in-bounds
/// - no other reference to the data of the same row can exist at the same time
#[inline]
pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: usize) -> PtrMut<'_> {
debug_assert!(row < self.data.len());
self.data.get_unchecked_mut(row)
pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: TableRow) -> PtrMut<'_> {
debug_assert!(row.index() < self.data.len());
self.data.get_unchecked_mut(row.index())
}
#[inline]
pub fn get_added_ticks(&self, row: usize) -> Option<&UnsafeCell<Tick>> {
self.added_ticks.get(row)
pub fn get_added_ticks(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
self.added_ticks.get(row.index())
}
#[inline]
pub fn get_changed_ticks(&self, row: usize) -> Option<&UnsafeCell<Tick>> {
self.changed_ticks.get(row)
pub fn get_changed_ticks(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
self.changed_ticks.get(row.index())
}
#[inline]
pub fn get_ticks(&self, row: usize) -> Option<ComponentTicks> {
if row < self.data.len() {
pub fn get_ticks(&self, row: TableRow) -> Option<ComponentTicks> {
if row.index() < self.data.len() {
// SAFETY: The size of the column has already been checked.
Some(unsafe { self.get_ticks_unchecked(row) })
} else {
@ -276,28 +311,28 @@ impl Column {
/// # Safety
/// index must be in-bounds
#[inline]
pub unsafe fn get_added_ticks_unchecked(&self, row: usize) -> &UnsafeCell<Tick> {
debug_assert!(row < self.added_ticks.len());
self.added_ticks.get_unchecked(row)
pub unsafe fn get_added_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
debug_assert!(row.index() < self.added_ticks.len());
self.added_ticks.get_unchecked(row.index())
}
/// # Safety
/// index must be in-bounds
#[inline]
pub unsafe fn get_changed_ticks_unchecked(&self, row: usize) -> &UnsafeCell<Tick> {
debug_assert!(row < self.changed_ticks.len());
self.changed_ticks.get_unchecked(row)
pub unsafe fn get_changed_ticks_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
debug_assert!(row.index() < self.changed_ticks.len());
self.changed_ticks.get_unchecked(row.index())
}
/// # Safety
/// index must be in-bounds
#[inline]
pub unsafe fn get_ticks_unchecked(&self, row: usize) -> ComponentTicks {
debug_assert!(row < self.added_ticks.len());
debug_assert!(row < self.changed_ticks.len());
pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks {
debug_assert!(row.index() < self.added_ticks.len());
debug_assert!(row.index() < self.changed_ticks.len());
ComponentTicks {
added: self.added_ticks.get_unchecked(row).read(),
changed: self.changed_ticks.get_unchecked(row).read(),
added: self.added_ticks.get_unchecked(row.index()).read(),
changed: self.changed_ticks.get_unchecked(row.index()).read(),
}
}
@ -385,16 +420,16 @@ impl Table {
///
/// # Safety
/// `row` must be in-bounds
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: usize) -> Option<Entity> {
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option<Entity> {
for column in self.columns.values_mut() {
column.swap_remove_unchecked(row);
}
let is_last = row == self.entities.len() - 1;
self.entities.swap_remove(row);
let is_last = row.index() == self.entities.len() - 1;
self.entities.swap_remove(row.index());
if is_last {
None
} else {
Some(self.entities[row])
Some(self.entities[row.index()])
}
}
@ -407,12 +442,12 @@ impl Table {
/// Row must be in-bounds
pub(crate) unsafe fn move_to_and_forget_missing_unchecked(
&mut self,
row: usize,
row: TableRow,
new_table: &mut Table,
) -> TableMoveResult {
debug_assert!(row < self.entity_count());
let is_last = row == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row));
debug_assert!(row.index() < self.entity_count());
let is_last = row.index() == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row.index()));
for (component_id, column) in self.columns.iter_mut() {
if let Some(new_column) = new_table.get_column_mut(*component_id) {
new_column.initialize_from_unchecked(column, row, new_row);
@ -426,7 +461,7 @@ impl Table {
swapped_entity: if is_last {
None
} else {
Some(self.entities[row])
Some(self.entities[row.index()])
},
}
}
@ -439,12 +474,12 @@ impl Table {
/// row must be in-bounds
pub(crate) unsafe fn move_to_and_drop_missing_unchecked(
&mut self,
row: usize,
row: TableRow,
new_table: &mut Table,
) -> TableMoveResult {
debug_assert!(row < self.entity_count());
let is_last = row == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row));
debug_assert!(row.index() < self.entity_count());
let is_last = row.index() == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row.index()));
for (component_id, column) in self.columns.iter_mut() {
if let Some(new_column) = new_table.get_column_mut(*component_id) {
new_column.initialize_from_unchecked(column, row, new_row);
@ -457,7 +492,7 @@ impl Table {
swapped_entity: if is_last {
None
} else {
Some(self.entities[row])
Some(self.entities[row.index()])
},
}
}
@ -470,12 +505,12 @@ impl Table {
/// `row` must be in-bounds. `new_table` must contain every component this table has
pub(crate) unsafe fn move_to_superset_unchecked(
&mut self,
row: usize,
row: TableRow,
new_table: &mut Table,
) -> TableMoveResult {
debug_assert!(row < self.entity_count());
let is_last = row == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row));
debug_assert!(row.index() < self.entity_count());
let is_last = row.index() == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row.index()));
for (component_id, column) in self.columns.iter_mut() {
new_table
.get_column_mut(*component_id)
@ -487,7 +522,7 @@ impl Table {
swapped_entity: if is_last {
None
} else {
Some(self.entities[row])
Some(self.entities[row.index()])
},
}
}
@ -524,7 +559,7 @@ impl Table {
///
/// # Safety
/// the allocated row must be written to immediately with valid values in each column
pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> usize {
pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow {
self.reserve(1);
let index = self.entities.len();
self.entities.push(entity);
@ -533,7 +568,7 @@ impl Table {
column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
}
index
TableRow(index)
}
#[inline]
@ -594,7 +629,7 @@ impl Default for Tables {
pub(crate) struct TableMoveResult {
pub swapped_entity: Option<Entity>,
pub new_row: usize,
pub new_row: TableRow,
}
impl Tables {
@ -690,7 +725,7 @@ mod tests {
use crate::{
component::{Components, Tick},
entity::Entity,
storage::TableBuilder,
storage::{TableBuilder, TableRow},
};
#[derive(Component)]
struct W<T>(T);
@ -699,7 +734,7 @@ mod tests {
fn table() {
let mut components = Components::default();
let mut storages = Storages::default();
let component_id = components.init_component::<W<usize>>(&mut storages);
let component_id = components.init_component::<W<TableRow>>(&mut storages);
let columns = &[component_id];
let mut builder = TableBuilder::with_capacity(0, columns.len());
builder.add_column(components.get_info(component_id).unwrap());
@ -709,7 +744,7 @@ mod tests {
// SAFETY: we allocate and immediately set data afterwards
unsafe {
let row = table.allocate(*entity);
let value: W<usize> = W(row);
let value: W<TableRow> = W(row);
OwningPtr::make(value, |value_ptr| {
table.get_column_mut(component_id).unwrap().initialize(
row,

View file

@ -7,7 +7,7 @@ use crate::{
TickCells,
},
entity::{Entities, Entity, EntityLocation},
storage::{Column, ComponentSparseSet, SparseSet, Storages},
storage::{Column, ComponentSparseSet, SparseSet, Storages, TableRow},
world::{Mut, World},
};
use bevy_ptr::{OwningPtr, Ptr};
@ -371,7 +371,8 @@ impl<'w> EntityMut<'w> {
);
// SAFETY: location matches current entity. `T` matches `bundle_info`
unsafe {
self.location = bundle_inserter.insert(self.entity, self.location.index, bundle);
self.location =
bundle_inserter.insert(self.entity, self.location.archetype_row, bundle);
}
self
@ -465,7 +466,7 @@ impl<'w> EntityMut<'w> {
new_archetype_id: ArchetypeId,
) {
let old_archetype = &mut archetypes[old_archetype_id];
let remove_result = old_archetype.swap_remove(old_location.index);
let remove_result = old_archetype.swap_remove(old_location.archetype_row);
if let Some(swapped_entity) = remove_result.swapped_entity {
entities.set(swapped_entity.index(), old_location);
}
@ -494,7 +495,7 @@ impl<'w> EntityMut<'w> {
if let Some(swapped_entity) = move_result.swapped_entity {
let swapped_location = entities.get(swapped_entity).unwrap();
archetypes[swapped_location.archetype_id]
.set_entity_table_row(swapped_location.index, old_table_row);
.set_entity_table_row(swapped_location.archetype_row, old_table_row);
}
new_location
@ -588,7 +589,7 @@ impl<'w> EntityMut<'w> {
.get_or_insert_with(component_id, Vec::new);
removed_components.push(self.entity);
}
let remove_result = archetype.swap_remove(location.index);
let remove_result = archetype.swap_remove(location.archetype_row);
if let Some(swapped_entity) = remove_result.swapped_entity {
// SAFETY: swapped_entity is valid and the swapped entity's components are
// moved to the new location immediately after.
@ -611,7 +612,7 @@ impl<'w> EntityMut<'w> {
if let Some(moved_entity) = moved_entity {
let moved_location = world.entities.get(moved_entity).unwrap();
world.archetypes[moved_location.archetype_id]
.set_entity_table_row(moved_location.index, table_row);
.set_entity_table_row(moved_location.archetype_row, table_row);
}
}
@ -701,11 +702,11 @@ fn fetch_table(
world: &World,
location: EntityLocation,
component_id: ComponentId,
) -> Option<(&Column, usize)> {
) -> Option<(&Column, TableRow)> {
let archetype = &world.archetypes[location.archetype_id];
let table = &world.storages.tables[archetype.table_id()];
let components = table.get_column(component_id)?;
let table_row = archetype.entity_table_row(location.index);
let table_row = archetype.entity_table_row(location.archetype_row);
Some((components, table_row))
}
@ -823,7 +824,7 @@ unsafe fn take_component<'a>(
let table = &mut storages.tables[archetype.table_id()];
// SAFETY: archetypes will always point to valid columns
let components = table.get_column_mut(component_id).unwrap();
let table_row = archetype.entity_table_row(location.index);
let table_row = archetype.entity_table_row(location.archetype_row);
// SAFETY: archetypes only store valid table_rows and the stored component type is T
components.get_data_unchecked_mut(table_row).promote()
}

View file

@ -8,7 +8,7 @@ pub use spawn_batch::*;
pub use world_cell::*;
use crate::{
archetype::{ArchetypeComponentId, ArchetypeId, Archetypes},
archetype::{ArchetypeComponentId, ArchetypeId, ArchetypeRow, Archetypes},
bundle::{Bundle, BundleInserter, BundleSpawner, Bundles},
change_detection::{MutUntyped, Ticks},
component::{
@ -329,10 +329,10 @@ impl World {
.entities()
.iter()
.enumerate()
.map(|(index, archetype_entity)| {
.map(|(archetype_row, archetype_entity)| {
let location = EntityLocation {
archetype_id: archetype.id(),
index,
archetype_row: ArchetypeRow::new(archetype_row),
};
EntityRef::new(self, archetype_entity.entity(), location)
})
@ -1096,7 +1096,7 @@ impl World {
if location.archetype_id == archetype =>
{
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { inserter.insert(entity, location.index, bundle) };
unsafe { inserter.insert(entity, location.archetype_row, bundle) };
}
_ => {
let mut inserter = bundle_info.get_bundle_inserter(
@ -1108,7 +1108,7 @@ impl World {
change_tick,
);
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
unsafe { inserter.insert(entity, location.index, bundle) };
unsafe { inserter.insert(entity, location.archetype_row, bundle) };
spawn_or_insert =
SpawnOrInsert::Insert(inserter, location.archetype_id);
}