use std::collections::HashMap;
use std::hash::{BuildHasherDefault, Hasher};
use std::sync::Arc;
use crate::Name;
const LINEAR_LOOKUP_MAX: usize = 8;
#[derive(Clone, Copy)]
pub(crate) struct NameHasher(u64);
impl Default for NameHasher {
fn default() -> Self {
Self(0xcbf2_9ce4_8422_2325)
}
}
impl Hasher for NameHasher {
#[inline]
fn write(&mut self, bytes: &[u8]) {
for &b in bytes {
self.0 = (self.0 ^ u64::from(b)).wrapping_mul(0x0000_0100_0000_01b3);
}
}
#[inline]
fn finish(&self) -> u64 {
self.0
}
}
#[derive(Clone, Default)]
pub(crate) struct NameDict {
inner: Arc<Inner>,
}
#[derive(Clone, Default)]
struct Inner {
names: Vec<Name>,
ids: HashMap<Name, u32, BuildHasherDefault<NameHasher>>,
}
impl NameDict {
#[inline]
pub(crate) fn id_of(&self, name: &str) -> Option<u32> {
let inner = &*self.inner;
if inner.names.len() <= LINEAR_LOOKUP_MAX {
inner
.names
.iter()
.position(|n| n.as_str() == name)
.map(|i| i as u32)
} else {
inner.ids.get(name).copied()
}
}
pub(crate) fn id_or_insert(&mut self, name: &Name) -> u32 {
self.id_or_insert_with(name, || name.clone())
}
pub(crate) fn id_or_insert_with(&mut self, name: &str, make: impl FnOnce() -> Name) -> u32 {
if let Some(id) = self.id_of(name) {
return id;
}
let name = make();
let inner = Arc::make_mut(&mut self.inner);
let id = inner.names.len() as u32;
inner.names.push(name.clone());
inner.ids.insert(name, id);
id
}
#[cfg(test)]
pub(crate) fn id_or_insert_str(&mut self, name: &str) -> u32 {
match self.id_of(name) {
Some(id) => id,
None => self.id_or_insert(&Name::new(name)),
}
}
#[inline]
pub(crate) fn name(&self, id: u32) -> &Name {
&self.inner.names[id as usize]
}
#[cfg(test)]
pub(crate) fn len(&self) -> usize {
self.inner.names.len()
}
pub(crate) fn heap_bytes(&self) -> usize {
let inner = &*self.inner;
inner.names.capacity() * std::mem::size_of::<Name>()
+ inner.ids.capacity() * (std::mem::size_of::<Name>() + std::mem::size_of::<u32>() + 1)
}
}
#[derive(Clone, Default)]
pub(crate) struct Dicts {
pub(crate) labels: NameDict,
pub(crate) types: NameDict,
pub(crate) keys: NameDict,
}
impl Dicts {
pub(crate) fn heap_bytes(&self) -> usize {
self.labels.heap_bytes() + self.types.heap_bytes() + self.keys.heap_bytes()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn names_are_numbered_once_in_both_lookup_modes() {
let mut dict = NameDict::default();
let names: Vec<Name> = (0..20).map(|i| Name::new(&format!("N{i}"))).collect();
for (i, name) in names.iter().enumerate() {
assert_eq!(dict.id_or_insert(name), i as u32);
for (j, earlier) in names[..=i].iter().enumerate() {
assert_eq!(dict.id_of(earlier), Some(j as u32));
assert_eq!(dict.name(j as u32), earlier);
}
}
assert_eq!(dict.id_or_insert(&names[3]), 3);
assert_eq!(dict.id_or_insert_str("N7"), 7);
assert_eq!(dict.id_or_insert_str("fresh"), 20);
assert_eq!(dict.id_of("missing"), None);
assert_eq!(dict.len(), 21);
}
#[test]
fn a_clone_shares_the_table_until_one_side_adds_a_name() {
let mut dict = NameDict::default();
dict.id_or_insert_str("A");
let mut copy = dict.clone();
assert_eq!(copy.id_or_insert_str("B"), 1);
assert_eq!(dict.id_of("B"), None);
assert_eq!(dict.id_or_insert_str("C"), 1);
assert_eq!(copy.name(1).as_str(), "B");
assert_eq!(dict.name(1).as_str(), "C");
}
}