use std::sync::{Arc, LazyLock, Once};
use catallaxy_gc::{RootSource, Tracer};
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use crate::object::{mark_value, str_of};
use crate::value::Value;
pub const MAX_SHAPE_KEYS: usize = 32;
const MAX_SHAPES: usize = 1 << 16;
pub struct Shape {
pub id: u32,
keys: Vec<(u64, Value)>,
transitions: Mutex<FxHashMap<Box<str>, Arc<Shape>>>,
}
struct ShapeRegistry {
count: usize,
keys: Vec<Value>,
}
static REGISTRY: Mutex<ShapeRegistry> = Mutex::new(ShapeRegistry { count: 1, keys: Vec::new() });
static ROOT_SHAPE: LazyLock<Arc<Shape>> =
LazyLock::new(|| Arc::new(Shape { id: 0, keys: Vec::new(), transitions: Mutex::new(FxHashMap::default()) }));
static SHAPE_ROOTS: Once = Once::new();
struct ShapeKeyRoots;
impl RootSource for ShapeKeyRoots {
fn trace_roots(&self, t: &mut Tracer) {
for &k in REGISTRY.lock().keys.iter() {
mark_value(t, k);
}
}
}
impl Shape {
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
self.keys.is_empty()
}
#[inline]
pub fn slot_of(&self, hash: u64, k: &str) -> Option<usize> {
self.keys.iter().position(|&(h, key)| h == hash && str_of(key) == Some(k))
}
fn add(self: &Arc<Shape>, hash: u64, key: Value) -> Option<Arc<Shape>> {
let k = str_of(key)?;
let mut tr = self.transitions.lock();
if let Some(next) = tr.get(k) {
return Some(next.clone());
}
let mut reg = REGISTRY.lock();
if reg.count >= MAX_SHAPES {
return None;
}
SHAPE_ROOTS.call_once(|| catallaxy_gc::add_global_roots(Box::new(ShapeKeyRoots)));
let id = reg.count as u32;
reg.count += 1;
reg.keys.push(key);
let mut keys = self.keys.clone();
keys.push((hash, key));
let next = Arc::new(Shape { id, keys, transitions: Mutex::new(FxHashMap::default()) });
tr.insert(k.into(), next.clone());
Some(next)
}
}
#[derive(Copy, Clone)]
pub struct Entry {
pub hash: u64,
pub key: Value,
pub value: Value,
}
const EMPTY: u32 = u32::MAX;
#[derive(Default)]
pub struct DictTable {
entries: Vec<Entry>,
indices: Vec<u32>,
live: usize,
version: u64,
shape: Option<Arc<Shape>>,
values: Vec<Value>,
}
pub enum Probe {
Found(usize),
Absent,
Check(Vec<usize>),
}
impl DictTable {
pub fn new() -> Self {
DictTable::default()
}
pub fn with_capacity(n: usize) -> Self {
let mut t = DictTable::default();
if n > 0 {
t.entries.reserve(n);
t.indices = vec![EMPTY; (n * 3 / 2).next_power_of_two().max(8)];
}
t
}
pub fn record(n: usize) -> Self {
DictTable { shape: Some(ROOT_SHAPE.clone()), values: Vec::with_capacity(n), ..DictTable::default() }
}
#[inline]
pub fn shape(&self) -> Option<&Arc<Shape>> {
self.shape.as_ref()
}
#[inline]
pub fn slot_value(&self, slot: usize) -> Option<Value> {
self.values.get(slot).copied()
}
fn leave_shape_mode(&mut self) {
let Some(shape) = self.shape.take() else { return };
let values = std::mem::take(&mut self.values);
let n = values.len();
self.entries = Vec::with_capacity(n + 1);
self.indices = vec![EMPTY; ((n + 1) * 3).next_power_of_two().max(8)];
for (i, (&(hash, key), value)) in shape.keys.iter().zip(values).enumerate() {
self.entries.push(Entry { hash, key, value });
self.place(hash, i as u32);
}
crate::note_external(self.heap_bytes());
}
pub fn len(&self) -> usize {
self.live
}
pub fn is_empty(&self) -> bool {
self.live == 0
}
pub fn version(&self) -> u64 {
self.version
}
pub fn heap_bytes(&self) -> usize {
self.entries.capacity() * std::mem::size_of::<Entry>() + self.indices.capacity() * 4 + self.values.capacity() * 8
}
pub fn trace(&self, t: &mut Tracer) {
for &v in &self.values {
mark_value(t, v);
}
for e in &self.entries {
if e.key != Value::TOMBSTONE {
mark_value(t, e.key);
mark_value(t, e.value);
}
}
}
pub fn iter(&self) -> impl Iterator<Item = Entry> + '_ {
(0..self.raw_len()).filter_map(|i| self.raw_entry(i))
}
pub fn entry(&self, idx: usize) -> Entry {
match &self.shape {
Some(shape) => {
let (hash, key) = shape.keys[idx];
Entry { hash, key, value: self.values[idx] }
}
None => self.entries[idx],
}
}
pub fn set_value_at(&mut self, idx: usize, v: Value) {
match self.shape {
Some(_) => self.values[idx] = v,
None => self.entries[idx].value = v,
}
}
pub fn raw_len(&self) -> usize {
match self.shape {
Some(_) => self.values.len(),
None => self.entries.len(),
}
}
pub fn raw_entry(&self, i: usize) -> Option<Entry> {
match self.shape {
Some(_) => (i < self.values.len()).then(|| self.entry(i)),
None => self.entries.get(i).filter(|e| e.key != Value::TOMBSTONE).copied(),
}
}
pub fn probe(&self, hash: u64, key: Value) -> Probe {
if let Some(shape) = &self.shape {
return match str_of(key).and_then(|k| shape.slot_of(hash, k)) {
Some(i) => Probe::Found(i),
None => Probe::Absent,
};
}
if self.indices.is_empty() {
return Probe::Absent;
}
let mask = self.indices.len() - 1;
let mut i = (hash as usize) & mask;
let mut perturb = hash;
let mut check = Vec::new();
loop {
let ix = self.indices[i];
if ix == EMPTY {
return if check.is_empty() { Probe::Absent } else { Probe::Check(check) };
}
let e = &self.entries[ix as usize];
if e.key != Value::TOMBSTONE && e.hash == hash {
if e.key == key {
return Probe::Found(ix as usize);
}
check.push(ix as usize);
}
perturb >>= 5;
i = (i.wrapping_mul(5).wrapping_add(1).wrapping_add(perturb as usize)) & mask;
}
}
pub fn insert_new(&mut self, hash: u64, key: Value, value: Value) {
if let Some(shape) = &self.shape {
if shape.len() < MAX_SHAPE_KEYS {
if let Some(next) = shape.add(hash, key) {
self.shape = Some(next);
self.values.push(value);
self.live += 1;
self.version += 1;
return;
}
}
self.leave_shape_mode();
}
if (self.entries.len() + 1) * 3 >= self.indices.len() * 2 {
self.grow();
}
let idx = self.entries.len() as u32;
self.entries.push(Entry { hash, key, value });
self.place(hash, idx);
self.live += 1;
self.version += 1;
}
fn place(&mut self, hash: u64, idx: u32) {
let mask = self.indices.len() - 1;
let mut i = (hash as usize) & mask;
let mut perturb = hash;
while self.indices[i] != EMPTY {
perturb >>= 5;
i = (i.wrapping_mul(5).wrapping_add(1).wrapping_add(perturb as usize)) & mask;
}
self.indices[i] = idx;
}
fn grow(&mut self) {
if self.live < self.entries.len() {
self.entries.retain(|e| e.key != Value::TOMBSTONE);
}
let want = ((self.live + 1) * 3).next_power_of_two().max(8);
self.indices = vec![EMPTY; want];
for i in 0..self.entries.len() {
let h = self.entries[i].hash;
self.place(h, i as u32);
}
crate::note_external(want * 4);
}
pub fn remove_at(&mut self, idx: usize) -> Value {
self.leave_shape_mode();
let e = &mut self.entries[idx];
let v = e.value;
e.key = Value::TOMBSTONE;
e.value = Value::NIL;
self.live -= 1;
self.version += 1;
v
}
pub fn clear(&mut self) {
if self.shape.is_some() {
self.shape = Some(ROOT_SHAPE.clone());
}
self.values.clear();
self.entries.clear();
self.indices.clear();
self.live = 0;
self.version += 1;
}
pub fn first_live(&self) -> Option<usize> {
if self.shape.is_some() {
return (!self.values.is_empty()).then_some(0);
}
self.entries.iter().position(|e| e.key != Value::TOMBSTONE)
}
}
impl Clone for DictTable {
fn clone(&self) -> Self {
if let Some(shape) = &self.shape {
return DictTable {
shape: Some(shape.clone()),
values: self.values.clone(),
live: self.live,
..DictTable::default()
};
}
let mut t = DictTable::with_capacity(self.live);
for e in self.iter() {
t.insert_new(e.hash, e.key, e.value);
}
t
}
}