1use std::collections::HashMap;
12use std::fmt;
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct ShapeId(pub u32);
17
18impl fmt::Display for ShapeId {
19 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20 write!(f, "Shape({})", self.0)
21 }
22}
23
24#[derive(Debug, Clone)]
30pub struct Shape {
31 pub id: ShapeId,
32 pub properties: Vec<u32>,
34 pub parent: Option<ShapeId>,
36 pub property_count: u16,
38}
39
40pub struct ShapeTransitionTable {
45 transitions: HashMap<(ShapeId, u32), ShapeId>,
47 shapes: Vec<Shape>,
49 next_id: u32,
51}
52
53impl ShapeTransitionTable {
54 pub fn new() -> Self {
56 let root = Shape {
57 id: ShapeId(0),
58 properties: Vec::new(),
59 parent: None,
60 property_count: 0,
61 };
62 Self {
63 transitions: HashMap::new(),
64 shapes: vec![root],
65 next_id: 1,
66 }
67 }
68
69 #[inline]
71 pub fn root() -> ShapeId {
72 ShapeId(0)
73 }
74
75 #[inline]
77 pub fn get_shape(&self, id: ShapeId) -> &Shape {
78 &self.shapes[id.0 as usize]
79 }
80
81 #[inline]
90 pub fn try_get_shape(&self, id: ShapeId) -> Option<&Shape> {
91 self.shapes.get(id.0 as usize)
92 }
93
94 pub fn transition(&mut self, from: ShapeId, property_name: u32) -> ShapeId {
99 if let Some(&existing) = self.transitions.get(&(from, property_name)) {
100 return existing;
101 }
102
103 let parent_shape = &self.shapes[from.0 as usize];
104 let mut properties = parent_shape.properties.clone();
105 properties.push(property_name);
106
107 let new_id = ShapeId(self.next_id);
108 self.next_id += 1;
109
110 let new_shape = Shape {
111 id: new_id,
112 properties,
113 parent: Some(from),
114 property_count: self.shapes[from.0 as usize].property_count + 1,
115 };
116
117 self.shapes.push(new_shape);
118 self.transitions.insert((from, property_name), new_id);
119 new_id
120 }
121
122 pub fn shape_for_keys(&mut self, keys: &[u32]) -> ShapeId {
124 let mut current = Self::root();
125 for &key in keys {
126 current = self.transition(current, key);
127 }
128 current
129 }
130
131 pub fn property_index(&self, shape_id: ShapeId, property_name: u32) -> Option<usize> {
138 let shape = self.shapes.get(shape_id.0 as usize)?;
139 shape.properties.iter().position(|&p| p == property_name)
140 }
141
142 #[inline]
144 pub fn shape_count(&self) -> usize {
145 self.shapes.len()
146 }
147}
148
149pub fn drain_shape_transitions() -> Vec<(ShapeId, ShapeId)> {
177 let Some(handle) = crate::shape_graph_current::try_current_shape_table() else {
178 return Vec::new();
179 };
180 handle
181 .transition_log()
182 .lock()
183 .map(|mut log| std::mem::take(&mut *log))
184 .unwrap_or_default()
185}
186
187pub fn shape_for_hashmap_keys(key_hashes: &[u32]) -> Option<ShapeId> {
193 if key_hashes.len() > 64 {
194 return None; }
196 let handle = crate::shape_graph_current::try_current_shape_table()?;
197 let mut table = handle.table().lock().ok()?;
198 Some(table.shape_for_keys(key_hashes))
199}
200
201pub fn shape_property_index(shape_id: ShapeId, property_hash: u32) -> Option<usize> {
205 let handle = crate::shape_graph_current::try_current_shape_table()?;
206 let table = handle.table().lock().ok()?;
207 table.property_index(shape_id, property_hash)
208}
209
210pub fn shape_transition(from: ShapeId, property_hash: u32) -> Option<ShapeId> {
220 let handle = crate::shape_graph_current::try_current_shape_table()?;
221 let mut table = handle.table().lock().ok()?;
222 let shape = table.try_get_shape(from)?;
223 if shape.property_count >= 64 {
224 return None; }
226 let new_id = table.transition(from, property_hash);
227 drop(table);
228 if let Ok(mut log) = handle.transition_log().lock() {
230 log.push((from, new_id));
231 }
232 Some(new_id)
233}
234
235#[inline]
239pub fn hash_property_name(name: &str) -> u32 {
240 let mut hash: u32 = 0x811c_9dc5;
241 for byte in name.as_bytes() {
242 hash ^= *byte as u32;
243 hash = hash.wrapping_mul(0x0100_0193);
244 }
245 hash
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 #[test]
253 fn test_root_shape_exists() {
254 let table = ShapeTransitionTable::new();
255 let root = table.get_shape(ShapeTransitionTable::root());
256 assert_eq!(root.id, ShapeId(0));
257 assert!(root.properties.is_empty());
258 assert_eq!(root.parent, None);
259 assert_eq!(root.property_count, 0);
260 }
261
262 #[test]
263 fn test_transition_creates_new_shape() {
264 let mut table = ShapeTransitionTable::new();
265 let child = table.transition(ShapeTransitionTable::root(), 42);
266 assert_ne!(child, ShapeTransitionTable::root());
267
268 let shape = table.get_shape(child);
269 assert_eq!(shape.properties, vec![42]);
270 assert_eq!(shape.property_count, 1);
271 assert_eq!(shape.parent, Some(ShapeTransitionTable::root()));
272 }
273
274 #[test]
275 fn test_transition_deduplication() {
276 let mut table = ShapeTransitionTable::new();
277 let child1 = table.transition(ShapeTransitionTable::root(), 42);
278 let child2 = table.transition(ShapeTransitionTable::root(), 42);
279 assert_eq!(child1, child2);
280 assert_eq!(table.shape_count(), 2);
282 }
283
284 #[test]
285 fn test_shape_for_keys() {
286 let mut table = ShapeTransitionTable::new();
287 let shape_id = table.shape_for_keys(&[10, 20, 30]);
288 let shape = table.get_shape(shape_id);
289 assert_eq!(shape.properties, vec![10, 20, 30]);
290 assert_eq!(shape.property_count, 3);
291 }
292
293 #[test]
294 fn test_property_index() {
295 let mut table = ShapeTransitionTable::new();
296 let shape_id = table.shape_for_keys(&[10, 20, 30]);
297 assert_eq!(table.property_index(shape_id, 10), Some(0));
298 assert_eq!(table.property_index(shape_id, 20), Some(1));
299 assert_eq!(table.property_index(shape_id, 30), Some(2));
300 }
301
302 #[test]
303 fn test_property_index_missing() {
304 let mut table = ShapeTransitionTable::new();
305 let shape_id = table.shape_for_keys(&[10, 20]);
306 assert_eq!(table.property_index(shape_id, 99), None);
307 }
308
309 #[test]
310 fn test_multiple_transition_paths() {
311 let mut table = ShapeTransitionTable::new();
312 let ab = table.shape_for_keys(&[1, 2]);
314 let ba = table.shape_for_keys(&[2, 1]);
316 assert_ne!(ab, ba);
318 let shape_ab = table.get_shape(ab);
319 let shape_ba = table.get_shape(ba);
320 assert_eq!(shape_ab.properties, vec![1, 2]);
321 assert_eq!(shape_ba.properties, vec![2, 1]);
322 }
323
324 #[test]
325 fn test_shape_count() {
326 let mut table = ShapeTransitionTable::new();
327 assert_eq!(table.shape_count(), 1); table.transition(ShapeTransitionTable::root(), 1);
329 assert_eq!(table.shape_count(), 2);
330 table.transition(ShapeTransitionTable::root(), 2);
331 assert_eq!(table.shape_count(), 3);
332 table.transition(ShapeTransitionTable::root(), 1);
334 assert_eq!(table.shape_count(), 3);
335 }
336
337 #[test]
338 fn test_parent_chain() {
339 let mut table = ShapeTransitionTable::new();
340 let a = table.transition(ShapeTransitionTable::root(), 10);
341 let ab = table.transition(a, 20);
342 let abc = table.transition(ab, 30);
343
344 let shape_abc = table.get_shape(abc);
345 assert_eq!(shape_abc.parent, Some(ab));
346
347 let shape_ab = table.get_shape(ab);
348 assert_eq!(shape_ab.parent, Some(a));
349
350 let shape_a = table.get_shape(a);
351 assert_eq!(shape_a.parent, Some(ShapeTransitionTable::root()));
352 }
353
354 #[test]
355 fn test_shape_id_display() {
356 assert_eq!(format!("{}", ShapeId(0)), "Shape(0)");
357 assert_eq!(format!("{}", ShapeId(42)), "Shape(42)");
358 }
359
360 #[test]
361 fn test_hash_property_name_consistency() {
362 let h1 = hash_property_name("foo");
363 let h2 = hash_property_name("foo");
364 assert_eq!(h1, h2);
365 assert_ne!(hash_property_name("foo"), hash_property_name("bar"));
366 }
367
368 #[test]
369 fn test_global_shape_for_keys() {
370 let handle = crate::shape_graph_current::ShapeTableHandle::new();
371 let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
372 let keys = &[hash_property_name("x"), hash_property_name("y")];
373 let id1 = shape_for_hashmap_keys(keys).unwrap();
374 let id2 = shape_for_hashmap_keys(keys).unwrap();
375 assert_eq!(id1, id2); }
377
378 #[test]
379 fn test_global_shape_transition() {
380 let handle = crate::shape_graph_current::ShapeTableHandle::new();
381 let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
382 let root = ShapeTransitionTable::root();
383 let prop = hash_property_name("test_prop");
384 let child = shape_transition(root, prop).unwrap();
385 assert_ne!(child, root);
386 }
387
388 #[test]
389 fn test_dictionary_mode_threshold() {
390 let handle = crate::shape_graph_current::ShapeTableHandle::new();
391 let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
392 let keys: Vec<u32> = (0..65).collect();
394 assert!(shape_for_hashmap_keys(&keys).is_none());
395 }
396
397 #[test]
398 fn test_free_funcs_degrade_without_scope() {
399 assert!(shape_for_hashmap_keys(&[1, 2]).is_some());
411 let root = ShapeTransitionTable::root();
412 assert!(shape_transition(root, 42).is_some());
413 assert_eq!(shape_property_index(root, u32::MAX), None);
418 let _ = drain_shape_transitions();
421 }
422
423 #[test]
433 fn cross_table_stale_shape_id_degrades_gracefully() {
434 let table = ShapeTransitionTable::new();
435 let huge = ShapeId(9_999);
436 assert_eq!(table.property_index(huge, 42), None);
437 assert!(table.try_get_shape(huge).is_none());
438
439 let handle = crate::shape_graph_current::ShapeTableHandle::new();
441 let _scope = crate::shape_graph_current::SyncShapeTableScope::enter(handle);
442 assert_eq!(shape_property_index(huge, 42), None);
443 assert_eq!(shape_transition(huge, 42), None);
444 }
445}