use alloc::boxed::Box;
use alloc::vec::Vec;
use core::any::TypeId;
use core::hash::{BuildHasher, Hash};
use hashbrown::hash_map::Entry;
use hashbrown::{DefaultHashBuilder, HashMap};
use indexmap::IndexMap;
use crate::any_index_map::DynIndexMap;
use crate::{ColumnId, MaybeSend, MaybeSync};
pub struct TypeTable<K, S = DefaultHashBuilder> {
indices: HashMap<TypeId, ColumnId>,
columns: Vec<DynIndexMap<K, S>>,
hasher: S,
}
impl<K> TypeTable<K> {
pub fn new() -> Self {
Self::with_hasher(DefaultHashBuilder::default())
}
}
impl<K, S> TypeTable<K, S> {
pub fn with_hasher(hasher: S) -> Self {
Self {
indices: HashMap::new(),
columns: Vec::new(),
hasher,
}
}
}
impl<K, S> TypeTable<K, S>
where
K: Eq + Hash + 'static,
S: BuildHasher,
{
pub fn get<V: 'static>(&self, key: &K) -> Option<&V> {
self.column::<V>()?.get(key)
}
pub fn get_mut<V: 'static>(&mut self, key: &K) -> Option<&mut V> {
self.column_mut::<V>()?.get_mut(key)
}
pub fn type_column<V: 'static>(&self) -> Option<ColumnId> {
self.indices.get(&TypeId::of::<V>()).copied()
}
pub fn get_by_column<V: 'static>(
&self,
col: ColumnId,
key: &K,
) -> Option<&V> {
self.columns.get(col.index())?.downcast_ref::<V>()?.get(key)
}
pub fn get_mut_by_column<V: 'static>(
&mut self,
col: ColumnId,
key: &K,
) -> Option<&mut V> {
self.columns
.get_mut(col.index())?
.downcast_mut::<V>()?
.get_mut(key)
}
pub fn contains<V: 'static>(&self, key: &K) -> bool {
self.column::<V>().is_some_and(|c| c.contains_key(key))
}
pub fn remove<V: 'static>(&mut self, key: &K) -> Option<V> {
self.column_mut::<V>()?.swap_remove(key)
}
pub fn remove_by_column<V: 'static>(
&mut self,
key: &K,
col: ColumnId,
) -> Option<V> {
self.columns
.get_mut(col.index())?
.downcast_mut::<V>()?
.swap_remove(key)
}
pub fn remove_row(&mut self, key: &K) -> bool {
let mut removed = false;
for column in self.columns.iter_mut() {
removed |= column.dyn_swap_remove(key);
}
removed
}
pub fn contains_row(&self, key: &K) -> bool {
self.columns.iter().any(|c| c.dyn_contains(key))
}
pub fn len<V: 'static>(&self) -> usize {
self.column::<V>().map_or(0, IndexMap::len)
}
pub fn is_empty<V: 'static>(&self) -> bool {
self.len::<V>() == 0
}
pub fn iter<V: 'static>(&self) -> impl Iterator<Item = (&K, &V)> {
self.column::<V>().into_iter().flat_map(IndexMap::iter)
}
pub fn clear<V: 'static>(&mut self) -> bool {
if let Some(column) = self.column_mut::<V>() {
column.clear();
return true;
}
false
}
fn column<V: 'static>(&self) -> Option<&IndexMap<K, V, S>> {
let col = *self.indices.get(&TypeId::of::<V>())?;
self.columns[col.index()].downcast_ref::<V>()
}
fn column_mut<V: 'static>(
&mut self,
) -> Option<&mut IndexMap<K, V, S>> {
let col = *self.indices.get(&TypeId::of::<V>())?;
self.columns[col.index()].downcast_mut::<V>()
}
}
impl<K, S> TypeTable<K, S>
where
K: Eq + Hash + 'static,
S: BuildHasher + Clone + 'static,
{
pub fn insert<V: 'static>(
&mut self,
key: K,
value: V,
) -> Option<V>
where
IndexMap<K, V, S>: MaybeSend + MaybeSync,
{
let col = self.ensure_column::<V>();
let column = unsafe {
self.columns[col.index()].downcast_unchecked_mut::<V>()
};
column.insert(key, value)
}
pub fn insert_by_column<V: 'static>(
&mut self,
key: K,
value: V,
col: ColumnId,
) -> Option<V> {
self.columns
.get_mut(col.index())?
.downcast_mut::<V>()?
.insert(key, value)
}
pub fn ensure_column<V: 'static>(&mut self) -> ColumnId
where
IndexMap<K, V, S>: MaybeSend + MaybeSync,
{
match self.indices.entry(TypeId::of::<V>()) {
Entry::Occupied(e) => *e.get(),
Entry::Vacant(e) => {
let col = ColumnId::new(self.columns.len());
self.columns.push(Box::new(
IndexMap::<K, V, S>::with_hasher(
self.hasher.clone(),
),
));
*e.insert(col)
}
}
}
}
impl<K> Default for TypeTable<K> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use alloc::string::String;
use super::{ColumnId, TypeTable};
#[derive(Debug, PartialEq, Clone, Copy)]
struct Position(f32, f32);
#[derive(Debug, PartialEq, Clone)]
struct Name(String);
#[test]
fn insert_and_get() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
assert_eq!(
table.get::<Position>(&0),
Some(&Position(1.0, 2.0))
);
}
#[test]
fn heterogeneous_by_shared_key() {
let mut table = TypeTable::<u32>::new();
table.insert(7, Position(3.0, 4.0));
table.insert(7, Name(String::from("player")));
assert_eq!(
table.get::<Position>(&7),
Some(&Position(3.0, 4.0))
);
assert_eq!(
table.get::<Name>(&7),
Some(&Name(String::from("player")))
);
}
#[test]
fn insert_returns_previous() {
let mut table = TypeTable::<u32>::new();
assert_eq!(table.insert(1, Position(0.0, 0.0)), None);
assert_eq!(
table.insert(1, Position(9.0, 9.0)),
Some(Position(0.0, 0.0))
);
}
#[test]
fn get_mut_modifies_value() {
let mut table = TypeTable::<u32>::new();
table.insert(1, Position(0.0, 0.0));
table.get_mut::<Position>(&1).unwrap().0 = 5.0;
assert_eq!(
table.get::<Position>(&1),
Some(&Position(5.0, 0.0))
);
}
#[test]
fn remove_returns_value() {
let mut table = TypeTable::<u32>::new();
table.insert(1, Position(5.0, 5.0));
assert_eq!(
table.remove::<Position>(&1),
Some(Position(5.0, 5.0))
);
assert!(table.get::<Position>(&1).is_none());
assert!(table.remove::<Position>(&1).is_none());
}
#[test]
fn remove_swaps_last_into_place() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(0.0, 0.0));
table.insert(1, Position(1.0, 1.0));
table.insert(2, Position(2.0, 2.0));
assert_eq!(
table.remove::<Position>(&0),
Some(Position(0.0, 0.0))
);
assert_eq!(
table.get::<Position>(&2),
Some(&Position(2.0, 2.0))
);
assert_eq!(
table.get::<Position>(&1),
Some(&Position(1.0, 1.0))
);
assert_eq!(table.len::<Position>(), 2);
}
#[test]
fn remove_row_drops_every_column() {
let mut table = TypeTable::<u32>::new();
table.insert(1, Position(1.0, 1.0));
table.insert(1, Name(String::from("player")));
assert!(table.remove_row(&1));
assert!(table.get::<Position>(&1).is_none());
assert!(table.get::<Name>(&1).is_none());
assert!(!table.remove_row(&1));
}
#[test]
fn contains_row_reports_any_column() {
let mut table = TypeTable::<u32>::new();
assert!(!table.contains_row(&1));
table.insert(1, Name(String::from("player")));
assert!(table.contains_row(&1));
assert!(!table.contains_row(&2));
}
#[test]
fn type_isolation() {
let mut table = TypeTable::<u32>::new();
table.insert(0, 50u64);
assert!(table.get::<u32>(&0).is_none());
assert_eq!(table.get::<u64>(&0), Some(&50u64));
}
#[test]
fn contains_reflects_presence() {
let mut table = TypeTable::<u32>::new();
assert!(!table.contains::<Position>(&0));
table.insert(0, Position(0.0, 0.0));
assert!(table.contains::<Position>(&0));
}
#[test]
fn clear_empties_column() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(0.0, 0.0));
table.insert(1, Position(1.0, 1.0));
assert!(table.clear::<Position>());
assert_eq!(table.len::<Position>(), 0);
assert!(table.get::<Position>(&0).is_none());
}
#[test]
fn clear_absent_column_returns_false() {
let mut table = TypeTable::<u32>::new();
assert!(!table.clear::<Position>());
}
#[test]
fn iter_yields_all_pairs() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(0.0, 0.0));
table.insert(1, Position(1.0, 1.0));
let mut keys = table
.iter::<Position>()
.map(|(k, _)| *k)
.collect::<alloc::vec::Vec<_>>();
keys.sort();
assert_eq!(keys, [0, 1]);
}
#[test]
fn type_column_none_before_insert() {
let table = TypeTable::<u32>::new();
assert_eq!(table.type_column::<Position>(), None);
}
#[test]
fn type_column_stable_across_inserts() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(0.0, 0.0));
let col = table.type_column::<Position>().unwrap();
table.insert(1, Position(1.0, 1.0));
table.insert(0, Name(String::from("player")));
assert_eq!(table.type_column::<Position>(), Some(col));
}
#[test]
fn ensure_column_is_idempotent() {
let mut table = TypeTable::<u32>::new();
let first = table.ensure_column::<Position>();
let second = table.ensure_column::<Position>();
assert_eq!(first, second);
assert_eq!(table.type_column::<Position>(), Some(first));
}
#[test]
fn ensure_column_distinct_per_type() {
let mut table = TypeTable::<u32>::new();
let pos = table.ensure_column::<Position>();
let name = table.ensure_column::<Name>();
assert_ne!(pos, name);
}
#[test]
fn get_by_column_matches_get() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
let col = table.type_column::<Position>().unwrap();
assert_eq!(
table.get_by_column::<Position>(col, &0),
table.get::<Position>(&0),
);
}
#[test]
fn get_by_column_wrong_type_returns_none() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
let col = table.type_column::<Position>().unwrap();
assert_eq!(table.get_by_column::<Name>(col, &0), None);
}
#[test]
fn get_by_column_out_of_bounds_returns_none() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
assert_eq!(
table.get_by_column::<Position>(
ColumnId::PLACEHOLDER,
&0,
),
None,
);
}
#[test]
fn get_by_column_absent_key_returns_none() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
let col = table.type_column::<Position>().unwrap();
assert_eq!(table.get_by_column::<Position>(col, &9), None);
}
#[test]
fn get_mut_by_column_modifies_value() {
let mut table = TypeTable::<u32>::new();
table.insert(1, Position(0.0, 0.0));
let col = table.type_column::<Position>().unwrap();
table.get_mut_by_column::<Position>(col, &1).unwrap().0 = 5.0;
assert_eq!(
table.get::<Position>(&1),
Some(&Position(5.0, 0.0))
);
}
#[test]
fn get_mut_by_column_wrong_type_returns_none() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
let col = table.type_column::<Position>().unwrap();
assert_eq!(table.get_mut_by_column::<Name>(col, &0), None);
}
#[test]
fn get_mut_by_column_out_of_bounds_returns_none() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
assert_eq!(
table.get_mut_by_column::<Position>(
ColumnId::PLACEHOLDER,
&0,
),
None,
);
}
#[test]
fn get_mut_by_column_absent_key_returns_none() {
let mut table = TypeTable::<u32>::new();
table.insert(0, Position(1.0, 2.0));
let col = table.type_column::<Position>().unwrap();
assert_eq!(
table.get_mut_by_column::<Position>(col, &9),
None
);
}
}