use std::sync::Arc;
use crate::gpu::shaders as wgsl;
const LEAST: usize = 64;
const EMPTY: [u32; 2] = [wgsl::NONE, wgsl::NONE];
#[derive(Clone, Default, Debug, PartialEq)]
pub(crate) struct KeptTable(Arc<[[u32; 2]]>);
impl KeptTable {
pub(crate) fn laid(before: &KeptTable, rows: &[[u32; 2]], gone: &[u32], came: &[[u32; 2]]) -> KeptTable {
let buckets = before.0.len();
if buckets > 0 && rows.len() * 5 <= buckets * 4 && (rows.len() * 10 >= buckets * 3 || buckets == LEAST) {
let mut table = before.0.to_vec();
for &d in gone {
remove(&mut table, d);
}
for &row in came {
insert(&mut table, row);
}
return KeptTable(table.into());
}
if rows.is_empty() {
return KeptTable::default();
}
let mut table = vec![EMPTY; (rows.len() * 5).div_ceil(3).max(LEAST)];
for &row in rows {
insert(&mut table, row);
}
KeptTable(table.into())
}
pub(crate) fn held(&self, d: u32) -> Option<u32> {
if self.0.is_empty() {
return None;
}
let mut b = home(d, self.0.len());
loop {
match self.0[b] {
[place, held] if place == d => return Some(held),
[wgsl::NONE, _] => return None,
_ => b = next(b, self.0.len()),
}
}
}
pub(crate) fn buckets(&self) -> &[[u32; 2]] {
&self.0
}
}
fn home(d: u32, buckets: usize) -> usize {
let mut h = d ^ (d >> 16);
h = h.wrapping_mul(0x7feb_352d);
h ^= h >> 15;
h = h.wrapping_mul(0x846c_a68b);
(h ^ (h >> 16)) as usize % buckets
}
fn next(b: usize, buckets: usize) -> usize {
if b + 1 == buckets { 0 } else { b + 1 }
}
fn insert(table: &mut [[u32; 2]], row: [u32; 2]) {
let mut b = home(row[0], table.len());
while table[b][0] != wgsl::NONE {
b = next(b, table.len());
}
table[b] = row;
}
fn remove(table: &mut [[u32; 2]], d: u32) {
let mut hole = home(d, table.len());
while table[hole][0] != d {
if table[hole][0] == wgsl::NONE {
return;
}
hole = next(hole, table.len());
}
let mut b = hole;
loop {
b = next(b, table.len());
let [place, _] = table[b];
if place == wgsl::NONE {
break;
}
let home = home(place, table.len());
let stays = if hole <= b { hole < home && home <= b } else { hole < home || home <= b };
if !stays {
table[hole] = table[b];
hole = b;
}
}
table[hole] = EMPTY;
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use super::*;
fn random(seed: &mut u64, n: u32) -> u32 {
*seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
((*seed >> 33) % u64::from(n)) as u32
}
#[test]
fn a_table_holds_every_kept_row_and_no_other() {
let mut seed = 7;
let mut kept: BTreeMap<u32, u32> = BTreeMap::new();
let mut table = KeptTable::default();
for round in 0..200 {
let (mut gone, mut came) = (Vec::new(), Vec::new());
for _ in 0..random(&mut seed, 40) {
let d = random(&mut seed, 5_000);
if gone.contains(&d) || came.iter().any(|row: &[u32; 2]| row[0] == d) {
continue;
}
if kept.remove(&d).is_some() {
gone.push(d);
} else {
kept.insert(d, round);
came.push([d, round]);
}
}
let rows: Vec<[u32; 2]> = kept.iter().map(|(&d, &held)| [d, held]).collect();
table = KeptTable::laid(&table, &rows, &gone, &came);
let buckets = table.buckets().len();
assert!(buckets == 0 || kept.len() * 5 <= buckets * 4, "at most four fifths full");
for d in 0..5_000 {
assert_eq!(table.held(d), kept.get(&d).copied(), "place {d} after round {round}");
}
}
}
#[test]
fn rows_coming_and_going_change_only_nearby_buckets() {
let rows: Vec<[u32; 2]> = (0..1_000).map(|d| [d * 7, d]).collect();
let table = KeptTable::laid(&KeptTable::default(), &rows, &[], &[]);
let gone: Vec<u32> = (0..10).map(|d| d * 70).collect();
let came: Vec<[u32; 2]> = (0..10).map(|d| [100_000 + d, d]).collect();
let now: Vec<[u32; 2]> =
rows.iter().filter(|r| !gone.contains(&r[0])).copied().chain(came.iter().copied()).collect();
let next = KeptTable::laid(&table, &now, &gone, &came);
assert_eq!(next.buckets().len(), table.buckets().len(), "updated in place");
let changed = table.buckets().iter().zip(next.buckets()).filter(|(a, b)| a != b).count();
assert!(changed <= 60, "{changed} buckets changed for 20 rows that came or went");
}
#[test]
fn a_table_is_rebuilt_past_four_fifths_full_and_under_three_tenths() {
let rows = |n: u32| (0..n).map(|d| [d, d]).collect::<Vec<_>>();
let small = KeptTable::laid(&KeptTable::default(), &rows(10), &[], &[]);
assert_eq!(small.buckets().len(), LEAST, "few rows get the fewest buckets");
let full = KeptTable::laid(&small, &rows(100), &[], &(10..100).map(|d| [d, d]).collect::<Vec<_>>());
assert_eq!(full.buckets().len(), 167, "past four fifths of 64, rebuilt three fifths full");
let more = KeptTable::laid(&full, &rows(130), &[], &(100..130).map(|d| [d, d]).collect::<Vec<_>>());
assert_eq!(more.buckets().len(), 167, "up to four fifths full, updated in place");
let gone: Vec<u32> = (51..130).collect();
assert_eq!(KeptTable::laid(&more, &rows(51), &gone, &[]).buckets().len(), 167, "three tenths, in place");
let gone: Vec<u32> = (20..130).collect();
assert_eq!(KeptTable::laid(&more, &rows(20), &gone, &[]).buckets().len(), LEAST, "under three tenths, rebuilt");
let gone: Vec<u32> = (0..130).collect();
assert_eq!(KeptTable::laid(&more, &[], &gone, &[]), KeptTable::default(), "no rows, no buckets");
}
}