use crate::hash::TypeIdMap;
use std::any::TypeId;
use crate::component::{Column, ColumnData, Component, Ticks};
use crate::{EcsError, Entity};
pub(crate) struct Archetype {
types: Vec<TypeId>,
columns: Vec<Box<dyn Column>>,
entities: Vec<Entity>,
add_edges: TypeIdMap<TypeId, usize>,
remove_edges: TypeIdMap<TypeId, usize>,
bundle_edges: TypeIdMap<TypeId, usize>,
}
impl Archetype {
pub(crate) fn new(types: Vec<TypeId>, columns: Vec<Box<dyn Column>>) -> Self {
debug_assert!(types.windows(2).all(|w| matches!(w, [a, b] if a < b)));
debug_assert_eq!(types.len(), columns.len());
Self {
types,
columns,
entities: Vec::new(),
add_edges: TypeIdMap::default(),
remove_edges: TypeIdMap::default(),
bundle_edges: TypeIdMap::default(),
}
}
pub(crate) fn types(&self) -> &[TypeId] {
&self.types
}
pub(crate) fn columns(&self) -> &[Box<dyn Column>] {
&self.columns
}
pub(crate) fn entities(&self) -> &[Entity] {
&self.entities
}
pub(crate) fn len(&self) -> usize {
self.entities.len()
}
pub(crate) fn parts_mut(&mut self) -> (&[TypeId], &mut [Box<dyn Column>], &[Entity]) {
(&self.types, &mut self.columns, &self.entities)
}
pub(crate) fn has(&self, ty: TypeId) -> bool {
self.types.binary_search(&ty).is_ok()
}
pub(crate) fn get<T: Component>(&self, row: usize) -> Option<&T> {
let column = self.types.binary_search(&TypeId::of::<T>()).ok()?;
self.columns
.get(column)?
.as_any()
.downcast_ref::<ColumnData<T>>()?
.values()
.get(row)
}
pub(crate) fn get_mut<T: Component>(&mut self, row: usize, tick: u64) -> Option<&mut T> {
let (value, changed) = self.column_mut::<T>()?.get_mut(row)?;
*changed = tick;
Some(value)
}
pub(crate) fn column_mut<T: Component>(&mut self) -> Option<&mut ColumnData<T>> {
let column = self.types.binary_search(&TypeId::of::<T>()).ok()?;
self.columns
.get_mut(column)?
.as_any_mut()
.downcast_mut::<ColumnData<T>>()
}
pub(crate) fn ticks(&self, ty: TypeId) -> Option<Ticks<'_>> {
let column = self.types.binary_search(&ty).ok()?;
Some(self.columns.get(column)?.ticks())
}
pub(crate) fn add_edge(&self, ty: TypeId) -> Option<usize> {
self.add_edges.get(&ty).copied()
}
pub(crate) fn remove_edge(&self, ty: TypeId) -> Option<usize> {
self.remove_edges.get(&ty).copied()
}
pub(crate) fn bundle_edge(&self, bundle: TypeId) -> Option<usize> {
self.bundle_edges.get(&bundle).copied()
}
pub(crate) fn cache_bundle_edge(&mut self, bundle: TypeId, target: usize) {
if self.bundle_edges.try_reserve(1).is_ok() {
self.bundle_edges.insert(bundle, target);
}
}
pub(crate) fn cache_edge(&mut self, add: bool, ty: TypeId, target: usize) {
let edges = if add {
&mut self.add_edges
} else {
&mut self.remove_edges
};
if edges.try_reserve(1).is_ok() {
edges.insert(ty, target);
}
}
pub(crate) fn reserve_row(&mut self) -> Result<(), EcsError> {
let entities = self.entities.len().saturating_add(1);
let oom = |_| EcsError::OutOfMemory { entities };
self.entities.try_reserve(1).map_err(oom)?;
for column in &mut self.columns {
column.reserve_rows(1).map_err(oom)?;
}
Ok(())
}
pub(crate) fn push_entity(&mut self, entity: Entity) -> usize {
let row = self.entities.len();
self.entities.push(entity);
row
}
pub(crate) fn push_component<T: Component>(&mut self, value: T, tick: u64) -> bool {
match self.column_mut::<T>() {
Some(column) => {
column.push(value, tick, tick);
true
}
None => false,
}
}
pub(crate) fn put_component<T: Component>(&mut self, row: usize, value: T, tick: u64) -> bool {
let Some(column) = self.column_mut::<T>() else {
return false;
};
match column.get_mut(row) {
Some((slot, changed)) => {
*slot = value;
*changed = tick;
}
None => {
debug_assert_eq!(column.len(), row, "a bundle writes the next row");
column.push(value, tick, tick);
}
}
true
}
pub(crate) fn is_aligned(&self) -> bool {
let rows = self.entities.len();
self.columns.iter().all(|column| {
let ticks = column.ticks();
ticks.added.len() == rows && ticks.changed.len() == rows
})
}
pub(crate) fn swap_remove_entity(&mut self, row: usize) -> Option<Entity> {
if row >= self.entities.len() {
return None;
}
self.entities.swap_remove(row);
self.entities.get(row).copied()
}
pub(crate) fn drop_row(&mut self, row: usize) {
for column in &mut self.columns {
let dropped = column.swap_remove_drop(row);
debug_assert!(dropped, "row {row} out of range");
}
}
pub(crate) fn move_row(
&mut self,
row: usize,
dst: &mut Self,
mut leftover: impl FnMut(TypeId, &mut dyn Column),
) {
for (ty, column) in self.types.iter().zip(&mut self.columns) {
match dst
.types
.binary_search(ty)
.ok()
.and_then(|i| dst.columns.get_mut(i))
{
Some(target) => {
let moved = column.swap_remove_into(row, target.as_mut());
debug_assert!(moved, "row {row} out of range or column type mismatch");
}
None => leftover(*ty, column.as_mut()),
}
}
}
}
pub(crate) fn two_mut<T>(slice: &mut [T], a: usize, b: usize) -> Option<(&mut T, &mut T)> {
if a < b {
let (left, right) = slice.split_at_mut_checked(b)?;
Some((left.get_mut(a)?, right.first_mut()?))
} else if b < a {
let (left, right) = slice.split_at_mut_checked(a)?;
let (eb, ea) = (left.get_mut(b)?, right.first_mut()?);
Some((ea, eb))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn two_mut_rejects_aliasing_and_out_of_range() {
let mut v = [1, 2, 3];
assert!(two_mut(&mut v, 1, 1).is_none());
assert!(two_mut(&mut v, 0, 3).is_none());
let (a, b) = two_mut(&mut v, 2, 0).unwrap();
std::mem::swap(a, b);
assert_eq!(v, [3, 2, 1]);
}
}