use crate::{
change_detection::{CheckChangeTicks, ComponentTicks, MaybeLocation, Tick},
component::{ComponentId, ComponentInfo, Components},
entity::Entity,
query::DebugCheckedUnwrap,
storage::{AbortOnPanic, ImmutableSparseSet, SparseSet},
};
use alloc::{boxed::Box, vec, vec::Vec};
use bevy_platform::collections::HashMap;
use bevy_ptr::{OwningPtr, Ptr, UnsafeCellDeref};
pub use column::*;
use core::{
cell::UnsafeCell,
num::NonZeroUsize,
ops::{Index, IndexMut},
panic::Location,
};
use nonmax::NonMaxU32;
mod column;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TableId(u32);
impl TableId {
#[inline]
pub const fn from_u32(index: u32) -> Self {
Self(index)
}
#[inline]
pub const fn from_usize(index: usize) -> Self {
debug_assert!(index as u32 as usize == index);
Self(index as u32)
}
#[inline]
pub const fn as_u32(self) -> u32 {
self.0
}
#[inline]
pub const fn as_usize(self) -> usize {
self.0 as usize
}
#[inline]
pub const fn empty() -> Self {
Self(0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct TableRow(NonMaxU32);
impl TableRow {
#[inline]
pub const fn new(index: NonMaxU32) -> Self {
Self(index)
}
#[inline]
pub const fn index(self) -> usize {
self.0.get() as usize
}
#[inline]
pub const fn index_u32(self) -> u32 {
self.0.get()
}
}
pub(crate) struct TableBuilder {
columns: SparseSet<ComponentId, Column>,
entities: Vec<Entity>,
}
impl TableBuilder {
pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self {
Self {
columns: SparseSet::with_capacity(column_capacity),
entities: Vec::with_capacity(capacity),
}
}
#[must_use]
pub fn add_column(mut self, component_info: &ComponentInfo) -> Self {
self.columns.insert(
component_info.id(),
Column::with_capacity(component_info, self.entities.capacity()),
);
self
}
#[must_use]
pub fn build(self) -> Table {
Table {
columns: self.columns.into_immutable(),
entities: self.entities,
}
}
}
pub struct Table {
columns: ImmutableSparseSet<ComponentId, Column>,
entities: Vec<Entity>,
}
impl Table {
#[inline]
pub fn entities(&self) -> &[Entity] {
&self.entities
}
#[inline]
pub fn capacity(&self) -> usize {
self.entities.capacity()
}
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option<Entity> {
debug_assert!(row.index_u32() < self.entity_count());
let last_element_index = self.entity_count() - 1;
if row.index_u32() != last_element_index {
for col in self.columns.values_mut() {
unsafe {
col.swap_remove_and_drop_unchecked_nonoverlapping(
last_element_index as usize,
row,
);
};
}
} else {
for col in self.columns.values_mut() {
col.drop_last_component(last_element_index as usize);
}
}
let is_last = row.index_u32() == last_element_index;
self.entities.swap_remove(row.index());
if is_last {
None
} else {
unsafe { Some(*self.entities.get_unchecked(row.index())) }
}
}
pub(crate) unsafe fn move_to_and_forget_missing_unchecked(
&mut self,
row: TableRow,
new_table: &mut Table,
) -> TableMoveResult {
debug_assert!(row.index_u32() < self.entity_count());
let last_element_index = self.entity_count() - 1;
let is_last = row.index_u32() == last_element_index;
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,
last_element_index as usize,
row,
new_row,
);
} else {
column.swap_remove_and_forget_unchecked(last_element_index as usize, row);
}
}
TableMoveResult {
new_row,
swapped_entity: if is_last {
None
} else {
unsafe { Some(*self.entities.get_unchecked(row.index())) }
},
}
}
pub(crate) unsafe fn move_to_and_drop_missing_unchecked(
&mut self,
row: TableRow,
new_table: &mut Table,
) -> TableMoveResult {
debug_assert!(row.index_u32() < self.entity_count());
let last_element_index = self.entity_count() - 1;
let is_last = row.index_u32() == last_element_index;
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,
last_element_index as usize,
row,
new_row,
);
} else {
column.swap_remove_and_drop_unchecked(last_element_index as usize, row);
}
}
TableMoveResult {
new_row,
swapped_entity: if is_last {
None
} else {
unsafe { Some(*self.entities.get_unchecked(row.index())) }
},
}
}
pub(crate) unsafe fn move_to_superset_unchecked(
&mut self,
row: TableRow,
new_table: &mut Table,
) -> TableMoveResult {
debug_assert!(row.index_u32() < self.entity_count());
let last_element_index = self.entity_count() - 1;
let is_last = row.index_u32() == last_element_index;
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, last_element_index as usize, row, new_row);
}
TableMoveResult {
new_row,
swapped_entity: if is_last {
None
} else {
unsafe { Some(*self.entities.get_unchecked(row.index())) }
},
}
}
pub unsafe fn get_data_slice_for<T>(
&self,
component_id: ComponentId,
) -> Option<&[UnsafeCell<T>]> {
self.get_column(component_id)
.map(|col| col.get_data_slice(self.entity_count() as usize))
}
pub fn get_added_ticks_slice_for(
&self,
component_id: ComponentId,
) -> Option<&[UnsafeCell<Tick>]> {
self.get_column(component_id)
.map(|col| unsafe { col.get_added_ticks_slice(self.entity_count() as usize) })
}
pub fn get_changed_ticks_slice_for(
&self,
component_id: ComponentId,
) -> Option<&[UnsafeCell<Tick>]> {
self.get_column(component_id)
.map(|col| unsafe { col.get_changed_ticks_slice(self.entity_count() as usize) })
}
pub fn get_changed_by_slice_for(
&self,
component_id: ComponentId,
) -> MaybeLocation<Option<&[UnsafeCell<&'static Location<'static>>]>> {
MaybeLocation::new_with_flattened(|| {
self.get_column(component_id)
.map(|col| unsafe { col.get_changed_by_slice(self.entity_count() as usize) })
})
}
pub fn get_changed_tick(
&self,
component_id: ComponentId,
row: TableRow,
) -> Option<&UnsafeCell<Tick>> {
(row.index_u32() < self.entity_count()).then_some(
unsafe {
self.get_column(component_id)?
.changed_ticks
.get_unchecked(row.index())
},
)
}
pub fn get_added_tick(
&self,
component_id: ComponentId,
row: TableRow,
) -> Option<&UnsafeCell<Tick>> {
(row.index_u32() < self.entity_count()).then_some(
unsafe {
self.get_column(component_id)?
.added_ticks
.get_unchecked(row.index())
},
)
}
pub fn get_changed_by(
&self,
component_id: ComponentId,
row: TableRow,
) -> MaybeLocation<Option<&UnsafeCell<&'static Location<'static>>>> {
MaybeLocation::new_with_flattened(|| {
(row.index_u32() < self.entity_count()).then_some(
unsafe {
self.get_column(component_id)?
.changed_by
.as_ref()
.map(|changed_by| changed_by.get_unchecked(row.index()))
},
)
})
}
pub unsafe fn get_ticks_unchecked(
&self,
component_id: ComponentId,
row: TableRow,
) -> Option<ComponentTicks> {
self.get_column(component_id).map(|col| ComponentTicks {
added: col.added_ticks.get_unchecked(row.index()).read(),
changed: col.changed_ticks.get_unchecked(row.index()).read(),
})
}
#[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.capacity() - self.entity_count() as usize) < additional {
let column_cap = self.capacity();
self.entities.reserve(additional);
let new_capacity = self.entities.capacity();
if column_cap == 0 {
unsafe { self.alloc_columns(NonZeroUsize::new_unchecked(new_capacity)) };
} else {
unsafe {
self.realloc_columns(
NonZeroUsize::new_unchecked(column_cap),
NonZeroUsize::new_unchecked(new_capacity),
);
};
}
}
}
fn alloc_columns(&mut self, new_capacity: NonZeroUsize) {
let _guard = AbortOnPanic;
for col in self.columns.values_mut() {
col.alloc(new_capacity);
}
core::mem::forget(_guard); }
unsafe fn realloc_columns(
&mut self,
current_column_capacity: NonZeroUsize,
new_capacity: NonZeroUsize,
) {
let _guard = AbortOnPanic;
for col in self.columns.values_mut() {
col.realloc(current_column_capacity, new_capacity);
}
core::mem::forget(_guard); }
pub(crate) unsafe fn allocate(&mut self, entity: Entity) -> TableRow {
self.reserve(1);
let len = self.entity_count();
let row = unsafe { TableRow::new(NonMaxU32::new_unchecked(len)) };
let len = len as usize;
self.entities.push(entity);
for col in self.columns.values_mut() {
col.added_ticks
.initialize_unchecked(len, UnsafeCell::new(Tick::new(0)));
col.changed_ticks
.initialize_unchecked(len, UnsafeCell::new(Tick::new(0)));
col.changed_by
.as_mut()
.zip(MaybeLocation::caller())
.map(|(changed_by, caller)| {
changed_by.initialize_unchecked(len, UnsafeCell::new(caller));
});
}
row
}
#[inline]
pub fn entity_count(&self) -> u32 {
self.entities.len() as u32
}
#[inline]
pub fn get_drop_for(&self, component_id: ComponentId) -> Option<unsafe fn(OwningPtr<'_>)> {
self.get_column(component_id)?.data.drop
}
#[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, check: CheckChangeTicks) {
let len = self.entity_count() as usize;
for col in self.columns.values_mut() {
unsafe { col.check_change_ticks(len, check) };
}
}
pub fn iter_columns(&self) -> impl Iterator<Item = &Column> {
self.columns.values()
}
pub(crate) fn clear(&mut self) {
let len = self.entity_count() as usize;
self.entities.clear();
for column in self.columns.values_mut() {
unsafe { column.clear(len) };
}
}
pub(crate) unsafe fn take_component(
&mut self,
component_id: ComponentId,
row: TableRow,
) -> OwningPtr<'_> {
self.get_column_mut(component_id)
.debug_checked_unwrap()
.data
.get_unchecked_mut(row.index())
.promote()
}
pub unsafe fn get_component(
&self,
component_id: ComponentId,
row: TableRow,
) -> Option<Ptr<'_>> {
self.get_column(component_id)
.map(|col| col.data.get_unchecked(row.index()))
}
}
pub struct Tables {
tables: Vec<Table>,
table_ids: HashMap<Box<[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.as_usize())
}
#[inline]
pub(crate) fn get_2_mut(&mut self, a: TableId, b: TableId) -> (&mut Table, &mut Table) {
if a.as_usize() > b.as_usize() {
let (b_slice, a_slice) = self.tables.split_at_mut(a.as_usize());
(&mut a_slice[0], &mut b_slice[b.as_usize()])
} else {
let (a_slice, b_slice) = self.tables.split_at_mut(b.as_usize());
(&mut a_slice[a.as_usize()], &mut b_slice[0])
}
}
pub(crate) unsafe fn get_id_or_insert(
&mut self,
component_ids: &[ComponentId],
components: &Components,
) -> TableId {
if component_ids.is_empty() {
return TableId::empty();
}
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 = table.add_column(components.get_info_unchecked(*component_id));
}
tables.push(table.build());
(component_ids.into(), TableId::from_usize(tables.len() - 1))
});
*value
}
pub fn iter(&self) -> core::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, check: CheckChangeTicks) {
for table in &mut self.tables {
table.check_change_ticks(check);
}
}
}
impl Index<TableId> for Tables {
type Output = Table;
#[inline]
fn index(&self, index: TableId) -> &Self::Output {
&self.tables[index.as_usize()]
}
}
impl IndexMut<TableId> for Tables {
#[inline]
fn index_mut(&mut self, index: TableId) -> &mut Self::Output {
&mut self.tables[index.as_usize()]
}
}
impl Drop for Table {
fn drop(&mut self) {
let len = self.entity_count() as usize;
let cap = self.capacity();
self.entities.clear();
for col in self.columns.values_mut() {
unsafe {
col.drop(cap, len);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{
change_detection::{MaybeLocation, Tick},
component::{Component, ComponentIds, Components, ComponentsRegistrator},
entity::{Entity, EntityIndex},
ptr::OwningPtr,
storage::{TableBuilder, TableId, TableRow, Tables},
};
use alloc::vec::Vec;
#[derive(Component)]
struct W<T>(T);
#[test]
fn only_one_empty_table() {
let components = Components::default();
let mut tables = Tables::default();
let component_ids = &[];
let table_id = unsafe { tables.get_id_or_insert(component_ids, &components) };
assert_eq!(table_id, TableId::empty());
}
#[test]
fn table() {
let mut components = Components::default();
let mut componentids = ComponentIds::default();
let mut registrator =
unsafe { ComponentsRegistrator::new(&mut components, &mut componentids) };
let component_id = registrator.register_component::<W<TableRow>>();
let columns = &[component_id];
let mut table = TableBuilder::with_capacity(0, columns.len())
.add_column(components.get_info(component_id).unwrap())
.build();
let entities = (0..200)
.map(|index| Entity::from_index(EntityIndex::from_raw_u32(index).unwrap()))
.collect::<Vec<_>>();
for entity in &entities {
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),
MaybeLocation::caller(),
);
});
};
}
assert_eq!(table.entity_capacity(), 256);
assert_eq!(table.entity_count(), 200);
}
}