use std::collections::HashMap;
use std::fmt;
#[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]
}
#[inline]
pub fn try_get_shape(&self, id: ShapeId) -> Option<&Shape> {
self.shapes.get(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.get(shape_id.0 as usize)?;
shape.properties.iter().position(|&p| p == property_name)
}
#[inline]
pub fn shape_count(&self) -> usize {
self.shapes.len()
}
}
pub fn drain_shape_transitions() -> Vec<(ShapeId, ShapeId)> {
let Some(handle) = crate::shape_graph_current::try_current_shape_table() else {
return Vec::new();
};
handle
.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 handle = crate::shape_graph_current::try_current_shape_table()?;
let mut table = handle.table().lock().ok()?;
Some(table.shape_for_keys(key_hashes))
}
pub fn shape_property_index(shape_id: ShapeId, property_hash: u32) -> Option<usize> {
let handle = crate::shape_graph_current::try_current_shape_table()?;
let table = handle.table().lock().ok()?;
table.property_index(shape_id, property_hash)
}
pub fn shape_transition(from: ShapeId, property_hash: u32) -> Option<ShapeId> {
let handle = crate::shape_graph_current::try_current_shape_table()?;
let mut table = handle.table().lock().ok()?;
let shape = table.try_get_shape(from)?;
if shape.property_count >= 64 {
return None; }
let new_id = table.transition(from, property_hash);
drop(table);
if let Ok(mut log) = handle.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 handle = crate::shape_graph_current::ShapeTableHandle::new();
let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
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 handle = crate::shape_graph_current::ShapeTableHandle::new();
let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
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 handle = crate::shape_graph_current::ShapeTableHandle::new();
let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
let keys: Vec<u32> = (0..65).collect();
assert!(shape_for_hashmap_keys(&keys).is_none());
}
#[test]
fn test_free_funcs_degrade_without_scope() {
assert!(shape_for_hashmap_keys(&[1, 2]).is_some());
let root = ShapeTransitionTable::root();
assert!(shape_transition(root, 42).is_some());
assert_eq!(shape_property_index(root, u32::MAX), None);
let _ = drain_shape_transitions();
}
#[test]
fn cross_table_stale_shape_id_degrades_gracefully() {
let table = ShapeTransitionTable::new();
let huge = ShapeId(9_999);
assert_eq!(table.property_index(huge, 42), None);
assert!(table.try_get_shape(huge).is_none());
let handle = crate::shape_graph_current::ShapeTableHandle::new();
let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
assert_eq!(shape_property_index(huge, 42), None);
assert_eq!(shape_transition(huge, 42), None);
}
}