use yo_common::{bytes_eq, hash_key, tag_of};
use crate::blob::Blob;
use crate::scan::Cursor;
pub const MAX_ROWS: usize = 0x00FF_FFFE;
pub const NAME_MAX: usize = u16::MAX as usize;
const EMPTY: u32 = u32::MAX;
const LOAD_NUM: usize = 3;
const LOAD_DEN: usize = 4;
const MIN_SLOTS: usize = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct NameRef {
at: u32,
len: u16,
}
#[derive(Debug, Clone, Copy)]
struct Row {
name: NameRef,
home: u32,
}
#[derive(Debug, Clone)]
pub struct Elements<V> {
slots: Box<[u32]>,
rows: Vec<Row>,
vals: Vec<V>,
names: Blob,
}
impl<V: Copy> Default for Elements<V> {
fn default() -> Elements<V> {
Elements::new()
}
}
impl<V: Copy> Elements<V> {
#[must_use]
pub fn new() -> Elements<V> {
Elements {
slots: Box::new([]),
rows: Vec::new(),
vals: Vec::new(),
names: Blob::new(),
}
}
#[must_use]
pub fn with_capacity(n: usize) -> Elements<V> {
let mut e = Elements::new();
e.reserve(n);
e
}
pub fn reserve(&mut self, n: usize) {
if n == 0 {
return;
}
self.rows.reserve(n.saturating_sub(self.rows.len()));
self.vals.reserve(n.saturating_sub(self.vals.len()));
if n * LOAD_DEN > self.slots.len() * LOAD_NUM {
self.grow_to(slots_for(n));
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.rows.len()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
#[inline]
#[must_use]
pub fn get(&self, name: &[u8]) -> Option<&V> {
let at = self.find(name)?;
Some(&self.vals[at])
}
#[inline]
pub fn get_mut(&mut self, name: &[u8]) -> Option<&mut V> {
let at = self.find(name)?;
Some(&mut self.vals[at])
}
#[inline]
#[must_use]
pub fn contains(&self, name: &[u8]) -> bool {
self.find(name).is_some()
}
#[inline]
#[must_use]
pub fn index_of(&self, name: &[u8]) -> Option<usize> {
self.find(name)
}
#[inline]
#[must_use]
pub fn hash_of(name: &[u8]) -> u64 {
hash(name)
}
#[inline]
#[must_use]
pub fn contains_hashed(&self, h: u64, name: &[u8]) -> bool {
self.find_hashed(h, name).is_some()
}
#[inline]
#[must_use]
pub fn index_of_hashed(&self, h: u64, name: &[u8]) -> Option<usize> {
self.find_hashed(h, name)
}
#[inline]
#[must_use]
pub fn get_hashed(&self, h: u64, name: &[u8]) -> Option<&V> {
let at = self.find_hashed(h, name)?;
Some(&self.vals[at])
}
#[inline]
pub fn get_hashed_mut(&mut self, h: u64, name: &[u8]) -> Option<&mut V> {
let at = self.find_hashed(h, name)?;
Some(&mut self.vals[at])
}
pub fn insert(&mut self, name: &[u8], value: V) -> Result<Option<V>, Full> {
self.insert_hashed(hash(name), name, value)
}
pub fn insert_hashed(&mut self, h: u64, name: &[u8], value: V) -> Result<Option<V>, Full> {
if name.len() > NAME_MAX {
return Err(Full::Name);
}
if let Some(at) = self.find_hashed(h, name) {
return Ok(Some(std::mem::replace(&mut self.vals[at], value)));
}
if self.rows.len() >= MAX_ROWS {
return Err(Full::Rows);
}
self.reserve_one();
let at = u32::try_from(self.rows.len()).expect("MAX_ROWS is under u32::MAX");
let name_ref = self.push_name(name);
self.rows.push(Row {
name: name_ref,
home: h as u32,
});
self.vals.push(value);
self.put_slot(h, at);
Ok(None)
}
pub fn remove(&mut self, name: &[u8]) -> Option<V> {
let at = self.find(name)?;
Some(self.remove_row(at))
}
#[inline]
pub fn remove_hashed(&mut self, h: u64, name: &[u8]) -> Option<V> {
let at = self.find_hashed(h, name)?;
Some(self.remove_row(at))
}
pub fn remove_at(&mut self, idx: usize) -> Option<V> {
if idx >= self.rows.len() {
return None;
}
Some(self.remove_row(idx))
}
#[inline]
#[must_use]
pub fn at(&self, idx: usize) -> Option<(&[u8], &V)> {
let row = self.rows.get(idx)?;
Some((self.name_of(row), &self.vals[idx]))
}
#[inline]
pub fn at_mut(&mut self, idx: usize) -> Option<&mut V> {
self.vals.get_mut(idx)
}
pub fn take_at(&mut self, idx: usize) -> Option<(Vec<u8>, V)> {
if idx >= self.rows.len() {
return None;
}
let name = self.name_of(&self.rows[idx]).to_vec();
let value = self.remove_row(idx);
Some((name, value))
}
pub fn iter(&self) -> impl Iterator<Item = (&[u8], &V)> {
self.rows
.iter()
.zip(&self.vals)
.map(|(r, v)| (self.name_of(r), v))
}
pub fn payloads_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.vals.iter_mut()
}
pub fn scan<F>(&self, cursor: Cursor, count: usize, mut f: F) -> Cursor
where
F: FnMut(&[u8], &V),
{
if self.rows.is_empty() {
return Cursor::END;
}
let here = cursor.rebase(1);
let top = self.rows.len() - 1;
let mut at = match here.idx() {
Some(idx) => (idx as usize).min(top),
None => top,
};
for _ in 0..count.max(1) {
let row = &self.rows[at];
f(self.name_of(row), &self.vals[at]);
if at == 0 {
return Cursor::END;
}
at -= 1;
}
Cursor::at(1, 0, at as u64)
}
pub fn clear(&mut self) {
self.rows.clear();
self.vals.clear();
self.names.clear();
for slot in &mut self.slots {
*slot = EMPTY;
}
}
#[must_use]
pub fn memory_bytes(&self) -> usize {
self.slot_bytes() + self.row_bytes() + self.names.memory_bytes()
}
#[must_use]
pub fn slot_bytes(&self) -> usize {
self.slots.len() * size_of::<u32>()
}
#[must_use]
pub fn row_bytes(&self) -> usize {
self.rows.capacity() * size_of::<Row>() + self.vals.capacity() * size_of::<V>()
}
#[must_use]
pub fn name_bytes(&self) -> usize {
self.names.memory_bytes()
}
#[inline]
#[must_use]
pub const fn dead_name_bytes(&self) -> usize {
self.names.dead()
}
#[inline]
fn find(&self, name: &[u8]) -> Option<usize> {
self.find_hashed(hash(name), name)
}
#[inline]
fn find_hashed(&self, h: u64, name: &[u8]) -> Option<usize> {
if self.rows.is_empty() {
return None;
}
let mask = self.slots.len() - 1;
let tag = tag_of(h);
let mut at = (h as usize) & mask;
loop {
let slot = self.slots[at];
if slot == EMPTY {
return None;
}
if slot >> 24 == u32::from(tag) {
let row = (slot & 0x00FF_FFFF) as usize;
if bytes_eq(self.name_of(&self.rows[row]), name) {
return Some(row);
}
}
at = (at + 1) & mask;
}
}
fn put_slot(&mut self, h: u64, row: u32) {
let mask = self.slots.len() - 1;
let mut at = (h as usize) & mask;
while self.slots[at] != EMPTY {
at = (at + 1) & mask;
}
self.slots[at] = (u32::from(tag_of(h)) << 24) | row;
}
fn remove_row(&mut self, at: usize) -> V {
let last = self.rows.len() - 1;
self.clear_slot(at);
if at != last {
let moved = self.rows[last].home;
self.repoint(moved, last, at);
self.rows.swap(at, last);
self.vals.swap(at, last);
}
let row = self.rows.pop().expect("the table was not empty");
let value = self.vals.pop().expect("a payload per row");
self.names.release(row.name.len as usize);
self.maybe_compact_names();
value
}
fn clear_slot(&mut self, row: usize) {
let mask = self.slots.len() - 1;
let mut at = (self.rows[row].home as usize) & mask;
loop {
let slot = self.slots[at];
debug_assert!(slot != EMPTY, "the row being removed has a slot");
if slot != EMPTY && (slot & 0x00FF_FFFF) as usize == row {
break;
}
at = (at + 1) & mask;
}
self.slots[at] = EMPTY;
let mut hole = at;
let mut scan = (at + 1) & mask;
while self.slots[scan] != EMPTY {
let slot = self.slots[scan];
let idx = (slot & 0x00FF_FFFF) as usize;
let home = (self.rows[idx].home as usize) & mask;
if (scan.wrapping_sub(home) & mask) >= (scan.wrapping_sub(hole) & mask) {
self.slots[hole] = slot;
self.slots[scan] = EMPTY;
hole = scan;
}
scan = (scan + 1) & mask;
}
}
fn repoint(&mut self, home: u32, from: usize, to: usize) {
let mask = self.slots.len() - 1;
let mut at = (home as usize) & mask;
loop {
let slot = self.slots[at];
debug_assert!(slot != EMPTY, "the row being moved has a slot");
if slot != EMPTY && (slot & 0x00FF_FFFF) as usize == from {
self.slots[at] =
(slot & 0xFF00_0000) | u32::try_from(to).expect("a row index fits in 24 bits");
return;
}
at = (at + 1) & mask;
}
}
fn reserve_one(&mut self) {
let want = self.rows.len() + 1;
crate::grow::reserve(&mut self.rows, 1);
crate::grow::reserve(&mut self.vals, 1);
if want * LOAD_DEN > self.slots.len() * LOAD_NUM {
self.grow_to(slots_for(want));
}
}
fn grow_to(&mut self, slots: usize) {
let slots = slots.max(MIN_SLOTS).next_power_of_two();
let mask = slots - 1;
let old = std::mem::replace(&mut self.slots, vec![EMPTY; slots].into_boxed_slice());
for &slot in &old {
if slot == EMPTY {
continue;
}
let row = (slot & 0x00FF_FFFF) as usize;
let mut at = (self.rows[row].home as usize) & mask;
while self.slots[at] != EMPTY {
at = (at + 1) & mask;
}
self.slots[at] = slot;
}
}
fn push_name(&mut self, name: &[u8]) -> NameRef {
NameRef {
at: self.names.push(name),
len: u16::try_from(name.len()).expect("the caller checked NAME_MAX"),
}
}
#[inline]
fn name_of(&self, row: &Row) -> &[u8] {
self.names.read(row.name.at, row.name.len as usize)
}
fn maybe_compact_names(&mut self) {
if !self.names.worth_compacting() {
return;
}
let rows = &mut self.rows;
self.names.compact(|keep| {
for row in rows.iter_mut() {
keep.moved(&mut row.name.at, row.name.len as usize);
}
});
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Full {
Name,
Rows,
}
#[inline]
fn hash(name: &[u8]) -> u64 {
hash_key(name)
}
fn slots_for(n: usize) -> usize {
((n * LOAD_DEN) / LOAD_NUM + 1)
.max(MIN_SLOTS)
.next_power_of_two()
}
#[cfg(test)]
mod tests {
use super::*;
type Set = Elements<()>;
fn set(members: &[&[u8]]) -> Set {
let mut s = Set::new();
for m in members {
s.insert(m, ()).expect("room");
}
s
}
#[test]
fn an_empty_table_allocates_nothing() {
let e = Set::new();
assert_eq!(e.len(), 0);
assert!(e.is_empty());
assert_eq!(e.memory_bytes(), 0);
assert!(!e.contains(b"anything"));
}
#[test]
fn what_goes_in_comes_out() {
let mut h: Elements<u64> = Elements::new();
assert_eq!(h.insert(b"name", 7), Ok(None));
assert_eq!(h.insert(b"age", 41), Ok(None));
assert_eq!(h.get(b"name"), Some(&7));
assert_eq!(h.get(b"age"), Some(&41));
assert_eq!(h.get(b"missing"), None);
assert_eq!(h.len(), 2);
}
#[test]
fn writing_a_field_again_replaces_it_and_says_so() {
let mut h: Elements<u64> = Elements::new();
assert_eq!(h.insert(b"f", 1), Ok(None));
assert_eq!(h.insert(b"f", 2), Ok(Some(1)));
assert_eq!(h.len(), 1, "an overwrite is not a second element");
assert_eq!(h.get(b"f"), Some(&2));
}
#[test]
fn rewriting_a_field_does_not_write_its_name_again() {
let mut h: Elements<u64> = Elements::new();
h.insert(b"a-fairly-long-field-name", 1).expect("room");
let after_first = h.memory_bytes();
for i in 0..1000 {
h.insert(b"a-fairly-long-field-name", i).expect("room");
}
assert_eq!(h.memory_bytes(), after_first);
assert_eq!(h.dead_name_bytes(), 0);
}
#[test]
fn removing_takes_the_element_out() {
let mut s = set(&[b"a", b"b", b"c"]);
assert_eq!(s.remove(b"b"), Some(()));
assert_eq!(s.remove(b"b"), None);
assert_eq!(s.len(), 2);
assert!(s.contains(b"a"));
assert!(s.contains(b"c"));
assert!(!s.contains(b"b"));
}
#[test]
fn the_rows_stay_dense_through_removals() {
let mut s = set(&[b"a", b"b", b"c", b"d", b"e"]);
s.remove(b"a").expect("there");
s.remove(b"c").expect("there");
assert_eq!(s.len(), 3);
let mut seen: Vec<Vec<u8>> = (0..s.len())
.map(|i| s.at(i).expect("dense").0.to_vec())
.collect();
seen.sort();
assert_eq!(seen, vec![b"b".to_vec(), b"d".to_vec(), b"e".to_vec()]);
assert_eq!(s.at(3), None);
}
#[test]
fn a_set_drained_one_draw_at_a_time_stays_correct() {
let names: Vec<Vec<u8>> = (0..500u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
let mut taken = Vec::new();
while !s.is_empty() {
let idx = (taken.len() * 7 + 3) % s.len();
let (name, ()) = s.take_at(idx).expect("in range");
assert!(!s.contains(&name), "it came out and stayed out");
taken.push(name);
}
assert_eq!(taken.len(), names.len());
taken.sort();
let mut want = names;
want.sort();
assert_eq!(taken, want);
}
#[test]
fn removals_do_not_hide_what_is_behind_them() {
let mut s = Set::new();
let names: Vec<Vec<u8>> = (0..200u32).map(|i| format!("k{i}").into_bytes()).collect();
for n in &names {
s.insert(n, ()).expect("room");
}
for n in names.iter().step_by(3) {
assert_eq!(s.remove(n), Some(()));
}
for (i, n) in names.iter().enumerate() {
assert_eq!(s.contains(n), i % 3 != 0, "member {i}");
}
}
#[test]
fn growth_keeps_everything_findable() {
let names: Vec<Vec<u8>> = (0..5000u32)
.map(|i| format!("member-number-{i}").into_bytes())
.collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
assert_eq!(s.len(), names.len());
for n in &names {
assert!(s.contains(n));
}
assert!(!s.contains(b"member-number-5000"));
}
#[test]
fn a_walk_reads_them_in_the_order_they_went_in() {
let s = set(&[b"first", b"second", b"third"]);
let seen: Vec<&[u8]> = s.iter().map(|(n, ())| n).collect();
assert_eq!(seen, vec![&b"first"[..], &b"second"[..], &b"third"[..]]);
}
#[test]
fn presizing_does_not_change_what_the_table_says() {
let mut a = Set::with_capacity(1000);
let mut b = Set::new();
for i in 0..1000u32 {
let n = format!("m{i}").into_bytes();
a.insert(&n, ()).expect("room");
b.insert(&n, ()).expect("room");
}
assert_eq!(a.len(), b.len());
for i in 0..1000u32 {
assert!(a.contains(format!("m{i}").as_bytes()));
}
}
#[test]
fn a_name_that_is_too_long_is_refused_and_not_truncated() {
let mut s = Set::new();
let long = vec![b'x'; NAME_MAX + 1];
assert_eq!(s.insert(&long, ()), Err(Full::Name));
assert!(s.is_empty());
let ok = vec![b'x'; NAME_MAX];
assert_eq!(s.insert(&ok, ()), Ok(None));
}
#[test]
fn dead_name_bytes_come_back() {
let mut s = Set::new();
let long: Vec<Vec<u8>> = (0..400u32)
.map(|i| format!("{i:0>64}").into_bytes())
.collect();
for n in &long {
s.insert(n, ()).expect("room");
}
let full = s.memory_bytes();
for n in long.iter().take(390) {
s.remove(n).expect("there");
}
assert!(
s.memory_bytes() < full,
"the blob shrank, {} against {full}",
s.memory_bytes()
);
assert!(
s.dead_name_bytes() < 4096,
"{} bytes left dead",
s.dead_name_bytes()
);
for n in long.iter().skip(390) {
assert!(s.contains(n), "still findable after the blob moved");
}
}
#[test]
fn clearing_keeps_the_allocation_and_forgets_the_elements() {
let mut s = set(&[b"a", b"b", b"c"]);
let before = s.memory_bytes();
s.clear();
assert!(s.is_empty());
assert!(!s.contains(b"a"));
assert_eq!(s.memory_bytes(), before, "the room is kept for the refill");
s.insert(b"a", ()).expect("room");
assert!(s.contains(b"a"));
}
#[test]
fn no_live_slot_can_look_empty() {
let mut s = Set::new();
for i in 0..2000u32 {
s.insert(format!("m{i}").as_bytes(), ()).expect("room");
}
assert_eq!(s.slots.iter().filter(|v| **v != EMPTY).count(), s.len());
const { assert!(MAX_ROWS < 0x00FF_FFFF, "a row index is never all ones") }
}
fn scan_all(s: &Set, page: usize) -> Vec<Vec<u8>> {
let mut out = Vec::new();
let mut c = Cursor::START;
loop {
c = s.scan(c, page, |n, ()| out.push(n.to_vec()));
if c.is_end() {
return out;
}
}
}
#[test]
fn a_scan_of_a_still_collection_returns_everything_once() {
let names: Vec<Vec<u8>> = (0..300u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
for page in [1, 7, 10, 1000] {
let mut seen = scan_all(&s, page);
assert_eq!(seen.len(), names.len(), "page {page} returned a duplicate");
seen.sort();
let mut want = names.clone();
want.sort();
assert_eq!(seen, want, "page {page}");
}
}
#[test]
fn scanning_an_empty_collection_is_over_immediately() {
let s = Set::new();
let mut hit = 0;
assert!(s.scan(Cursor::START, 10, |_, ()| hit += 1).is_end());
assert_eq!(hit, 0);
}
#[test]
fn a_scan_never_misses_a_member_that_stayed() {
let names: Vec<Vec<u8>> = (0..400u32).map(|i| format!("m{i}").into_bytes()).collect();
let mut s = Set::new();
for n in &names {
s.insert(n, ()).expect("room");
}
let doomed: Vec<Vec<u8>> = names.iter().step_by(7).cloned().collect();
let mut gone = 0usize;
let mut seen: Vec<Vec<u8>> = Vec::new();
let mut c = Cursor::START;
loop {
c = s.scan(c, 9, |n, ()| seen.push(n.to_vec()));
for n in doomed.iter().skip(gone).take(3) {
s.remove(n);
}
gone = (gone + 3).min(doomed.len());
if c.is_end() {
break;
}
}
for n in &names {
if doomed.contains(n) {
continue;
}
assert!(
seen.contains(n),
"{} was there all along",
String::from_utf8_lossy(n)
);
}
}
#[test]
fn a_stale_cursor_is_answered_and_not_refused() {
let s = set(&[b"a", b"b", b"c"]);
let mut seen = Vec::new();
let c = s.scan(Cursor::at(1, 0, 900), 2, |n, ()| seen.push(n.to_vec()));
assert_eq!(seen, vec![b"c".to_vec(), b"b".to_vec()]);
assert_eq!(c.idx(), Some(0));
let mut also = Vec::new();
s.scan(Cursor::at(16, 9, 4), 99, |n, ()| also.push(n.to_vec()));
assert_eq!(also.len(), 3);
}
#[test]
fn taking_by_index_and_by_name_leave_the_same_table() {
let mut by_index = set(&[b"a", b"b", b"c", b"d"]);
let mut by_name = set(&[b"a", b"b", b"c", b"d"]);
let name = by_index.at(1).expect("in range").0.to_vec();
assert_eq!(by_index.remove_at(1), Some(()));
assert_eq!(by_name.remove(&name), Some(()));
assert_eq!(by_index.remove_at(99), None);
let mut left: Vec<Vec<u8>> = by_index.iter().map(|(n, ())| n.to_vec()).collect();
let mut also: Vec<Vec<u8>> = by_name.iter().map(|(n, ())| n.to_vec()).collect();
left.sort();
also.sort();
assert_eq!(left, also);
assert_eq!(left.len(), 3);
}
#[test]
fn a_row_is_twelve_bytes_whatever_the_collection_stores() {
assert_eq!(size_of::<Row>(), 12);
assert_eq!(size_of::<Row>() + size_of::<()>(), 12, "a set member");
assert_eq!(size_of::<Row>() + size_of::<f64>(), 20, "a sorted set");
assert_eq!(
size_of::<Row>() + size_of::<crate::blob::Span>(),
20,
"a hash field"
);
}
#[test]
fn a_payload_can_be_changed_in_place() {
let mut h: Elements<i64> = Elements::new();
h.insert(b"counter", 1).expect("room");
*h.get_mut(b"counter").expect("there") += 41;
assert_eq!(h.get(b"counter"), Some(&42));
assert_eq!(h.get_mut(b"nothing"), None);
}
}