Reduced TableRow as Casting (#10811)

# Objective

- Fixes #10806

## Solution

Replaced `new` and `index` methods for both `TableRow` and `TableId`
with `from_*` and `as_*` methods. These remove the need to perform
casting at call sites, reducing the total number of casts in the Bevy
codebase. Within these methods, an appropriate `debug_assertion` ensures
the cast will behave in an expected manner (no wrapping, etc.). I am
using a `debug_assertion` instead of an `assert` to reduce any possible
runtime overhead, however minimal. This choice is something I am open to
changing (or leaving up to another PR) if anyone has any strong
arguments for it.

---

## Changelog

- `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32`
(private change)
- Replaced `TableRow::new` and `TableRow::index` methods with
`TableRow::from_*` and `TableRow::as_*`, with `debug_assertions`
protecting any internal casting.
- Replaced `TableId::new` and `TableId::index` methods with
`TableId::from_*` and `TableId::as_*`, with `debug_assertions`
protecting any internal casting.
- All `TableId` methods are now `const`

## Migration Guide

- `TableRow::new` -> `TableRow::from_usize`
- `TableRow::index` -> `TableRow::as_usize`
- `TableId::new` -> `TableId::from_usize`
- `TableId::index` -> `TableId::as_usize`

---

## Notes

I have chosen to remove the `index` and `new` methods for the following
chain of reasoning:

- Across the codebase, `new` was called with a mixture of `u32` and
`usize` values. Likewise for `index`.
- Choosing `new` to either be `usize` or `u32` would break half of these
call-sites, requiring `as` casting at the site.
- Adding a second method `new_u32` or `new_usize` avoids the above, bu
looks visually inconsistent.
- Therefore, they should be replaced with `from_*` and `as_*` methods
instead.

Worth noting is that by updating `ComponentSparseSet`, there are now
zero instances of interacting with the inner value of `TableRow` as a
`u32`, it is exclusively used as a `usize` value (due to interactions
with methods like `len` and slice indexing). I have left the `as_u32`
and `from_u32` methods as the "proper" constructors/getters.
This commit is contained in:
Zachary Harrold 2023-12-05 13:44:33 +11:00 committed by GitHub
parent 83ee6de1da
commit 72adf2ae2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 195 additions and 146 deletions

View File

@ -605,7 +605,7 @@ unsafe impl<T: Component> WorldQuery for &T {
StorageType::Table => fetch StorageType::Table => fetch
.table_components .table_components
.debug_checked_unwrap() .debug_checked_unwrap()
.get(table_row.index()) .get(table_row.as_usize())
.deref(), .deref(),
StorageType::SparseSet => fetch StorageType::SparseSet => fetch
.sparse_set .sparse_set
@ -760,10 +760,10 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
let (table_components, added_ticks, changed_ticks) = let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap(); fetch.table_data.debug_checked_unwrap();
Ref { Ref {
value: table_components.get(table_row.index()).deref(), value: table_components.get(table_row.as_usize()).deref(),
ticks: Ticks { ticks: Ticks {
added: added_ticks.get(table_row.index()).deref(), added: added_ticks.get(table_row.as_usize()).deref(),
changed: changed_ticks.get(table_row.index()).deref(), changed: changed_ticks.get(table_row.as_usize()).deref(),
this_run: fetch.this_run, this_run: fetch.this_run,
last_run: fetch.last_run, last_run: fetch.last_run,
}, },
@ -927,10 +927,10 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
let (table_components, added_ticks, changed_ticks) = let (table_components, added_ticks, changed_ticks) =
fetch.table_data.debug_checked_unwrap(); fetch.table_data.debug_checked_unwrap();
Mut { Mut {
value: table_components.get(table_row.index()).deref_mut(), value: table_components.get(table_row.as_usize()).deref_mut(),
ticks: TicksMut { ticks: TicksMut {
added: added_ticks.get(table_row.index()).deref_mut(), added: added_ticks.get(table_row.as_usize()).deref_mut(),
changed: changed_ticks.get(table_row.index()).deref_mut(), changed: changed_ticks.get(table_row.as_usize()).deref_mut(),
this_run: fetch.this_run, this_run: fetch.this_run,
last_run: fetch.last_run, last_run: fetch.last_run,
}, },

View File

@ -626,7 +626,7 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
StorageType::Table => fetch StorageType::Table => fetch
.table_ticks .table_ticks
.debug_checked_unwrap() .debug_checked_unwrap()
.get(table_row.index()) .get(table_row.as_usize())
.deref() .deref()
.is_newer_than(fetch.last_run, fetch.this_run), .is_newer_than(fetch.last_run, fetch.this_run),
StorageType::SparseSet => { StorageType::SparseSet => {
@ -802,7 +802,7 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
StorageType::Table => fetch StorageType::Table => fetch
.table_ticks .table_ticks
.debug_checked_unwrap() .debug_checked_unwrap()
.get(table_row.index()) .get(table_row.as_usize())
.deref() .deref()
.is_newer_than(fetch.last_run, fetch.this_run), .is_newer_than(fetch.last_run, fetch.this_run),
StorageType::SparseSet => { StorageType::SparseSet => {

View File

@ -106,6 +106,11 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> {
where where
Func: FnMut(B, Q::Item<'w>) -> B, Func: FnMut(B, Q::Item<'w>) -> B,
{ {
assert!(
rows.end <= u32::MAX as usize,
"TableRow is only valid up to u32::MAX"
);
Q::set_table(&mut self.cursor.fetch, &self.query_state.fetch_state, table); Q::set_table(&mut self.cursor.fetch, &self.query_state.fetch_state, table);
F::set_table( F::set_table(
&mut self.cursor.filter, &mut self.cursor.filter,
@ -117,7 +122,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> {
for row in rows { for row in rows {
// SAFETY: Caller assures `row` in range of the current archetype. // SAFETY: Caller assures `row` in range of the current archetype.
let entity = entities.get_unchecked(row); let entity = entities.get_unchecked(row);
let row = TableRow::new(row); let row = TableRow::from_usize(row);
// SAFETY: set_table was called prior. // SAFETY: set_table was called prior.
// Caller assures `row` in range of the current archetype. // Caller assures `row` in range of the current archetype.
if !F::filter_fetch(&mut self.cursor.filter, *entity, row) { if !F::filter_fetch(&mut self.cursor.filter, *entity, row) {
@ -707,7 +712,11 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIterationCursor<'w, 's
let index = self.current_row - 1; let index = self.current_row - 1;
if Self::IS_DENSE { if Self::IS_DENSE {
let entity = self.table_entities.get_unchecked(index); let entity = self.table_entities.get_unchecked(index);
Some(Q::fetch(&mut self.fetch, *entity, TableRow::new(index))) Some(Q::fetch(
&mut self.fetch,
*entity,
TableRow::from_usize(index),
))
} else { } else {
let archetype_entity = self.archetype_entities.get_unchecked(index); let archetype_entity = self.archetype_entities.get_unchecked(index);
Some(Q::fetch( Some(Q::fetch(
@ -768,7 +777,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIterationCursor<'w, 's
// SAFETY: set_table was called prior. // SAFETY: set_table was called prior.
// `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. // `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 entity = self.table_entities.get_unchecked(self.current_row);
let row = TableRow::new(self.current_row); let row = TableRow::from_usize(self.current_row);
if !F::filter_fetch(&mut self.filter, *entity, row) { if !F::filter_fetch(&mut self.filter, *entity, row) {
self.current_row += 1; self.current_row += 1;
continue; continue;

View File

@ -267,7 +267,7 @@ impl<Q: WorldQueryData, F: WorldQueryFilter> QueryState<Q, F> {
self.matched_archetypes.set(archetype_index, true); self.matched_archetypes.set(archetype_index, true);
self.matched_archetype_ids.push(archetype.id()); self.matched_archetype_ids.push(archetype.id());
} }
let table_index = archetype.table_id().index(); let table_index = archetype.table_id().as_usize();
if !self.matched_tables.contains(table_index) { if !self.matched_tables.contains(table_index) {
self.matched_tables.grow(table_index + 1); self.matched_tables.grow(table_index + 1);
self.matched_tables.set(table_index, true); self.matched_tables.set(table_index, true);

View File

@ -40,7 +40,7 @@ impl<const SEND: bool> Drop for ResourceData<SEND> {
impl<const SEND: bool> ResourceData<SEND> { impl<const SEND: bool> ResourceData<SEND> {
/// The only row in the underlying column. /// The only row in the underlying column.
const ROW: TableRow = TableRow::new(0); const ROW: TableRow = TableRow::from_u32(0);
/// Validates the access to `!Send` resources is only done on the thread they were created from. /// Validates the access to `!Send` resources is only done on the thread they were created from.
/// ///

View File

@ -124,7 +124,7 @@ pub struct ComponentSparseSet {
entities: Vec<EntityIndex>, entities: Vec<EntityIndex>,
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
entities: Vec<Entity>, entities: Vec<Entity>,
sparse: SparseArray<EntityIndex, u32>, sparse: SparseArray<EntityIndex, TableRow>,
} }
impl ComponentSparseSet { impl ComponentSparseSet {
@ -171,13 +171,13 @@ impl ComponentSparseSet {
) { ) {
if let Some(&dense_index) = self.sparse.get(entity.index()) { if let Some(&dense_index) = self.sparse.get(entity.index()) {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index as usize]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
self.dense self.dense.replace(dense_index, value, change_tick);
.replace(TableRow::new(dense_index as usize), value, change_tick);
} else { } else {
let dense_index = self.dense.len(); let dense_index = self.dense.len();
self.dense.push(value, ComponentTicks::new(change_tick)); self.dense.push(value, ComponentTicks::new(change_tick));
self.sparse.insert(entity.index(), dense_index as u32); self.sparse
.insert(entity.index(), TableRow::from_usize(dense_index));
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(self.entities.len(), dense_index); assert_eq!(self.entities.len(), dense_index);
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
@ -194,7 +194,7 @@ impl ComponentSparseSet {
{ {
if let Some(&dense_index) = self.sparse.get(entity.index()) { if let Some(&dense_index) = self.sparse.get(entity.index()) {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index as usize]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
true true
} else { } else {
false false
@ -209,12 +209,11 @@ impl ComponentSparseSet {
/// Returns `None` if `entity` does not have a component in the sparse set. /// Returns `None` if `entity` does not have a component in the sparse set.
#[inline] #[inline]
pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> { pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> {
self.sparse.get(entity.index()).map(|dense_index| { self.sparse.get(entity.index()).map(|&dense_index| {
let dense_index = (*dense_index) as usize;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.get_data_unchecked(TableRow::new(dense_index)) } unsafe { self.dense.get_data_unchecked(dense_index) }
}) })
} }
@ -223,9 +222,9 @@ impl ComponentSparseSet {
/// Returns `None` if `entity` does not have a component in the sparse set. /// Returns `None` if `entity` does not have a component in the sparse set.
#[inline] #[inline]
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> { pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> {
let dense_index = TableRow::new(*self.sparse.get(entity.index())? as usize); let dense_index = *self.sparse.get(entity.index())?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index.index()]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { unsafe {
Some(( Some((
@ -243,16 +242,11 @@ impl ComponentSparseSet {
/// Returns `None` if `entity` does not have a component in the sparse set. /// Returns `None` if `entity` does not have a component in the sparse set.
#[inline] #[inline]
pub fn get_added_tick(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> { pub fn get_added_tick(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
let dense_index = *self.sparse.get(entity.index())? as usize; let dense_index = *self.sparse.get(entity.index())?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { unsafe { Some(self.dense.get_added_tick_unchecked(dense_index)) }
Some(
self.dense
.get_added_tick_unchecked(TableRow::new(dense_index)),
)
}
} }
/// Returns a reference to the "changed" tick of the entity's component value. /// Returns a reference to the "changed" tick of the entity's component value.
@ -260,16 +254,11 @@ impl ComponentSparseSet {
/// Returns `None` if `entity` does not have a component in the sparse set. /// Returns `None` if `entity` does not have a component in the sparse set.
#[inline] #[inline]
pub fn get_changed_tick(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> { pub fn get_changed_tick(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
let dense_index = *self.sparse.get(entity.index())? as usize; let dense_index = *self.sparse.get(entity.index())?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { unsafe { Some(self.dense.get_changed_tick_unchecked(dense_index)) }
Some(
self.dense
.get_changed_tick_unchecked(TableRow::new(dense_index)),
)
}
} }
/// Returns a reference to the "added" and "changed" ticks of the entity's component value. /// Returns a reference to the "added" and "changed" ticks of the entity's component value.
@ -277,11 +266,11 @@ impl ComponentSparseSet {
/// Returns `None` if `entity` does not have a component in the sparse set. /// Returns `None` if `entity` does not have a component in the sparse set.
#[inline] #[inline]
pub fn get_ticks(&self, entity: Entity) -> Option<ComponentTicks> { pub fn get_ticks(&self, entity: Entity) -> Option<ComponentTicks> {
let dense_index = *self.sparse.get(entity.index())? as usize; let dense_index = *self.sparse.get(entity.index())?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
// SAFETY: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { Some(self.dense.get_ticks_unchecked(TableRow::new(dense_index))) } unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) }
} }
/// Removes the `entity` from this sparse set and returns a pointer to the associated value (if /// Removes the `entity` from this sparse set and returns a pointer to the associated value (if
@ -289,23 +278,19 @@ impl ComponentSparseSet {
#[must_use = "The returned pointer must be used to drop the removed component."] #[must_use = "The returned pointer must be used to drop the removed component."]
pub(crate) fn remove_and_forget(&mut self, entity: Entity) -> Option<OwningPtr<'_>> { pub(crate) fn remove_and_forget(&mut self, entity: Entity) -> Option<OwningPtr<'_>> {
self.sparse.remove(entity.index()).map(|dense_index| { self.sparse.remove(entity.index()).map(|dense_index| {
let dense_index = dense_index as usize;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
self.entities.swap_remove(dense_index); self.entities.swap_remove(dense_index.as_usize());
let is_last = dense_index == self.dense.len() - 1; let is_last = dense_index.as_usize() == self.dense.len() - 1;
// SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid // SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid
let (value, _) = unsafe { let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
self.dense
.swap_remove_and_forget_unchecked(TableRow::new(dense_index))
};
if !is_last { if !is_last {
let swapped_entity = self.entities[dense_index]; let swapped_entity = self.entities[dense_index.as_usize()];
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
let index = swapped_entity; let index = swapped_entity;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let index = swapped_entity.index(); let index = swapped_entity.index();
*self.sparse.get_mut(index).unwrap() = dense_index as u32; *self.sparse.get_mut(index).unwrap() = dense_index;
} }
value value
}) })
@ -316,20 +301,21 @@ impl ComponentSparseSet {
/// Returns `true` if `entity` had a component value in the sparse set. /// Returns `true` if `entity` had a component value in the sparse set.
pub(crate) fn remove(&mut self, entity: Entity) -> bool { pub(crate) fn remove(&mut self, entity: Entity) -> bool {
if let Some(dense_index) = self.sparse.remove(entity.index()) { if let Some(dense_index) = self.sparse.remove(entity.index()) {
let dense_index = dense_index as usize;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
assert_eq!(entity, self.entities[dense_index]); assert_eq!(entity, self.entities[dense_index.as_usize()]);
self.entities.swap_remove(dense_index); self.entities.swap_remove(dense_index.as_usize());
let is_last = dense_index == self.dense.len() - 1; let is_last = dense_index.as_usize() == self.dense.len() - 1;
// SAFETY: if the sparse index points to something in the dense vec, it exists // SAFETY: if the sparse index points to something in the dense vec, it exists
unsafe { self.dense.swap_remove_unchecked(TableRow::new(dense_index)) } unsafe {
self.dense.swap_remove_unchecked(dense_index);
}
if !is_last { if !is_last {
let swapped_entity = self.entities[dense_index]; let swapped_entity = self.entities[dense_index.as_usize()];
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
let index = swapped_entity; let index = swapped_entity;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
let index = swapped_entity.index(); let index = swapped_entity.index();
*self.sparse.get_mut(index).unwrap() = dense_index as u32; *self.sparse.get_mut(index).unwrap() = dense_index;
} }
true true
} else { } else {

View File

@ -35,25 +35,48 @@ impl TableId {
/// Creates a new [`TableId`]. /// Creates a new [`TableId`].
/// ///
/// `index` *must* be retrieved from calling [`TableId::index`] on a `TableId` you got /// `index` *must* be retrieved from calling [`TableId::as_u32`] on a `TableId` you got
/// from a table of a given [`World`] or the created ID may be invalid. /// from a table of a given [`World`] or the created ID may be invalid.
/// ///
/// [`World`]: crate::world::World /// [`World`]: crate::world::World
#[inline] #[inline]
pub fn new(index: usize) -> Self { pub const fn from_u32(index: u32) -> Self {
TableId(index as u32) Self(index)
}
/// Creates a new [`TableId`].
///
/// `index` *must* be retrieved from calling [`TableId::as_usize`] on a `TableId` you got
/// from a table of a given [`World`] or the created ID may be invalid.
///
/// [`World`]: crate::world::World
///
/// # Panics
///
/// Will panic if the provided value does not fit within a [`u32`].
#[inline]
pub const fn from_usize(index: usize) -> Self {
assert!(index as u32 as usize == index);
Self(index as u32)
} }
/// Gets the underlying table index from the ID. /// Gets the underlying table index from the ID.
#[inline] #[inline]
pub fn index(self) -> usize { pub const fn as_u32(self) -> u32 {
self.0
}
/// Gets the underlying table index from the ID.
#[inline]
pub const fn as_usize(self) -> usize {
// usize is at least u32 in Bevy
self.0 as usize self.0 as usize
} }
/// The [`TableId`] of the [`Table`] without any components. /// The [`TableId`] of the [`Table`] without any components.
#[inline] #[inline]
pub const fn empty() -> TableId { pub const fn empty() -> Self {
TableId(0) Self(0)
} }
} }
@ -82,15 +105,33 @@ impl TableRow {
/// Creates a `TableRow`. /// Creates a `TableRow`.
#[inline] #[inline]
pub const fn new(index: usize) -> Self { pub const fn from_u32(index: u32) -> Self {
Self(index)
}
/// Creates a `TableRow` from a [`usize`] index.
///
/// # Panics
///
/// Will panic if the provided value does not fit within a [`u32`].
#[inline]
pub const fn from_usize(index: usize) -> Self {
assert!(index as u32 as usize == index);
Self(index as u32) Self(index as u32)
} }
/// Gets the index of the row. /// Gets the index of the row as a [`usize`].
#[inline] #[inline]
pub const fn index(self) -> usize { pub const fn as_usize(self) -> usize {
// usize is at least u32 in Bevy
self.0 as usize self.0 as usize
} }
/// Gets the index of the row as a [`usize`].
#[inline]
pub const fn as_u32(self) -> u32 {
self.0
}
} }
/// A type-erased contiguous container for data of a homogeneous type. /// A type-erased contiguous container for data of a homogeneous type.
@ -139,10 +180,13 @@ impl Column {
/// Assumes data has already been allocated for the given row. /// Assumes data has already been allocated for the given row.
#[inline] #[inline]
pub(crate) unsafe fn initialize(&mut self, row: TableRow, data: OwningPtr<'_>, tick: Tick) { pub(crate) unsafe fn initialize(&mut self, row: TableRow, data: OwningPtr<'_>, tick: Tick) {
debug_assert!(row.index() < self.len()); debug_assert!(row.as_usize() < self.len());
self.data.initialize_unchecked(row.index(), data); self.data.initialize_unchecked(row.as_usize(), data);
*self.added_ticks.get_unchecked_mut(row.index()).get_mut() = tick; *self.added_ticks.get_unchecked_mut(row.as_usize()).get_mut() = tick;
*self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = tick; *self
.changed_ticks
.get_unchecked_mut(row.as_usize())
.get_mut() = tick;
} }
/// Writes component data to the column at given row. /// Writes component data to the column at given row.
@ -152,9 +196,12 @@ impl Column {
/// Assumes data has already been allocated for the given row. /// Assumes data has already been allocated for the given row.
#[inline] #[inline]
pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: Tick) { pub(crate) unsafe fn replace(&mut self, row: TableRow, data: OwningPtr<'_>, change_tick: Tick) {
debug_assert!(row.index() < self.len()); debug_assert!(row.as_usize() < self.len());
self.data.replace_unchecked(row.index(), data); self.data.replace_unchecked(row.as_usize(), data);
*self.changed_ticks.get_unchecked_mut(row.index()).get_mut() = change_tick; *self
.changed_ticks
.get_unchecked_mut(row.as_usize())
.get_mut() = change_tick;
} }
/// Writes component data to the column at given row. /// Writes component data to the column at given row.
@ -165,8 +212,8 @@ impl Column {
/// Assumes data has already been allocated for the given row. /// Assumes data has already been allocated for the given row.
#[inline] #[inline]
pub(crate) unsafe fn replace_untracked(&mut self, row: TableRow, data: OwningPtr<'_>) { pub(crate) unsafe fn replace_untracked(&mut self, row: TableRow, data: OwningPtr<'_>) {
debug_assert!(row.index() < self.len()); debug_assert!(row.as_usize() < self.len());
self.data.replace_unchecked(row.index(), data); self.data.replace_unchecked(row.as_usize(), data);
} }
/// Gets the current number of elements stored in the column. /// Gets the current number of elements stored in the column.
@ -193,9 +240,9 @@ impl Column {
/// ///
#[inline] #[inline]
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) { pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) {
self.data.swap_remove_and_drop_unchecked(row.index()); self.data.swap_remove_and_drop_unchecked(row.as_usize());
self.added_ticks.swap_remove(row.index()); self.added_ticks.swap_remove(row.as_usize());
self.changed_ticks.swap_remove(row.index()); self.changed_ticks.swap_remove(row.as_usize());
} }
/// Removes an element from the [`Column`] and returns it and its change detection ticks. /// Removes an element from the [`Column`] and returns it and its change detection ticks.
@ -214,11 +261,11 @@ impl Column {
&mut self, &mut self,
row: TableRow, row: TableRow,
) -> Option<(OwningPtr<'_>, ComponentTicks)> { ) -> Option<(OwningPtr<'_>, ComponentTicks)> {
(row.index() < self.data.len()).then(|| { (row.as_usize() < self.data.len()).then(|| {
// SAFETY: The row was length checked before this. // SAFETY: The row was length checked before this.
let data = unsafe { self.data.swap_remove_and_forget_unchecked(row.index()) }; let data = unsafe { self.data.swap_remove_and_forget_unchecked(row.as_usize()) };
let added = self.added_ticks.swap_remove(row.index()).into_inner(); let added = self.added_ticks.swap_remove(row.as_usize()).into_inner();
let changed = self.changed_ticks.swap_remove(row.index()).into_inner(); let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner();
(data, ComponentTicks { added, changed }) (data, ComponentTicks { added, changed })
}) })
} }
@ -241,9 +288,9 @@ impl Column {
&mut self, &mut self,
row: TableRow, row: TableRow,
) -> (OwningPtr<'_>, ComponentTicks) { ) -> (OwningPtr<'_>, ComponentTicks) {
let data = self.data.swap_remove_and_forget_unchecked(row.index()); let data = self.data.swap_remove_and_forget_unchecked(row.as_usize());
let added = self.added_ticks.swap_remove(row.index()).into_inner(); let added = self.added_ticks.swap_remove(row.as_usize()).into_inner();
let changed = self.changed_ticks.swap_remove(row.index()).into_inner(); let changed = self.changed_ticks.swap_remove(row.as_usize()).into_inner();
(data, ComponentTicks { added, changed }) (data, ComponentTicks { added, changed })
} }
@ -266,12 +313,12 @@ impl Column {
dst_row: TableRow, dst_row: TableRow,
) { ) {
debug_assert!(self.data.layout() == other.data.layout()); debug_assert!(self.data.layout() == other.data.layout());
let ptr = self.data.get_unchecked_mut(dst_row.index()); let ptr = self.data.get_unchecked_mut(dst_row.as_usize());
other.data.swap_remove_unchecked(src_row.index(), ptr); other.data.swap_remove_unchecked(src_row.as_usize(), ptr);
*self.added_ticks.get_unchecked_mut(dst_row.index()) = *self.added_ticks.get_unchecked_mut(dst_row.as_usize()) =
other.added_ticks.swap_remove(src_row.index()); other.added_ticks.swap_remove(src_row.as_usize());
*self.changed_ticks.get_unchecked_mut(dst_row.index()) = *self.changed_ticks.get_unchecked_mut(dst_row.as_usize()) =
other.changed_ticks.swap_remove(src_row.index()); other.changed_ticks.swap_remove(src_row.as_usize());
} }
/// Pushes a new value onto the end of the [`Column`]. /// Pushes a new value onto the end of the [`Column`].
@ -338,15 +385,15 @@ impl Column {
/// Returns `None` if `row` is out of bounds. /// Returns `None` if `row` is out of bounds.
#[inline] #[inline]
pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> { pub fn get(&self, row: TableRow) -> Option<(Ptr<'_>, TickCells<'_>)> {
(row.index() < self.data.len()) (row.as_usize() < self.data.len())
// SAFETY: The row is length checked before fetching the pointer. This is being // SAFETY: The row is length checked before fetching the pointer. This is being
// accessed through a read-only reference to the column. // accessed through a read-only reference to the column.
.then(|| unsafe { .then(|| unsafe {
( (
self.data.get_unchecked(row.index()), self.data.get_unchecked(row.as_usize()),
TickCells { TickCells {
added: self.added_ticks.get_unchecked(row.index()), added: self.added_ticks.get_unchecked(row.as_usize()),
changed: self.changed_ticks.get_unchecked(row.index()), changed: self.changed_ticks.get_unchecked(row.as_usize()),
}, },
) )
}) })
@ -357,9 +404,11 @@ impl Column {
/// Returns `None` if `row` is out of bounds. /// Returns `None` if `row` is out of bounds.
#[inline] #[inline]
pub fn get_data(&self, row: TableRow) -> Option<Ptr<'_>> { pub fn get_data(&self, row: TableRow) -> Option<Ptr<'_>> {
// SAFETY: The row is length checked before fetching the pointer. This is being (row.as_usize() < self.data.len()).then(|| {
// accessed through a read-only reference to the column. // SAFETY: The row is length checked before fetching the pointer. This is being
(row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked(row.index()) }) // accessed through a read-only reference to the column.
unsafe { self.data.get_unchecked(row.as_usize()) }
})
} }
/// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not /// Fetches a read-only reference to the data at `row`. Unlike [`Column::get`] this does not
@ -370,8 +419,8 @@ impl Column {
/// - no other mutable reference to the data of the same row can exist at the same time /// - no other mutable reference to the data of the same row can exist at the same time
#[inline] #[inline]
pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> { pub unsafe fn get_data_unchecked(&self, row: TableRow) -> Ptr<'_> {
debug_assert!(row.index() < self.data.len()); debug_assert!(row.as_usize() < self.data.len());
self.data.get_unchecked(row.index()) self.data.get_unchecked(row.as_usize())
} }
/// Fetches a mutable reference to the data at `row`. /// Fetches a mutable reference to the data at `row`.
@ -379,9 +428,11 @@ impl Column {
/// Returns `None` if `row` is out of bounds. /// Returns `None` if `row` is out of bounds.
#[inline] #[inline]
pub fn get_data_mut(&mut self, row: TableRow) -> 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 (row.as_usize() < self.data.len()).then(|| {
// accessed through an exclusive reference to the column. // SAFETY: The row is length checked before fetching the pointer. This is being
(row.index() < self.data.len()).then(|| unsafe { self.data.get_unchecked_mut(row.index()) }) // accessed through an exclusive reference to the column.
unsafe { self.data.get_unchecked_mut(row.as_usize()) }
})
} }
/// Fetches a mutable reference to the data at `row`. Unlike [`Column::get_data_mut`] this does not /// Fetches a mutable reference to the data at `row`. Unlike [`Column::get_data_mut`] this does not
@ -392,8 +443,8 @@ impl Column {
/// - no other reference to the data of the same row can exist at the same time /// - no other reference to the data of the same row can exist at the same time
#[inline] #[inline]
pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: TableRow) -> PtrMut<'_> { pub(crate) unsafe fn get_data_unchecked_mut(&mut self, row: TableRow) -> PtrMut<'_> {
debug_assert!(row.index() < self.data.len()); debug_assert!(row.as_usize() < self.data.len());
self.data.get_unchecked_mut(row.index()) self.data.get_unchecked_mut(row.as_usize())
} }
/// Fetches the "added" change detection tick for the value at `row`. /// Fetches the "added" change detection tick for the value at `row`.
@ -405,7 +456,7 @@ impl Column {
/// adhere to the safety invariants of [`UnsafeCell`]. /// adhere to the safety invariants of [`UnsafeCell`].
#[inline] #[inline]
pub fn get_added_tick(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> { pub fn get_added_tick(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
self.added_ticks.get(row.index()) self.added_ticks.get(row.as_usize())
} }
/// Fetches the "changed" change detection tick for the value at `row`. /// Fetches the "changed" change detection tick for the value at `row`.
@ -417,7 +468,7 @@ impl Column {
/// adhere to the safety invariants of [`UnsafeCell`]. /// adhere to the safety invariants of [`UnsafeCell`].
#[inline] #[inline]
pub fn get_changed_tick(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> { pub fn get_changed_tick(&self, row: TableRow) -> Option<&UnsafeCell<Tick>> {
self.changed_ticks.get(row.index()) self.changed_ticks.get(row.as_usize())
} }
/// Fetches the change detection ticks for the value at `row`. /// Fetches the change detection ticks for the value at `row`.
@ -425,7 +476,7 @@ impl Column {
/// Returns `None` if `row` is out of bounds. /// Returns `None` if `row` is out of bounds.
#[inline] #[inline]
pub fn get_ticks(&self, row: TableRow) -> Option<ComponentTicks> { pub fn get_ticks(&self, row: TableRow) -> Option<ComponentTicks> {
if row.index() < self.data.len() { if row.as_usize() < self.data.len() {
// SAFETY: The size of the column has already been checked. // SAFETY: The size of the column has already been checked.
Some(unsafe { self.get_ticks_unchecked(row) }) Some(unsafe { self.get_ticks_unchecked(row) })
} else { } else {
@ -440,8 +491,8 @@ impl Column {
/// `row` must be within the range `[0, self.len())`. /// `row` must be within the range `[0, self.len())`.
#[inline] #[inline]
pub unsafe fn get_added_tick_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> { pub unsafe fn get_added_tick_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
debug_assert!(row.index() < self.added_ticks.len()); debug_assert!(row.as_usize() < self.added_ticks.len());
self.added_ticks.get_unchecked(row.index()) self.added_ticks.get_unchecked(row.as_usize())
} }
/// Fetches the "changed" change detection tick for the value at `row`. Unlike [`Column::get_changed_tick`] /// Fetches the "changed" change detection tick for the value at `row`. Unlike [`Column::get_changed_tick`]
@ -451,8 +502,8 @@ impl Column {
/// `row` must be within the range `[0, self.len())`. /// `row` must be within the range `[0, self.len())`.
#[inline] #[inline]
pub unsafe fn get_changed_tick_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> { pub unsafe fn get_changed_tick_unchecked(&self, row: TableRow) -> &UnsafeCell<Tick> {
debug_assert!(row.index() < self.changed_ticks.len()); debug_assert!(row.as_usize() < self.changed_ticks.len());
self.changed_ticks.get_unchecked(row.index()) self.changed_ticks.get_unchecked(row.as_usize())
} }
/// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`] /// Fetches the change detection ticks for the value at `row`. Unlike [`Column::get_ticks`]
@ -462,11 +513,11 @@ impl Column {
/// `row` must be within the range `[0, self.len())`. /// `row` must be within the range `[0, self.len())`.
#[inline] #[inline]
pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks { pub unsafe fn get_ticks_unchecked(&self, row: TableRow) -> ComponentTicks {
debug_assert!(row.index() < self.added_ticks.len()); debug_assert!(row.as_usize() < self.added_ticks.len());
debug_assert!(row.index() < self.changed_ticks.len()); debug_assert!(row.as_usize() < self.changed_ticks.len());
ComponentTicks { ComponentTicks {
added: self.added_ticks.get_unchecked(row.index()).read(), added: self.added_ticks.get_unchecked(row.as_usize()).read(),
changed: self.changed_ticks.get_unchecked(row.index()).read(), changed: self.changed_ticks.get_unchecked(row.as_usize()).read(),
} }
} }
@ -565,12 +616,12 @@ impl Table {
for column in self.columns.values_mut() { for column in self.columns.values_mut() {
column.swap_remove_unchecked(row); column.swap_remove_unchecked(row);
} }
let is_last = row.index() == self.entities.len() - 1; let is_last = row.as_usize() == self.entities.len() - 1;
self.entities.swap_remove(row.index()); self.entities.swap_remove(row.as_usize());
if is_last { if is_last {
None None
} else { } else {
Some(self.entities[row.index()]) Some(self.entities[row.as_usize()])
} }
} }
@ -587,9 +638,9 @@ impl Table {
row: TableRow, row: TableRow,
new_table: &mut Table, new_table: &mut Table,
) -> TableMoveResult { ) -> TableMoveResult {
debug_assert!(row.index() < self.entity_count()); debug_assert!(row.as_usize() < self.entity_count());
let is_last = row.index() == self.entities.len() - 1; let is_last = row.as_usize() == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row.index())); let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize()));
for (component_id, column) in self.columns.iter_mut() { for (component_id, column) in self.columns.iter_mut() {
if let Some(new_column) = new_table.get_column_mut(*component_id) { if let Some(new_column) = new_table.get_column_mut(*component_id) {
new_column.initialize_from_unchecked(column, row, new_row); new_column.initialize_from_unchecked(column, row, new_row);
@ -603,7 +654,7 @@ impl Table {
swapped_entity: if is_last { swapped_entity: if is_last {
None None
} else { } else {
Some(self.entities[row.index()]) Some(self.entities[row.as_usize()])
}, },
} }
} }
@ -619,9 +670,9 @@ impl Table {
row: TableRow, row: TableRow,
new_table: &mut Table, new_table: &mut Table,
) -> TableMoveResult { ) -> TableMoveResult {
debug_assert!(row.index() < self.entity_count()); debug_assert!(row.as_usize() < self.entity_count());
let is_last = row.index() == self.entities.len() - 1; let is_last = row.as_usize() == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row.index())); let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize()));
for (component_id, column) in self.columns.iter_mut() { for (component_id, column) in self.columns.iter_mut() {
if let Some(new_column) = new_table.get_column_mut(*component_id) { if let Some(new_column) = new_table.get_column_mut(*component_id) {
new_column.initialize_from_unchecked(column, row, new_row); new_column.initialize_from_unchecked(column, row, new_row);
@ -634,7 +685,7 @@ impl Table {
swapped_entity: if is_last { swapped_entity: if is_last {
None None
} else { } else {
Some(self.entities[row.index()]) Some(self.entities[row.as_usize()])
}, },
} }
} }
@ -650,9 +701,9 @@ impl Table {
row: TableRow, row: TableRow,
new_table: &mut Table, new_table: &mut Table,
) -> TableMoveResult { ) -> TableMoveResult {
debug_assert!(row.index() < self.entity_count()); debug_assert!(row.as_usize() < self.entity_count());
let is_last = row.index() == self.entities.len() - 1; let is_last = row.as_usize() == self.entities.len() - 1;
let new_row = new_table.allocate(self.entities.swap_remove(row.index())); let new_row = new_table.allocate(self.entities.swap_remove(row.as_usize()));
for (component_id, column) in self.columns.iter_mut() { for (component_id, column) in self.columns.iter_mut() {
new_table new_table
.get_column_mut(*component_id) .get_column_mut(*component_id)
@ -664,7 +715,7 @@ impl Table {
swapped_entity: if is_last { swapped_entity: if is_last {
None None
} else { } else {
Some(self.entities[row.index()]) Some(self.entities[row.as_usize()])
}, },
} }
} }
@ -728,7 +779,7 @@ impl Table {
column.added_ticks.push(UnsafeCell::new(Tick::new(0))); column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
column.changed_ticks.push(UnsafeCell::new(Tick::new(0))); column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
} }
TableRow::new(index) TableRow::from_usize(index)
} }
/// Gets the number of entities currently being stored in the table. /// Gets the number of entities currently being stored in the table.
@ -819,7 +870,7 @@ impl Tables {
/// Returns `None` if `id` is invalid. /// Returns `None` if `id` is invalid.
#[inline] #[inline]
pub fn get(&self, id: TableId) -> Option<&Table> { pub fn get(&self, id: TableId) -> Option<&Table> {
self.tables.get(id.index()) self.tables.get(id.as_usize())
} }
/// Fetches mutable references to two different [`Table`]s. /// Fetches mutable references to two different [`Table`]s.
@ -829,12 +880,12 @@ impl Tables {
/// Panics if `a` and `b` are equal. /// Panics if `a` and `b` are equal.
#[inline] #[inline]
pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) { pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) {
if a.index() > b.index() { if a.as_usize() > b.as_usize() {
let (b_slice, a_slice) = self.tables.split_at_mut(a.index()); let (b_slice, a_slice) = self.tables.split_at_mut(a.as_usize());
(&mut a_slice[0], &mut b_slice[b.index()]) (&mut a_slice[0], &mut b_slice[b.as_usize()])
} else { } else {
let (a_slice, b_slice) = self.tables.split_at_mut(b.index()); let (a_slice, b_slice) = self.tables.split_at_mut(b.as_usize());
(&mut a_slice[a.index()], &mut b_slice[0]) (&mut a_slice[a.as_usize()], &mut b_slice[0])
} }
} }
@ -859,7 +910,10 @@ impl Tables {
table = table.add_column(components.get_info_unchecked(*component_id)); table = table.add_column(components.get_info_unchecked(*component_id));
} }
tables.push(table.build()); tables.push(table.build());
(component_ids.to_vec(), TableId::new(tables.len() - 1)) (
component_ids.to_vec(),
TableId::from_usize(tables.len() - 1),
)
}); });
*value *value
@ -889,14 +943,14 @@ impl Index<TableId> for Tables {
#[inline] #[inline]
fn index(&self, index: TableId) -> &Self::Output { fn index(&self, index: TableId) -> &Self::Output {
&self.tables[index.index()] &self.tables[index.as_usize()]
} }
} }
impl IndexMut<TableId> for Tables { impl IndexMut<TableId> for Tables {
#[inline] #[inline]
fn index_mut(&mut self, index: TableId) -> &mut Self::Output { fn index_mut(&mut self, index: TableId) -> &mut Self::Output {
&mut self.tables[index.index()] &mut self.tables[index.as_usize()]
} }
} }