779 lines
26 KiB
Rust
779 lines
26 KiB
Rust
use crate::{
|
|
component::{ComponentId, ComponentInfo, ComponentTicks, Components, Tick, TickCells},
|
|
entity::Entity,
|
|
query::DebugCheckedUnwrap,
|
|
storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet},
|
|
};
|
|
use bevy_ptr::{OwningPtr, Ptr, PtrMut, UnsafeCellDeref};
|
|
use bevy_utils::HashMap;
|
|
use std::alloc::Layout;
|
|
use std::{
|
|
cell::UnsafeCell,
|
|
ops::{Index, IndexMut},
|
|
};
|
|
|
|
/// An opaque unique ID for a [`Table`] within a [`World`].
|
|
///
|
|
/// Can be used with [`Tables::get`] to fetch the corresponding
|
|
/// table.
|
|
///
|
|
/// Each [`Archetype`] always points to a table via [`Archetype::table_id`].
|
|
/// Multiple archetypes can point to the same table so long as the components
|
|
/// stored in the table are identical, but do not share the same sparse set
|
|
/// components.
|
|
///
|
|
/// [`World`]: crate::world::World
|
|
/// [`Archetype`]: crate::archetype::Archetype
|
|
/// [`Archetype::table_id`]: crate::archetype::Archetype::table_id
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct TableId(u32);
|
|
|
|
impl TableId {
|
|
pub(crate) const INVALID: TableId = TableId(u32::MAX);
|
|
|
|
#[inline]
|
|
pub fn new(index: usize) -> Self {
|
|
TableId(index as u32)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
|
|
#[inline]
|
|
pub const fn empty() -> TableId {
|
|
TableId(0)
|
|
}
|
|
}
|
|
|
|
/// A opaque newtype for rows in [`Table`]s. Specifies a single row in a specific table.
|
|
///
|
|
/// Values of this type are retrievable 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 appropriate 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(u32);
|
|
|
|
impl TableRow {
|
|
pub const INVALID: TableRow = TableRow(u32::MAX);
|
|
|
|
/// Creates a `TableRow`.
|
|
#[inline]
|
|
pub const fn new(index: usize) -> Self {
|
|
Self(index as u32)
|
|
}
|
|
|
|
/// Gets the index of the row.
|
|
#[inline]
|
|
pub const fn index(self) -> usize {
|
|
self.0 as usize
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct Column {
|
|
data: BlobVec,
|
|
added_ticks: Vec<UnsafeCell<Tick>>,
|
|
changed_ticks: Vec<UnsafeCell<Tick>>,
|
|
}
|
|
|
|
impl Column {
|
|
#[inline]
|
|
pub(crate) fn with_capacity(component_info: &ComponentInfo, capacity: usize) -> Self {
|
|
Column {
|
|
// SAFETY: component_info.drop() is valid for the types that will be inserted.
|
|
data: unsafe { BlobVec::new(component_info.layout(), component_info.drop(), capacity) },
|
|
added_ticks: Vec::with_capacity(capacity),
|
|
changed_ticks: Vec::with_capacity(capacity),
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn item_layout(&self) -> Layout {
|
|
self.data.layout()
|
|
}
|
|
|
|
/// Writes component data to the column at given row.
|
|
/// Assumes the slot is uninitialized, drop is not called.
|
|
/// To overwrite existing initialized value, use `replace` instead.
|
|
///
|
|
/// # Safety
|
|
/// Assumes data has already been allocated for the given row.
|
|
#[inline]
|
|
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.
|
|
/// Assumes the slot is initialized, calls drop.
|
|
///
|
|
/// # Safety
|
|
/// Assumes data has already been allocated for the given row.
|
|
#[inline]
|
|
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.index())
|
|
.get_mut()
|
|
.set_changed(change_tick);
|
|
}
|
|
|
|
/// Writes component data to the column at given row.
|
|
/// Assumes the slot is initialized, calls drop.
|
|
/// Does not update the Component's ticks.
|
|
///
|
|
/// # Safety
|
|
/// Assumes data has already been allocated for the given row.
|
|
#[inline]
|
|
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]
|
|
pub fn len(&self) -> usize {
|
|
self.data.len()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_empty(&self) -> bool {
|
|
self.data.is_empty()
|
|
}
|
|
|
|
/// # Safety
|
|
/// index must be in-bounds
|
|
#[inline]
|
|
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: TableRow,
|
|
) -> Option<(OwningPtr<'_>, ComponentTicks)> {
|
|
(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.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 })
|
|
})
|
|
}
|
|
|
|
/// # Safety
|
|
/// index must be in-bounds
|
|
#[inline]
|
|
#[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: TableRow,
|
|
) -> (OwningPtr<'_>, ComponentTicks) {
|
|
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 })
|
|
}
|
|
|
|
/// Removes the element from `other` at `src_row` and inserts it
|
|
/// into the current column to initialize the values at `dst_row`.
|
|
/// Does not do any bounds checking.
|
|
///
|
|
/// # Safety
|
|
///
|
|
/// - `other` must have the same data layout as `self`
|
|
/// - `src_row` must be in bounds for `other`
|
|
/// - `dst_row` must be in bounds for `self`
|
|
/// - `other[src_row]` must be initialized to a valid value.
|
|
/// - `self[dst_row]` must not be initialized yet.
|
|
#[inline]
|
|
pub(crate) unsafe fn initialize_from_unchecked(
|
|
&mut self,
|
|
other: &mut Column,
|
|
src_row: TableRow,
|
|
dst_row: TableRow,
|
|
) {
|
|
debug_assert!(self.data.layout() == other.data.layout());
|
|
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
|
|
// - ptr must point to valid data of this column's component type
|
|
pub(crate) unsafe fn push(&mut self, ptr: OwningPtr<'_>, ticks: ComponentTicks) {
|
|
self.data.push(ptr);
|
|
self.added_ticks.push(UnsafeCell::new(ticks.added));
|
|
self.changed_ticks.push(UnsafeCell::new(ticks.changed));
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn reserve_exact(&mut self, additional: usize) {
|
|
self.data.reserve_exact(additional);
|
|
self.added_ticks.reserve_exact(additional);
|
|
self.changed_ticks.reserve_exact(additional);
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_data_ptr(&self) -> Ptr<'_> {
|
|
self.data.get_ptr()
|
|
}
|
|
|
|
/// # Safety
|
|
/// The type `T` must be the type of the items in this column.
|
|
pub unsafe fn get_data_slice<T>(&self) -> &[UnsafeCell<T>] {
|
|
self.data.get_slice()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_added_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
|
|
&self.added_ticks
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_changed_ticks_slice(&self) -> &[UnsafeCell<Tick>] {
|
|
&self.changed_ticks
|
|
}
|
|
|
|
#[inline]
|
|
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.index()),
|
|
TickCells {
|
|
added: self.added_ticks.get_unchecked(row.index()),
|
|
changed: self.changed_ticks.get_unchecked(row.index()),
|
|
},
|
|
)
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
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.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: TableRow) -> Ptr<'_> {
|
|
debug_assert!(row.index() < self.data.len());
|
|
self.data.get_unchecked(row.index())
|
|
}
|
|
|
|
#[inline]
|
|
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.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: TableRow) -> PtrMut<'_> {
|
|
debug_assert!(row.index() < self.data.len());
|
|
self.data.get_unchecked_mut(row.index())
|
|
}
|
|
|
|
#[inline]
|
|
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: TableRow) -> Option<&UnsafeCell<Tick>> {
|
|
self.changed_ticks.get(row.index())
|
|
}
|
|
|
|
#[inline]
|
|
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 {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// # Safety
|
|
/// index must be in-bounds
|
|
#[inline]
|
|
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: 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: 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.index()).read(),
|
|
changed: self.changed_ticks.get_unchecked(row.index()).read(),
|
|
}
|
|
}
|
|
|
|
pub fn clear(&mut self) {
|
|
self.data.clear();
|
|
self.added_ticks.clear();
|
|
self.changed_ticks.clear();
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
|
for component_ticks in &mut self.added_ticks {
|
|
component_ticks.get_mut().check_tick(change_tick);
|
|
}
|
|
for component_ticks in &mut self.changed_ticks {
|
|
component_ticks.get_mut().check_tick(change_tick);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A builder type for constructing [`Table`]s.
|
|
///
|
|
/// - Use [`with_capacity`] to initialize the builder.
|
|
/// - Repeatedly call [`add_column`] to add columns for components.
|
|
/// - Finalize with [`build`] to get the constructed [`Table`].
|
|
///
|
|
/// [`with_capacity`]: Self::with_capacity
|
|
/// [`add_column`]: Self::add_column
|
|
/// [`build`]: Self::build
|
|
pub(crate) struct TableBuilder {
|
|
columns: SparseSet<ComponentId, Column>,
|
|
capacity: usize,
|
|
}
|
|
|
|
impl TableBuilder {
|
|
/// Creates a blank [`Table`], allocating space for `column_capacity` columns
|
|
/// with the capacity to hold `capacity` entities worth of components each.
|
|
pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self {
|
|
Self {
|
|
columns: SparseSet::with_capacity(column_capacity),
|
|
capacity,
|
|
}
|
|
}
|
|
|
|
pub fn add_column(&mut self, component_info: &ComponentInfo) {
|
|
self.columns.insert(
|
|
component_info.id(),
|
|
Column::with_capacity(component_info, self.capacity),
|
|
);
|
|
}
|
|
|
|
pub fn build(self) -> Table {
|
|
Table {
|
|
columns: self.columns.into_immutable(),
|
|
entities: Vec::with_capacity(self.capacity),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities
|
|
/// in a [`World`].
|
|
///
|
|
/// Conceptually, a `Table` can be thought of as an `HashMap<ComponentId, Column>`, where
|
|
/// each `Column` is a type-erased `Vec<T: Component>`. Each row corresponds to a single entity
|
|
/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same
|
|
/// entity). Fetching components from a table involves fetching the associated column for a
|
|
/// component type (via it's [`ComponentId`]), then fetching the entity's row within that column.
|
|
///
|
|
/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays
|
|
/// [`Component`]: crate::component::Component
|
|
/// [`World`]: crate::world::World
|
|
pub struct Table {
|
|
columns: ImmutableSparseSet<ComponentId, Column>,
|
|
entities: Vec<Entity>,
|
|
}
|
|
|
|
impl Table {
|
|
#[inline]
|
|
pub fn entities(&self) -> &[Entity] {
|
|
&self.entities
|
|
}
|
|
|
|
/// Removes the entity at the given row and returns the entity swapped in to replace it (if an
|
|
/// entity was swapped in)
|
|
///
|
|
/// # Safety
|
|
/// `row` must be in-bounds
|
|
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.index() == self.entities.len() - 1;
|
|
self.entities.swap_remove(row.index());
|
|
if is_last {
|
|
None
|
|
} else {
|
|
Some(self.entities[row.index()])
|
|
}
|
|
}
|
|
|
|
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
|
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
|
/// to replace it (if an entity was swapped in). missing columns will be "forgotten". It is
|
|
/// the caller's responsibility to drop them
|
|
///
|
|
/// # Safety
|
|
/// Row must be in-bounds
|
|
pub(crate) unsafe fn move_to_and_forget_missing_unchecked(
|
|
&mut self,
|
|
row: TableRow,
|
|
new_table: &mut Table,
|
|
) -> TableMoveResult {
|
|
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);
|
|
} else {
|
|
// It's the caller's responsibility to drop these cases.
|
|
let (_, _) = column.swap_remove_and_forget_unchecked(row);
|
|
}
|
|
}
|
|
TableMoveResult {
|
|
new_row,
|
|
swapped_entity: if is_last {
|
|
None
|
|
} else {
|
|
Some(self.entities[row.index()])
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
|
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
|
/// to replace it (if an entity was swapped in).
|
|
///
|
|
/// # Safety
|
|
/// row must be in-bounds
|
|
pub(crate) unsafe fn move_to_and_drop_missing_unchecked(
|
|
&mut self,
|
|
row: TableRow,
|
|
new_table: &mut Table,
|
|
) -> TableMoveResult {
|
|
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);
|
|
} else {
|
|
column.swap_remove_unchecked(row);
|
|
}
|
|
}
|
|
TableMoveResult {
|
|
new_row,
|
|
swapped_entity: if is_last {
|
|
None
|
|
} else {
|
|
Some(self.entities[row.index()])
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Moves the `row` column values to `new_table`, for the columns shared between both tables.
|
|
/// Returns the index of the new row in `new_table` and the entity in this table swapped in
|
|
/// to replace it (if an entity was swapped in).
|
|
///
|
|
/// # Safety
|
|
/// `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: TableRow,
|
|
new_table: &mut Table,
|
|
) -> TableMoveResult {
|
|
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)
|
|
.debug_checked_unwrap()
|
|
.initialize_from_unchecked(column, row, new_row);
|
|
}
|
|
TableMoveResult {
|
|
new_row,
|
|
swapped_entity: if is_last {
|
|
None
|
|
} else {
|
|
Some(self.entities[row.index()])
|
|
},
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get_column(&self, component_id: ComponentId) -> Option<&Column> {
|
|
self.columns.get(component_id)
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn get_column_mut(&mut self, component_id: ComponentId) -> Option<&mut Column> {
|
|
self.columns.get_mut(component_id)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn has_column(&self, component_id: ComponentId) -> bool {
|
|
self.columns.contains(component_id)
|
|
}
|
|
|
|
pub(crate) fn reserve(&mut self, additional: usize) {
|
|
if self.entities.capacity() - self.entities.len() < additional {
|
|
self.entities.reserve(additional);
|
|
|
|
// use entities vector capacity as driving capacity for all related allocations
|
|
let new_capacity = self.entities.capacity();
|
|
|
|
for column in self.columns.values_mut() {
|
|
column.reserve_exact(new_capacity - column.len());
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Allocates space for a new entity
|
|
///
|
|
/// # Safety
|
|
/// the allocated row must be written to immediately with valid values in each column
|
|
pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow {
|
|
self.reserve(1);
|
|
let index = self.entities.len();
|
|
self.entities.push(entity);
|
|
for column in self.columns.values_mut() {
|
|
column.data.set_len(self.entities.len());
|
|
column.added_ticks.push(UnsafeCell::new(Tick::new(0)));
|
|
column.changed_ticks.push(UnsafeCell::new(Tick::new(0)));
|
|
}
|
|
TableRow::new(index)
|
|
}
|
|
|
|
#[inline]
|
|
pub fn entity_count(&self) -> usize {
|
|
self.entities.len()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn component_count(&self) -> usize {
|
|
self.columns.len()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn entity_capacity(&self) -> usize {
|
|
self.entities.capacity()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_empty(&self) -> bool {
|
|
self.entities.is_empty()
|
|
}
|
|
|
|
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
|
for column in self.columns.values_mut() {
|
|
column.check_change_ticks(change_tick);
|
|
}
|
|
}
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &Column> {
|
|
self.columns.values()
|
|
}
|
|
|
|
pub(crate) fn clear(&mut self) {
|
|
self.entities.clear();
|
|
for column in self.columns.values_mut() {
|
|
column.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A collection of [`Table`] storages, indexed by [`TableId`]
|
|
///
|
|
/// Can be accessed via [`Storages`](crate::storage::Storages)
|
|
pub struct Tables {
|
|
tables: Vec<Table>,
|
|
table_ids: HashMap<Vec<ComponentId>, TableId>,
|
|
}
|
|
|
|
impl Default for Tables {
|
|
fn default() -> Self {
|
|
let empty_table = TableBuilder::with_capacity(0, 0).build();
|
|
Tables {
|
|
tables: vec![empty_table],
|
|
table_ids: HashMap::default(),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) struct TableMoveResult {
|
|
pub swapped_entity: Option<Entity>,
|
|
pub new_row: TableRow,
|
|
}
|
|
|
|
impl Tables {
|
|
#[inline]
|
|
pub fn len(&self) -> usize {
|
|
self.tables.len()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn is_empty(&self) -> bool {
|
|
self.tables.is_empty()
|
|
}
|
|
|
|
#[inline]
|
|
pub fn get(&self, id: TableId) -> Option<&Table> {
|
|
self.tables.get(id.index())
|
|
}
|
|
|
|
#[inline]
|
|
pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) {
|
|
if a.index() > b.index() {
|
|
let (b_slice, a_slice) = self.tables.split_at_mut(a.index());
|
|
(&mut a_slice[0], &mut b_slice[b.index()])
|
|
} else {
|
|
let (a_slice, b_slice) = self.tables.split_at_mut(b.index());
|
|
(&mut a_slice[a.index()], &mut b_slice[0])
|
|
}
|
|
}
|
|
|
|
/// # Safety
|
|
/// `component_ids` must contain components that exist in `components`
|
|
pub(crate) unsafe fn get_id_or_insert(
|
|
&mut self,
|
|
component_ids: &[ComponentId],
|
|
components: &Components,
|
|
) -> TableId {
|
|
let tables = &mut self.tables;
|
|
let (_key, value) = self
|
|
.table_ids
|
|
.raw_entry_mut()
|
|
.from_key(component_ids)
|
|
.or_insert_with(|| {
|
|
let mut table = TableBuilder::with_capacity(0, component_ids.len());
|
|
for component_id in component_ids {
|
|
table.add_column(components.get_info_unchecked(*component_id));
|
|
}
|
|
tables.push(table.build());
|
|
(component_ids.to_vec(), TableId::new(tables.len() - 1))
|
|
});
|
|
|
|
*value
|
|
}
|
|
|
|
pub fn iter(&self) -> std::slice::Iter<'_, Table> {
|
|
self.tables.iter()
|
|
}
|
|
|
|
pub(crate) fn clear(&mut self) {
|
|
for table in &mut self.tables {
|
|
table.clear();
|
|
}
|
|
}
|
|
|
|
pub(crate) fn check_change_ticks(&mut self, change_tick: u32) {
|
|
for table in &mut self.tables {
|
|
table.check_change_ticks(change_tick);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Index<TableId> for Tables {
|
|
type Output = Table;
|
|
|
|
#[inline]
|
|
fn index(&self, index: TableId) -> &Self::Output {
|
|
&self.tables[index.index()]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<TableId> for Tables {
|
|
#[inline]
|
|
fn index_mut(&mut self, index: TableId) -> &mut Self::Output {
|
|
&mut self.tables[index.index()]
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate as bevy_ecs;
|
|
use crate::component::Component;
|
|
use crate::ptr::OwningPtr;
|
|
use crate::storage::Storages;
|
|
use crate::{
|
|
component::{Components, Tick},
|
|
entity::Entity,
|
|
storage::{TableBuilder, TableRow},
|
|
};
|
|
#[derive(Component)]
|
|
struct W<T>(T);
|
|
|
|
#[test]
|
|
fn table() {
|
|
let mut components = Components::default();
|
|
let mut storages = Storages::default();
|
|
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());
|
|
let mut table = builder.build();
|
|
let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>();
|
|
for entity in &entities {
|
|
// SAFETY: we allocate and immediately set data afterwards
|
|
unsafe {
|
|
let row = table.allocate(*entity);
|
|
let value: W<TableRow> = W(row);
|
|
OwningPtr::make(value, |value_ptr| {
|
|
table.get_column_mut(component_id).unwrap().initialize(
|
|
row,
|
|
value_ptr,
|
|
Tick::new(0),
|
|
);
|
|
});
|
|
};
|
|
}
|
|
|
|
assert_eq!(table.entity_capacity(), 256);
|
|
assert_eq!(table.entity_count(), 200);
|
|
}
|
|
}
|