use crate::slots::PAGE;
struct Slot<T> {
generation: u32,
value: Option<T>,
}
pub(crate) struct Table<T> {
pages: Vec<Box<[Slot<T>]>>,
free: Vec<usize>,
ceiling: usize,
live: usize,
}
impl<T> Table<T> {
pub fn new(capacity: usize) -> Self {
let mut table = Self {
pages: Vec::new(),
free: Vec::new(),
ceiling: capacity,
live: 0,
};
table.grow();
table
}
pub fn remaining(&self) -> usize {
self.ceiling - self.live
}
fn grow(&mut self) -> bool {
let base = self.pages.len() * PAGE;
if base >= self.ceiling {
return false;
}
let len = PAGE.min(self.ceiling - base);
self.pages.push(
(0..len)
.map(|_| Slot {
generation: 1,
value: None,
})
.collect::<Vec<_>>()
.into_boxed_slice(),
);
self.free.extend((base..base + len).rev());
true
}
fn slot(&self, i: usize) -> Option<&Slot<T>> {
self.pages.get(i / PAGE)?.get(i % PAGE)
}
fn slot_mut(&mut self, i: usize) -> Option<&mut Slot<T>> {
self.pages.get_mut(i / PAGE)?.get_mut(i % PAGE)
}
pub fn insert(&mut self, value: T) -> Option<u64> {
if self.free.is_empty() && !self.grow() {
return None;
}
let i = self.free.pop()?;
self.live += 1;
let slot = self.slot_mut(i).expect("free index names a built slot");
slot.value = Some(value);
Some((u64::from(slot.generation) << 32) | i as u64)
}
pub fn get(&self, key: u64) -> Option<&T> {
let s = self.slot(key as u32 as usize)?;
(s.generation == (key >> 32) as u32)
.then_some(s.value.as_ref())
.flatten()
}
pub fn get_mut(&mut self, key: u64) -> Option<&mut T> {
let s = self.slot_mut(key as u32 as usize)?;
(s.generation == (key >> 32) as u32)
.then_some(s.value.as_mut())
.flatten()
}
pub fn remove(&mut self, key: u64) -> Option<T> {
let i = key as u32 as usize;
let s = self.slot_mut(i)?;
if s.generation != (key >> 32) as u32 {
return None;
}
let value = s.value.take()?;
if let Some(generation) = s.generation.checked_add(1) {
s.generation = generation;
self.free.push(i);
self.live -= 1;
}
Some(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stale_generation_never_reaches_reused_slot() {
let mut t = Table::new(1);
let a = t.insert(12).expect("capacity");
assert_eq!(t.remove(a), Some(12));
let b = t.insert(34).expect("capacity");
assert_ne!(a, b);
assert_eq!(t.get(a), None);
assert_eq!(t.remove(a), None);
assert_eq!(t.get(b), Some(&34));
}
#[test]
fn a_key_taken_before_growth_still_names_its_value_after_it() {
let mut t = Table::new(4096);
let early: Vec<u64> = (0..PAGE).map(|i| t.insert(i).expect("capacity")).collect();
assert_eq!(t.remaining(), 4096 - PAGE);
let late: Vec<u64> = (PAGE..PAGE * 8)
.map(|i| t.insert(i).expect("capacity"))
.collect();
for (i, key) in early.iter().enumerate() {
assert_eq!(
t.get(*key),
Some(&i),
"key {key} lost its value across growth"
);
assert_eq!(*key as u32 as usize, i, "key {key} changed slot");
}
for (n, key) in late.iter().enumerate() {
assert_eq!(t.get(*key), Some(&(PAGE + n)));
}
for (i, key) in early.iter().enumerate() {
assert_eq!(t.remove(*key), Some(i));
}
}
#[test]
fn the_ceiling_is_a_ceiling_and_a_partial_page_does_not_exceed_it() {
let mut t: Table<usize> = Table::new(PAGE + 3);
let keys: Vec<u64> = (0..PAGE + 3)
.map(|i| t.insert(i).expect("within the ceiling"))
.collect();
assert_eq!(t.remaining(), 0);
assert!(t.insert(0).is_none(), "the ceiling refuses one more");
assert!(
keys.iter().all(|k| (*k as u32 as usize) < PAGE + 3),
"no key addresses a slot past the ceiling"
);
t.remove(keys[0]).expect("live");
assert_eq!(t.remaining(), 1);
assert!(t.insert(99).is_some(), "a freed slot is offered again");
}
#[test]
fn growth_builds_one_page_at_a_time() {
let mut t: Table<usize> = Table::new(1_000_000);
assert_eq!(t.pages.len(), 1, "page zero comes with the table");
t.insert(0).expect("capacity");
assert_eq!(t.pages.len(), 1, "one page serves the first insert");
for i in 1..PAGE {
t.insert(i).expect("capacity");
}
assert_eq!(
t.pages.len(),
1,
"the page is filled before the next is built"
);
t.insert(PAGE).expect("capacity");
assert_eq!(t.pages.len(), 2);
}
#[test]
fn a_retired_slot_is_not_counted_as_remaining_capacity() {
let mut t: Table<usize> = Table::new(2);
let key = t.insert(0).expect("capacity");
assert_eq!(t.remaining(), 1);
let i = key as u32 as usize;
t.slot_mut(i).expect("built").generation = u32::MAX;
let saturated = (u64::from(u32::MAX) << 32) | i as u64;
assert_eq!(t.remove(saturated), Some(0));
assert_eq!(t.remaining(), 1, "the retired slot stays spent");
assert!(t.insert(1).is_some(), "the other slot is still available");
assert_eq!(t.remaining(), 0);
assert!(
t.insert(2).is_none(),
"a retired slot is never offered again"
);
}
}