1use std::collections::HashMap;
12use std::fmt;
13use std::sync::Mutex;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct ShapeId(pub u32);
18
19impl fmt::Display for ShapeId {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 write!(f, "Shape({})", self.0)
22 }
23}
24
25#[derive(Debug, Clone)]
31pub struct Shape {
32 pub id: ShapeId,
33 pub properties: Vec<u32>,
35 pub parent: Option<ShapeId>,
37 pub property_count: u16,
39}
40
41pub struct ShapeTransitionTable {
46 transitions: HashMap<(ShapeId, u32), ShapeId>,
48 shapes: Vec<Shape>,
50 next_id: u32,
52}
53
54impl ShapeTransitionTable {
55 pub fn new() -> Self {
57 let root = Shape {
58 id: ShapeId(0),
59 properties: Vec::new(),
60 parent: None,
61 property_count: 0,
62 };
63 Self {
64 transitions: HashMap::new(),
65 shapes: vec![root],
66 next_id: 1,
67 }
68 }
69
70 #[inline]
72 pub fn root() -> ShapeId {
73 ShapeId(0)
74 }
75
76 #[inline]
78 pub fn get_shape(&self, id: ShapeId) -> &Shape {
79 &self.shapes[id.0 as usize]
80 }
81
82 pub fn transition(&mut self, from: ShapeId, property_name: u32) -> ShapeId {
87 if let Some(&existing) = self.transitions.get(&(from, property_name)) {
88 return existing;
89 }
90
91 let parent_shape = &self.shapes[from.0 as usize];
92 let mut properties = parent_shape.properties.clone();
93 properties.push(property_name);
94
95 let new_id = ShapeId(self.next_id);
96 self.next_id += 1;
97
98 let new_shape = Shape {
99 id: new_id,
100 properties,
101 parent: Some(from),
102 property_count: self.shapes[from.0 as usize].property_count + 1,
103 };
104
105 self.shapes.push(new_shape);
106 self.transitions.insert((from, property_name), new_id);
107 new_id
108 }
109
110 pub fn shape_for_keys(&mut self, keys: &[u32]) -> ShapeId {
112 let mut current = Self::root();
113 for &key in keys {
114 current = self.transition(current, key);
115 }
116 current
117 }
118
119 pub fn property_index(&self, shape_id: ShapeId, property_name: u32) -> Option<usize> {
124 let shape = &self.shapes[shape_id.0 as usize];
125 shape.properties.iter().position(|&p| p == property_name)
126 }
127
128 #[inline]
130 pub fn shape_count(&self) -> usize {
131 self.shapes.len()
132 }
133}
134
135static GLOBAL_SHAPE_TABLE: std::sync::LazyLock<Mutex<ShapeTransitionTable>> =
138 std::sync::LazyLock::new(|| Mutex::new(ShapeTransitionTable::new()));
139
140static SHAPE_TRANSITION_LOG: std::sync::LazyLock<Mutex<Vec<(ShapeId, ShapeId)>>> =
147 std::sync::LazyLock::new(|| Mutex::new(Vec::new()));
148
149pub fn drain_shape_transitions() -> Vec<(ShapeId, ShapeId)> {
156 SHAPE_TRANSITION_LOG
157 .lock()
158 .map(|mut log| std::mem::take(&mut *log))
159 .unwrap_or_default()
160}
161
162pub fn shape_for_hashmap_keys(key_hashes: &[u32]) -> Option<ShapeId> {
167 if key_hashes.len() > 64 {
168 return None; }
170 let mut table = GLOBAL_SHAPE_TABLE.lock().ok()?;
171 Some(table.shape_for_keys(key_hashes))
172}
173
174pub fn shape_property_index(shape_id: ShapeId, property_hash: u32) -> Option<usize> {
178 let table = GLOBAL_SHAPE_TABLE.lock().ok()?;
179 table.property_index(shape_id, property_hash)
180}
181
182pub fn shape_transition(from: ShapeId, property_hash: u32) -> Option<ShapeId> {
187 let mut table = GLOBAL_SHAPE_TABLE.lock().ok()?;
188 let shape = table.get_shape(from);
189 if shape.property_count >= 64 {
190 return None; }
192 let new_id = table.transition(from, property_hash);
193 if let Ok(mut log) = SHAPE_TRANSITION_LOG.lock() {
195 log.push((from, new_id));
196 }
197 Some(new_id)
198}
199
200#[inline]
204pub fn hash_property_name(name: &str) -> u32 {
205 let mut hash: u32 = 0x811c_9dc5;
206 for byte in name.as_bytes() {
207 hash ^= *byte as u32;
208 hash = hash.wrapping_mul(0x0100_0193);
209 }
210 hash
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216
217 #[test]
218 fn test_root_shape_exists() {
219 let table = ShapeTransitionTable::new();
220 let root = table.get_shape(ShapeTransitionTable::root());
221 assert_eq!(root.id, ShapeId(0));
222 assert!(root.properties.is_empty());
223 assert_eq!(root.parent, None);
224 assert_eq!(root.property_count, 0);
225 }
226
227 #[test]
228 fn test_transition_creates_new_shape() {
229 let mut table = ShapeTransitionTable::new();
230 let child = table.transition(ShapeTransitionTable::root(), 42);
231 assert_ne!(child, ShapeTransitionTable::root());
232
233 let shape = table.get_shape(child);
234 assert_eq!(shape.properties, vec![42]);
235 assert_eq!(shape.property_count, 1);
236 assert_eq!(shape.parent, Some(ShapeTransitionTable::root()));
237 }
238
239 #[test]
240 fn test_transition_deduplication() {
241 let mut table = ShapeTransitionTable::new();
242 let child1 = table.transition(ShapeTransitionTable::root(), 42);
243 let child2 = table.transition(ShapeTransitionTable::root(), 42);
244 assert_eq!(child1, child2);
245 assert_eq!(table.shape_count(), 2);
247 }
248
249 #[test]
250 fn test_shape_for_keys() {
251 let mut table = ShapeTransitionTable::new();
252 let shape_id = table.shape_for_keys(&[10, 20, 30]);
253 let shape = table.get_shape(shape_id);
254 assert_eq!(shape.properties, vec![10, 20, 30]);
255 assert_eq!(shape.property_count, 3);
256 }
257
258 #[test]
259 fn test_property_index() {
260 let mut table = ShapeTransitionTable::new();
261 let shape_id = table.shape_for_keys(&[10, 20, 30]);
262 assert_eq!(table.property_index(shape_id, 10), Some(0));
263 assert_eq!(table.property_index(shape_id, 20), Some(1));
264 assert_eq!(table.property_index(shape_id, 30), Some(2));
265 }
266
267 #[test]
268 fn test_property_index_missing() {
269 let mut table = ShapeTransitionTable::new();
270 let shape_id = table.shape_for_keys(&[10, 20]);
271 assert_eq!(table.property_index(shape_id, 99), None);
272 }
273
274 #[test]
275 fn test_multiple_transition_paths() {
276 let mut table = ShapeTransitionTable::new();
277 let ab = table.shape_for_keys(&[1, 2]);
279 let ba = table.shape_for_keys(&[2, 1]);
281 assert_ne!(ab, ba);
283 let shape_ab = table.get_shape(ab);
284 let shape_ba = table.get_shape(ba);
285 assert_eq!(shape_ab.properties, vec![1, 2]);
286 assert_eq!(shape_ba.properties, vec![2, 1]);
287 }
288
289 #[test]
290 fn test_shape_count() {
291 let mut table = ShapeTransitionTable::new();
292 assert_eq!(table.shape_count(), 1); table.transition(ShapeTransitionTable::root(), 1);
294 assert_eq!(table.shape_count(), 2);
295 table.transition(ShapeTransitionTable::root(), 2);
296 assert_eq!(table.shape_count(), 3);
297 table.transition(ShapeTransitionTable::root(), 1);
299 assert_eq!(table.shape_count(), 3);
300 }
301
302 #[test]
303 fn test_parent_chain() {
304 let mut table = ShapeTransitionTable::new();
305 let a = table.transition(ShapeTransitionTable::root(), 10);
306 let ab = table.transition(a, 20);
307 let abc = table.transition(ab, 30);
308
309 let shape_abc = table.get_shape(abc);
310 assert_eq!(shape_abc.parent, Some(ab));
311
312 let shape_ab = table.get_shape(ab);
313 assert_eq!(shape_ab.parent, Some(a));
314
315 let shape_a = table.get_shape(a);
316 assert_eq!(shape_a.parent, Some(ShapeTransitionTable::root()));
317 }
318
319 #[test]
320 fn test_shape_id_display() {
321 assert_eq!(format!("{}", ShapeId(0)), "Shape(0)");
322 assert_eq!(format!("{}", ShapeId(42)), "Shape(42)");
323 }
324
325 #[test]
326 fn test_hash_property_name_consistency() {
327 let h1 = hash_property_name("foo");
328 let h2 = hash_property_name("foo");
329 assert_eq!(h1, h2);
330 assert_ne!(hash_property_name("foo"), hash_property_name("bar"));
331 }
332
333 #[test]
334 fn test_global_shape_for_keys() {
335 let keys = &[hash_property_name("x"), hash_property_name("y")];
336 let id1 = shape_for_hashmap_keys(keys).unwrap();
337 let id2 = shape_for_hashmap_keys(keys).unwrap();
338 assert_eq!(id1, id2); }
340
341 #[test]
342 fn test_global_shape_transition() {
343 let root = ShapeTransitionTable::root();
344 let prop = hash_property_name("test_prop");
345 let child = shape_transition(root, prop).unwrap();
346 assert_ne!(child, root);
347 }
348
349 #[test]
350 fn test_dictionary_mode_threshold() {
351 let keys: Vec<u32> = (0..65).collect();
353 assert!(shape_for_hashmap_keys(&keys).is_none());
354 }
355}