use std::collections::HashMap;
use std::fmt;
use std::sync::Mutex;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ShapeId(pub u32);
impl fmt::Display for ShapeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Shape({})", self.0)
}
}
#[derive(Debug, Clone)]
pub struct Shape {
pub id: ShapeId,
pub properties: Vec<u32>,
pub parent: Option<ShapeId>,
pub property_count: u16,
}
pub struct ShapeTransitionTable {
transitions: HashMap<(ShapeId, u32), ShapeId>,
shapes: Vec<Shape>,
next_id: u32,
}
impl ShapeTransitionTable {
pub fn new() -> Self {
let root = Shape {
id: ShapeId(0),
properties: Vec::new(),
parent: None,
property_count: 0,
};
Self {
transitions: HashMap::new(),
shapes: vec![root],
next_id: 1,
}
}
#[inline]
pub fn root() -> ShapeId {
ShapeId(0)
}
#[inline]
pub fn get_shape(&self, id: ShapeId) -> &Shape {
&self.shapes[id.0 as usize]
}
pub fn transition(&mut self, from: ShapeId, property_name: u32) -> ShapeId {
if let Some(&existing) = self.transitions.get(&(from, property_name)) {
return existing;
}
let parent_shape = &self.shapes[from.0 as usize];
let mut properties = parent_shape.properties.clone();
properties.push(property_name);
let new_id = ShapeId(self.next_id);
self.next_id += 1;
let new_shape = Shape {
id: new_id,
properties,
parent: Some(from),
property_count: self.shapes[from.0 as usize].property_count + 1,
};
self.shapes.push(new_shape);
self.transitions.insert((from, property_name), new_id);
new_id
}
pub fn shape_for_keys(&mut self, keys: &[u32]) -> ShapeId {
let mut current = Self::root();
for &key in keys {
current = self.transition(current, key);
}
current
}
pub fn property_index(&self, shape_id: ShapeId, property_name: u32) -> Option<usize> {
let shape = &self.shapes[shape_id.0 as usize];
shape.properties.iter().position(|&p| p == property_name)
}
#[inline]
pub fn shape_count(&self) -> usize {
self.shapes.len()
}
}
static GLOBAL_SHAPE_TABLE: std::sync::LazyLock<Mutex<ShapeTransitionTable>> =
std::sync::LazyLock::new(|| Mutex::new(ShapeTransitionTable::new()));
static SHAPE_TRANSITION_LOG: std::sync::LazyLock<Mutex<Vec<(ShapeId, ShapeId)>>> =
std::sync::LazyLock::new(|| Mutex::new(Vec::new()));
pub fn drain_shape_transitions() -> Vec<(ShapeId, ShapeId)> {
SHAPE_TRANSITION_LOG
.lock()
.map(|mut log| std::mem::take(&mut *log))
.unwrap_or_default()
}
pub fn shape_for_hashmap_keys(key_hashes: &[u32]) -> Option<ShapeId> {
if key_hashes.len() > 64 {
return None; }
let mut table = GLOBAL_SHAPE_TABLE.lock().ok()?;
Some(table.shape_for_keys(key_hashes))
}
pub fn shape_property_index(shape_id: ShapeId, property_hash: u32) -> Option<usize> {
let table = GLOBAL_SHAPE_TABLE.lock().ok()?;
table.property_index(shape_id, property_hash)
}
pub fn shape_transition(from: ShapeId, property_hash: u32) -> Option<ShapeId> {
let mut table = GLOBAL_SHAPE_TABLE.lock().ok()?;
let shape = table.get_shape(from);
if shape.property_count >= 64 {
return None; }
let new_id = table.transition(from, property_hash);
if let Ok(mut log) = SHAPE_TRANSITION_LOG.lock() {
log.push((from, new_id));
}
Some(new_id)
}
#[inline]
pub fn hash_property_name(name: &str) -> u32 {
let mut hash: u32 = 0x811c_9dc5;
for byte in name.as_bytes() {
hash ^= *byte as u32;
hash = hash.wrapping_mul(0x0100_0193);
}
hash
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_root_shape_exists() {
let table = ShapeTransitionTable::new();
let root = table.get_shape(ShapeTransitionTable::root());
assert_eq!(root.id, ShapeId(0));
assert!(root.properties.is_empty());
assert_eq!(root.parent, None);
assert_eq!(root.property_count, 0);
}
#[test]
fn test_transition_creates_new_shape() {
let mut table = ShapeTransitionTable::new();
let child = table.transition(ShapeTransitionTable::root(), 42);
assert_ne!(child, ShapeTransitionTable::root());
let shape = table.get_shape(child);
assert_eq!(shape.properties, vec![42]);
assert_eq!(shape.property_count, 1);
assert_eq!(shape.parent, Some(ShapeTransitionTable::root()));
}
#[test]
fn test_transition_deduplication() {
let mut table = ShapeTransitionTable::new();
let child1 = table.transition(ShapeTransitionTable::root(), 42);
let child2 = table.transition(ShapeTransitionTable::root(), 42);
assert_eq!(child1, child2);
assert_eq!(table.shape_count(), 2);
}
#[test]
fn test_shape_for_keys() {
let mut table = ShapeTransitionTable::new();
let shape_id = table.shape_for_keys(&[10, 20, 30]);
let shape = table.get_shape(shape_id);
assert_eq!(shape.properties, vec![10, 20, 30]);
assert_eq!(shape.property_count, 3);
}
#[test]
fn test_property_index() {
let mut table = ShapeTransitionTable::new();
let shape_id = table.shape_for_keys(&[10, 20, 30]);
assert_eq!(table.property_index(shape_id, 10), Some(0));
assert_eq!(table.property_index(shape_id, 20), Some(1));
assert_eq!(table.property_index(shape_id, 30), Some(2));
}
#[test]
fn test_property_index_missing() {
let mut table = ShapeTransitionTable::new();
let shape_id = table.shape_for_keys(&[10, 20]);
assert_eq!(table.property_index(shape_id, 99), None);
}
#[test]
fn test_multiple_transition_paths() {
let mut table = ShapeTransitionTable::new();
let ab = table.shape_for_keys(&[1, 2]);
let ba = table.shape_for_keys(&[2, 1]);
assert_ne!(ab, ba);
let shape_ab = table.get_shape(ab);
let shape_ba = table.get_shape(ba);
assert_eq!(shape_ab.properties, vec![1, 2]);
assert_eq!(shape_ba.properties, vec![2, 1]);
}
#[test]
fn test_shape_count() {
let mut table = ShapeTransitionTable::new();
assert_eq!(table.shape_count(), 1); table.transition(ShapeTransitionTable::root(), 1);
assert_eq!(table.shape_count(), 2);
table.transition(ShapeTransitionTable::root(), 2);
assert_eq!(table.shape_count(), 3);
table.transition(ShapeTransitionTable::root(), 1);
assert_eq!(table.shape_count(), 3);
}
#[test]
fn test_parent_chain() {
let mut table = ShapeTransitionTable::new();
let a = table.transition(ShapeTransitionTable::root(), 10);
let ab = table.transition(a, 20);
let abc = table.transition(ab, 30);
let shape_abc = table.get_shape(abc);
assert_eq!(shape_abc.parent, Some(ab));
let shape_ab = table.get_shape(ab);
assert_eq!(shape_ab.parent, Some(a));
let shape_a = table.get_shape(a);
assert_eq!(shape_a.parent, Some(ShapeTransitionTable::root()));
}
#[test]
fn test_shape_id_display() {
assert_eq!(format!("{}", ShapeId(0)), "Shape(0)");
assert_eq!(format!("{}", ShapeId(42)), "Shape(42)");
}
#[test]
fn test_hash_property_name_consistency() {
let h1 = hash_property_name("foo");
let h2 = hash_property_name("foo");
assert_eq!(h1, h2);
assert_ne!(hash_property_name("foo"), hash_property_name("bar"));
}
#[test]
fn test_global_shape_for_keys() {
let keys = &[hash_property_name("x"), hash_property_name("y")];
let id1 = shape_for_hashmap_keys(keys).unwrap();
let id2 = shape_for_hashmap_keys(keys).unwrap();
assert_eq!(id1, id2); }
#[test]
fn test_global_shape_transition() {
let root = ShapeTransitionTable::root();
let prop = hash_property_name("test_prop");
let child = shape_transition(root, prop).unwrap();
assert_ne!(child, root);
}
#[test]
fn test_dictionary_mode_threshold() {
let keys: Vec<u32> = (0..65).collect();
assert!(shape_for_hashmap_keys(&keys).is_none());
}
}