cranpose_core/
split_slot_storage.rs

1//! Split slot storage that separates layout from payload.
2//!
3//! This backend keeps the slot layout (groups, nodes, value references) separate
4//! from the actual payload data stored in a HashMap. This separation allows the
5//! layout to be overwritten with gaps without losing the associated payload,
6//! enabling efficient data reuse across composition passes.
7//!
8//! ## Implementation Details
9//!
10//! - **Layout storage**: Vec of `LayoutSlot` containing structural information
11//!   (groups, nodes) and references to payload via anchor IDs.
12//! - **Payload storage**: `HashMap<anchor_id, Box<dyn Any>>` for actual data,
13//!   persisting across gap cycles.
14//! - **Value slot allocation**: Checks if existing layout slot references valid
15//!   payload with matching type. If type mismatch or missing payload, creates
16//!   new payload at the same anchor ID.
17//! - **Gap handling**: When finalizing, marks unreached layout slots as gaps
18//!   while preserving group metadata (key, scope, len). Payload remains intact.
19//! - **Anchor lifecycle**:
20//!   1. Created with `alloc_anchor()` when allocating value/group/node slots
21//!   2. Marked dirty when layout is modified
22//!   3. Rebuilt during `flush()` by scanning layout and updating anchor map
23//!
24//! ## Trade-offs
25//!
26//! - **Pros**: Payload persists across gaps (better memory reuse), simpler gap
27//!   management (no need to preserve data in gap slots)
28//! - **Cons**: HashMap overhead, potential for orphaned payloads (mitigated by
29//!   debug assertions), indirection cost for payload access
30
31use crate::{
32    slot_storage::{GroupId, SlotStorage, StartGroup, ValueSlotId},
33    AnchorId, Key, NodeId, Owned, ScopeId,
34};
35use std::any::Any;
36use std::cell::Cell;
37use std::collections::HashMap;
38
39/// Split slot storage implementation.
40///
41/// Separates slot layout (structural information) from payload data
42/// (remembered values), allowing layout changes without data loss.
43#[derive(Default)]
44pub struct SplitSlotStorage {
45    /// Layout slots containing structural info and references to payload.
46    layout: Vec<LayoutSlot>,
47    /// Payload storage indexed by anchor ID.
48    payload: HashMap<usize, Box<dyn Any>>,
49    /// Current cursor in the layout.
50    cursor: usize,
51    /// Group stack tracking composition nesting.
52    group_stack: Vec<GroupFrame>,
53    /// Anchor ID → layout position mapping.
54    anchors: Vec<usize>,
55    /// Whether anchors need rebuilding.
56    anchors_dirty: bool,
57    /// Counter for allocating anchor IDs.
58    next_anchor_id: Cell<usize>,
59    /// Tracks whether last begin_group restored from gap.
60    last_start_was_gap: bool,
61}
62
63struct GroupFrame {
64    #[allow(dead_code)] // Tracked for debugging/future inspection tools
65    key: Key,
66    start: usize,
67    end: usize,
68    #[allow(dead_code)] // Tracked for debugging/future recomposition heuristics
69    force_children_recompose: bool,
70}
71
72/// Layout slot containing only structural information.
73enum LayoutSlot {
74    Group {
75        key: Key,
76        anchor: AnchorId,
77        len: usize,
78        scope: Option<ScopeId>,
79        has_gap_children: bool,
80    },
81    /// Reference to a value in the payload map.
82    ValueRef {
83        anchor: AnchorId,
84    },
85    Node {
86        anchor: AnchorId,
87        id: NodeId,
88    },
89    Gap {
90        anchor: AnchorId,
91        group_key: Option<Key>,
92        group_scope: Option<ScopeId>,
93        group_len: usize,
94    },
95}
96
97impl LayoutSlot {
98    fn anchor_id(&self) -> AnchorId {
99        match self {
100            LayoutSlot::Group { anchor, .. } => *anchor,
101            LayoutSlot::ValueRef { anchor } => *anchor,
102            LayoutSlot::Node { anchor, .. } => *anchor,
103            LayoutSlot::Gap { anchor, .. } => *anchor,
104        }
105    }
106}
107
108impl Default for LayoutSlot {
109    fn default() -> Self {
110        LayoutSlot::Gap {
111            anchor: AnchorId::INVALID,
112            group_key: None,
113            group_scope: None,
114            group_len: 0,
115        }
116    }
117}
118
119impl SplitSlotStorage {
120    pub fn new() -> Self {
121        Self {
122            next_anchor_id: Cell::new(1), // Start at 1 (0 is INVALID)
123            ..Default::default()
124        }
125    }
126
127    fn alloc_anchor(&self) -> AnchorId {
128        let id = self.next_anchor_id.get();
129        self.next_anchor_id.set(id + 1);
130        AnchorId::new(id)
131    }
132
133    fn ensure_capacity(&mut self) {
134        const INITIAL_CAP: usize = 32;
135        if self.layout.is_empty() {
136            self.layout.resize_with(INITIAL_CAP, LayoutSlot::default);
137        } else if self.cursor >= self.layout.len() {
138            let new_size = self.layout.len() * 2;
139            self.layout.resize_with(new_size, LayoutSlot::default);
140        }
141    }
142
143    fn insert_at_cursor(&mut self, slot: LayoutSlot) {
144        self.ensure_capacity();
145
146        // Check if we can reuse a gap
147        if matches!(self.layout.get(self.cursor), Some(LayoutSlot::Gap { .. })) {
148            self.layout[self.cursor] = slot;
149        } else {
150            // Need to insert - for simplicity, just overwrite
151            // A full implementation would shift
152            if self.cursor < self.layout.len() {
153                self.layout[self.cursor] = slot;
154            }
155        }
156
157        // Update group end to account for this slot
158        if let Some(frame) = self.group_stack.last_mut() {
159            if self.cursor >= frame.end {
160                frame.end = self.cursor + 1;
161            }
162        }
163        self.anchors_dirty = true;
164    }
165
166    fn start_group(&mut self, key: Key) -> (usize, bool) {
167        self.ensure_capacity();
168
169        // Check for gap group restoration
170        if let Some(LayoutSlot::Gap {
171            group_key: Some(gap_key),
172            group_scope,
173            group_len,
174            anchor: gap_anchor,
175        }) = self.layout.get(self.cursor)
176        {
177            if *gap_key == key {
178                // Reuse the gap's anchor if valid, otherwise allocate new
179                let anchor = if gap_anchor.is_valid() {
180                    *gap_anchor
181                } else {
182                    self.alloc_anchor()
183                };
184                let scope = *group_scope;
185                let len = *group_len;
186                self.layout[self.cursor] = LayoutSlot::Group {
187                    key,
188                    anchor,
189                    len,
190                    scope,
191                    has_gap_children: true,
192                };
193
194                let start = self.cursor;
195                self.cursor += 1;
196                // Set frame.end to start + len + 1 to properly bound the group.
197                // This accounts for the group slot itself (at `start`) plus `len` children.
198                // IMPORTANT: This pairing assumes do_finalize_current_group stores the
199                // child count (not including the group slot) in group_len.
200                self.group_stack.push(GroupFrame {
201                    key,
202                    start,
203                    end: start + len + 1,
204                    force_children_recompose: true,
205                });
206                self.last_start_was_gap = true;
207                self.anchors_dirty = true;
208                return (start, true);
209            }
210        }
211
212        // Create new group
213        let anchor = self.alloc_anchor();
214        let slot = LayoutSlot::Group {
215            key,
216            anchor,
217            len: 0,
218            scope: None,
219            has_gap_children: false,
220        };
221
222        self.insert_at_cursor(slot);
223        let start = self.cursor;
224        self.cursor += 1;
225        self.group_stack.push(GroupFrame {
226            key,
227            start,
228            end: start,
229            force_children_recompose: false,
230        });
231        self.last_start_was_gap = false;
232        (start, false)
233    }
234
235    fn do_end_group(&mut self) {
236        if let Some(frame) = self.group_stack.pop() {
237            let len = self.cursor.saturating_sub(frame.start + 1);
238            if let Some(LayoutSlot::Group { len: slot_len, .. }) = self.layout.get_mut(frame.start)
239            {
240                *slot_len = len;
241            }
242        }
243    }
244
245    fn do_finalize_current_group(&mut self) -> bool {
246        let frame_end = match self.group_stack.last() {
247            Some(frame) => frame.end,
248            None => {
249                // Root-level finalization: mark everything from cursor to end as gaps
250                if self.cursor >= self.layout.len() {
251                    return false;
252                }
253                let mut marked = false;
254                while self.cursor < self.layout.len() {
255                    let slot = &mut self.layout[self.cursor];
256                    let anchor = slot.anchor_id();
257                    let (group_key, group_scope, group_len) = match slot {
258                        LayoutSlot::Group {
259                            key, scope, len, ..
260                        } => (Some(*key), *scope, *len),
261                        _ => (None, None, 0),
262                    };
263                    *slot = LayoutSlot::Gap {
264                        anchor,
265                        group_key,
266                        group_scope,
267                        group_len,
268                    };
269                    marked = true;
270                    self.cursor += 1;
271                }
272                // Mark anchors dirty so flush() rebuilds the anchor map
273                self.anchors_dirty = true;
274                return marked;
275            }
276        };
277
278        let mut marked = false;
279        while self.cursor < frame_end && self.cursor < self.layout.len() {
280            let slot = &mut self.layout[self.cursor];
281            let anchor = slot.anchor_id();
282            let (group_key, group_scope, group_len) = match slot {
283                LayoutSlot::Group {
284                    key, scope, len, ..
285                } => (Some(*key), *scope, *len),
286                _ => (None, None, 0),
287            };
288
289            // Note: We do NOT drop the payload here - it persists!
290            // IMPORTANT: group_len stores the number of children (not including the group slot).
291            // This pairs with start_group's calculation of frame.end = start + len + 1.
292            *slot = LayoutSlot::Gap {
293                anchor,
294                group_key,
295                group_scope,
296                group_len,
297            };
298            marked = true;
299            self.cursor += 1;
300        }
301
302        if let Some(frame) = self.group_stack.last_mut() {
303            frame.end = self.cursor;
304        }
305        marked
306    }
307}
308
309impl SlotStorage for SplitSlotStorage {
310    type Group = GroupId;
311    type ValueSlot = ValueSlotId;
312
313    fn begin_group(&mut self, key: Key) -> StartGroup<Self::Group> {
314        let (idx, restored) = self.start_group(key);
315        StartGroup {
316            group: GroupId::new(idx),
317            restored_from_gap: restored,
318        }
319    }
320
321    fn set_group_scope(&mut self, group: Self::Group, scope: ScopeId) {
322        if let Some(LayoutSlot::Group {
323            scope: slot_scope, ..
324        }) = self.layout.get_mut(group.index())
325        {
326            *slot_scope = Some(scope);
327        }
328    }
329
330    fn end_group(&mut self) {
331        self.do_end_group();
332    }
333
334    fn skip_current_group(&mut self) {
335        if let Some(LayoutSlot::Group { len, .. }) = self.layout.get(self.cursor) {
336            self.cursor += 1 + len;
337        }
338    }
339
340    fn nodes_in_current_group(&self) -> Vec<NodeId> {
341        let mut nodes = Vec::new();
342        if let Some(frame) = self.group_stack.last() {
343            for pos in (frame.start + 1)..frame.end {
344                if let Some(LayoutSlot::Node { id, .. }) = self.layout.get(pos) {
345                    nodes.push(*id);
346                }
347            }
348        }
349        nodes
350    }
351
352    fn begin_recranpose_at_scope(&mut self, scope: ScopeId) -> Option<Self::Group> {
353        for (idx, slot) in self.layout.iter().enumerate() {
354            if let LayoutSlot::Group { scope: Some(s), .. } = slot {
355                if *s == scope {
356                    self.cursor = idx;
357                    return Some(GroupId::new(idx));
358                }
359            }
360        }
361        None
362    }
363
364    fn end_recompose(&mut self) {
365        // No-op
366    }
367
368    fn alloc_value_slot<T: 'static>(&mut self, init: impl FnOnce() -> T) -> Self::ValueSlot {
369        self.ensure_capacity();
370
371        // Check if current slot is a value ref we can reuse
372        if let Some(LayoutSlot::ValueRef { anchor }) = self.layout.get(self.cursor) {
373            let anchor_id = anchor.0;
374            // Check if payload exists and has correct type
375            if let Some(data) = self.payload.get(&anchor_id) {
376                if data.is::<T>() {
377                    // Reuse existing slot with matching type
378                    let slot_id = ValueSlotId::new(self.cursor);
379                    self.cursor += 1;
380                    return slot_id;
381                } else {
382                    // Type mismatch: overwrite payload with new value
383                    self.payload.insert(anchor_id, Box::new(init()));
384                    let slot_id = ValueSlotId::new(self.cursor);
385                    self.cursor += 1;
386                    return slot_id;
387                }
388            } else {
389                // Layout points to missing payload: create new payload
390                self.payload.insert(anchor_id, Box::new(init()));
391                let slot_id = ValueSlotId::new(self.cursor);
392                self.cursor += 1;
393                return slot_id;
394            }
395        }
396
397        // Check if it's a gap we can reuse
398        if matches!(self.layout.get(self.cursor), Some(LayoutSlot::Gap { .. })) {
399            // Create new value slot in the gap
400            let anchor = self.alloc_anchor();
401            let anchor_id = anchor.0;
402
403            // Store payload
404            self.payload.insert(anchor_id, Box::new(init()));
405
406            // Store layout ref
407            self.layout[self.cursor] = LayoutSlot::ValueRef { anchor };
408
409            let slot_id = ValueSlotId::new(self.cursor);
410            self.cursor += 1;
411
412            if let Some(frame) = self.group_stack.last_mut() {
413                if self.cursor > frame.end {
414                    frame.end = self.cursor;
415                }
416            }
417            self.anchors_dirty = true;
418            return slot_id;
419        }
420
421        // Create new value slot
422        let anchor = self.alloc_anchor();
423        let anchor_id = anchor.0;
424
425        // Store payload
426        self.payload.insert(anchor_id, Box::new(init()));
427
428        // Store layout ref
429        let slot = LayoutSlot::ValueRef { anchor };
430        self.insert_at_cursor(slot);
431
432        let slot_id = ValueSlotId::new(self.cursor);
433        self.cursor += 1;
434        slot_id
435    }
436
437    fn read_value<T: 'static>(&self, slot: Self::ValueSlot) -> &T {
438        let layout_slot = self
439            .layout
440            .get(slot.index())
441            .expect("layout slot not found");
442        let anchor = layout_slot.anchor_id();
443        let data = self.payload.get(&anchor.0).expect("payload not found");
444        data.downcast_ref::<T>().expect("type mismatch")
445    }
446
447    fn read_value_mut<T: 'static>(&mut self, slot: Self::ValueSlot) -> &mut T {
448        let layout_slot = self
449            .layout
450            .get(slot.index())
451            .expect("layout slot not found");
452        let anchor = layout_slot.anchor_id();
453        let data = self.payload.get_mut(&anchor.0).expect("payload not found");
454        data.downcast_mut::<T>().expect("type mismatch")
455    }
456
457    fn write_value<T: 'static>(&mut self, slot: Self::ValueSlot, value: T) {
458        let layout_slot = self
459            .layout
460            .get(slot.index())
461            .expect("layout slot not found");
462        let anchor = layout_slot.anchor_id();
463        self.payload.insert(anchor.0, Box::new(value));
464    }
465
466    fn remember<T: 'static>(&mut self, init: impl FnOnce() -> T) -> Owned<T> {
467        let slot = self.alloc_value_slot(|| Owned::new(init()));
468        self.read_value::<Owned<T>>(slot).clone()
469    }
470
471    fn peek_node(&self) -> Option<NodeId> {
472        if let Some(LayoutSlot::Node { id, .. }) = self.layout.get(self.cursor) {
473            Some(*id)
474        } else {
475            None
476        }
477    }
478
479    fn record_node(&mut self, id: NodeId) {
480        self.ensure_capacity();
481        let anchor = self.alloc_anchor();
482        let slot = LayoutSlot::Node { anchor, id };
483        self.insert_at_cursor(slot);
484        self.cursor += 1;
485    }
486
487    fn advance_after_node_read(&mut self) {
488        self.cursor += 1;
489    }
490
491    fn step_back(&mut self) {
492        self.cursor = self.cursor.saturating_sub(1);
493    }
494
495    fn finalize_current_group(&mut self) -> bool {
496        self.do_finalize_current_group()
497    }
498
499    fn reset(&mut self) {
500        self.cursor = 0;
501        self.group_stack.clear();
502    }
503
504    fn flush(&mut self) {
505        // Rebuild anchors if needed
506        if self.anchors_dirty {
507            for pos in self.anchors.iter_mut() {
508                *pos = usize::MAX;
509            }
510
511            for (idx, slot) in self.layout.iter().enumerate() {
512                let anchor = slot.anchor_id();
513                if anchor.is_valid() {
514                    let id = anchor.0;
515                    if id >= self.anchors.len() {
516                        self.anchors.resize(id + 1, usize::MAX);
517                    }
518                    self.anchors[id] = idx;
519                }
520            }
521
522            self.anchors_dirty = false;
523        }
524    }
525}
526
527impl SplitSlotStorage {
528    /// Debug method to dump all groups.
529    pub fn debug_dump_groups(&self) -> Vec<(usize, Key, Option<ScopeId>, usize)> {
530        self.layout
531            .iter()
532            .enumerate()
533            .filter_map(|(i, slot)| match slot {
534                LayoutSlot::Group {
535                    key, len, scope, ..
536                } => Some((i, *key, *scope, *len)),
537                _ => None,
538            })
539            .collect()
540    }
541
542    /// Debug method to dump all slots.
543    pub fn debug_dump_all_slots(&self) -> Vec<(usize, String)> {
544        self.layout
545            .iter()
546            .enumerate()
547            .map(|(i, slot)| {
548                let desc = match slot {
549                    LayoutSlot::Group {
550                        key,
551                        scope,
552                        len,
553                        has_gap_children,
554                        ..
555                    } => {
556                        format!(
557                            "Group(key={}, scope={:?}, len={}, gaps={})",
558                            key, scope, len, has_gap_children
559                        )
560                    }
561                    LayoutSlot::ValueRef { .. } => "ValueRef".to_string(),
562                    LayoutSlot::Node { id, .. } => format!("Node(id={})", id),
563                    LayoutSlot::Gap {
564                        group_key,
565                        group_scope,
566                        group_len,
567                        ..
568                    } => {
569                        format!(
570                            "Gap(key={:?}, scope={:?}, len={})",
571                            group_key, group_scope, group_len
572                        )
573                    }
574                };
575                (i, desc)
576            })
577            .collect()
578    }
579}