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:
		
							parent
							
								
									a3f203b504
								
							
						
					
					
						commit
						530be10e72
					
				@ -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))*
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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)]
 | 
			
		||||
 | 
			
		||||
@ -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> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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(),
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
@ -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))]
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user