use alloc::vec::Vec;
use crate::ecs::entity::Entity;
use crate::ecs::mask::{ComponentId, ComponentMask};
const NO_ROW: u32 = u32::MAX;
#[derive(Default, Debug)]
pub struct JoinIndex {
occupants: Vec<Option<Entity>>,
masks: Vec<ComponentMask>,
rows: Vec<Vec<u32>>,
}
impl JoinIndex {
pub fn new() -> JoinIndex {
JoinIndex::default()
}
pub fn set(&mut self, entity: Entity, id: ComponentId, row: u32) {
let index = entity.index() as usize;
self.grow_to(index);
if self.occupants[index] != Some(entity) {
self.reset_index(index);
self.occupants[index] = Some(entity);
}
self.masks[index].insert(id);
let column = self.row_column(id.get());
if index >= column.len() {
column.resize(index + 1, NO_ROW);
}
column[index] = row;
}
pub fn clear(&mut self, entity: Entity, id: ComponentId) {
let index = entity.index() as usize;
if self.occupants.get(index).copied().flatten() != Some(entity) {
return;
}
self.masks[index].remove(id);
if let Some(slot) = self
.rows
.get_mut(id.get() as usize)
.and_then(|column| column.get_mut(index))
{
*slot = NO_ROW;
}
if self.masks[index].is_empty() {
self.occupants[index] = None;
}
}
pub fn clear_entity(&mut self, entity: Entity) {
let index = entity.index() as usize;
if self.occupants.get(index).copied().flatten() != Some(entity) {
return;
}
self.reset_index(index);
}
pub fn mask(&self, entity: Entity) -> ComponentMask {
let index = entity.index() as usize;
if self.occupants.get(index).copied().flatten() != Some(entity) {
return ComponentMask::EMPTY;
}
self.masks
.get(index)
.copied()
.unwrap_or(ComponentMask::EMPTY)
}
pub fn row(&self, entity: Entity, id: ComponentId) -> Option<u32> {
let index = entity.index() as usize;
if self.occupants.get(index).copied().flatten() != Some(entity) {
return None;
}
let row = self.rows.get(id.get() as usize)?.get(index).copied()?;
(row != NO_ROW).then_some(row)
}
pub fn matches(
&self,
entity: Entity,
required: ComponentMask,
excluded: ComponentMask,
) -> bool {
let mask = self.mask(entity);
mask.contains_all(required) && mask.is_disjoint(excluded)
}
fn reset_index(&mut self, index: usize) {
if let Some(mask) = self.masks.get_mut(index) {
*mask = ComponentMask::EMPTY;
}
for column in &mut self.rows {
if let Some(slot) = column.get_mut(index) {
*slot = NO_ROW;
}
}
if index < self.occupants.len() {
self.occupants[index] = None;
}
}
fn grow_to(&mut self, index: usize) {
if index >= self.occupants.len() {
self.occupants.resize(index + 1, None);
}
if index >= self.masks.len() {
self.masks.resize(index + 1, ComponentMask::EMPTY);
}
}
fn row_column(&mut self, id: u8) -> &mut Vec<u32> {
let id = id as usize;
if id >= self.rows.len() {
self.rows.resize_with(id + 1, Vec::new);
}
&mut self.rows[id]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ecs::entity::Entities;
fn ids() -> (ComponentId, ComponentId, ComponentId) {
(
ComponentId::new(1),
ComponentId::new(2),
ComponentId::new(3),
)
}
#[test]
fn set_records_mask_and_row() {
let mut entities = Entities::new();
let e = entities.alloc();
let (transform, mesh, _) = ids();
let mut join = JoinIndex::new();
join.set(e, transform, 0);
join.set(e, mesh, 7);
assert!(join.mask(e).contains(transform));
assert!(join.mask(e).contains(mesh));
assert_eq!(join.row(e, transform), Some(0));
assert_eq!(join.row(e, mesh), Some(7));
assert_eq!(join.row(e, ComponentId::new(9)), None);
}
#[test]
fn clear_removes_one_component() {
let mut entities = Entities::new();
let e = entities.alloc();
let (transform, mesh, _) = ids();
let mut join = JoinIndex::new();
join.set(e, transform, 0);
join.set(e, mesh, 1);
join.clear(e, mesh);
assert!(join.mask(e).contains(transform));
assert!(!join.mask(e).contains(mesh));
assert_eq!(join.row(e, mesh), None);
assert_eq!(join.row(e, transform), Some(0));
}
#[test]
fn clear_last_component_frees_the_slot() {
let mut entities = Entities::new();
let e = entities.alloc();
let (transform, _, _) = ids();
let mut join = JoinIndex::new();
join.set(e, transform, 0);
join.clear(e, transform);
assert!(join.mask(e).is_empty());
assert_eq!(join.row(e, transform), None);
}
#[test]
fn clear_entity_removes_everything() {
let mut entities = Entities::new();
let e = entities.alloc();
let (transform, mesh, collider) = ids();
let mut join = JoinIndex::new();
join.set(e, transform, 0);
join.set(e, mesh, 1);
join.set(e, collider, 2);
join.clear_entity(e);
assert!(join.mask(e).is_empty());
assert_eq!(join.row(e, transform), None);
assert_eq!(join.row(e, mesh), None);
assert_eq!(join.row(e, collider), None);
}
#[test]
fn matches_required_and_excluded() {
let mut entities = Entities::new();
let e = entities.alloc();
let (transform, mesh, collider) = ids();
let mut join = JoinIndex::new();
join.set(e, transform, 0);
join.set(e, mesh, 1);
let required = ComponentMask::with(transform);
let want_mesh = {
let mut m = ComponentMask::with(transform);
m.insert(mesh);
m
};
assert!(join.matches(e, required, ComponentMask::with(collider)));
assert!(join.matches(e, want_mesh, ComponentMask::EMPTY));
assert!(!join.matches(e, required, ComponentMask::with(mesh)));
assert!(!join.matches(e, ComponentMask::with(collider), ComponentMask::EMPTY));
}
#[test]
fn distinct_entities_are_independent() {
let mut entities = Entities::new();
let a = entities.alloc();
let b = entities.alloc();
let (transform, mesh, _) = ids();
let mut join = JoinIndex::new();
join.set(a, transform, 0);
join.set(b, mesh, 0);
assert!(join.mask(a).contains(transform));
assert!(!join.mask(a).contains(mesh));
assert!(join.mask(b).contains(mesh));
assert!(!join.mask(b).contains(transform));
}
#[test]
fn stale_handle_resolves_to_empty_not_the_recycled_occupant() {
let mut entities = Entities::new();
let a = entities.alloc();
let (transform, mesh, _) = ids();
let mut join = JoinIndex::new();
join.set(a, transform, 0);
join.set(a, mesh, 5);
entities.despawn(a);
let b = entities.alloc();
assert_eq!(a.index(), b.index());
assert_ne!(a, b);
join.set(b, transform, 0);
assert!(join.mask(b).contains(transform));
assert!(!join.mask(b).contains(mesh));
assert_eq!(join.row(b, mesh), None);
assert!(join.mask(a).is_empty());
assert_eq!(join.row(a, transform), None);
}
#[test]
fn recycled_index_rejects_old_generation_before_overwrite() {
let mut entities = Entities::new();
let a = entities.alloc();
let (transform, _, _) = ids();
let mut join = JoinIndex::new();
join.set(a, transform, 0);
entities.despawn(a);
let b = entities.alloc();
assert_eq!(a.index(), b.index());
assert!(join.mask(b).is_empty());
assert_eq!(join.row(b, transform), None);
}
}