inkferro_core/dom/arena.rs
1//! Arena: externally-indexed `Vec<Option<Node>>`.
2//!
3//! See module docs (`dom/mod.rs`) for the id-storage rationale.
4
5use super::node::Node;
6
7/// Slab storage for DOM nodes, indexed by externally-supplied `u32` ids.
8///
9/// `Vec<Option<Node>>` is grown on demand; the allocator trusts that the
10/// JS reconciler uses dense sequential ids. An adversarial gap id just
11/// over-allocates — acceptable here.
12#[derive(Debug, Default)]
13pub struct Arena {
14 slots: Vec<Option<Node>>,
15}
16
17impl Arena {
18 pub fn new() -> Self {
19 Self::default()
20 }
21
22 /// Insert `node` at `id`, growing the backing vec if necessary.
23 /// If the slot is already occupied the call is a no-op (React allocates
24 /// each id exactly once; a double-create indicates a protocol error).
25 pub(crate) fn insert(&mut self, id: u32, node: Node) {
26 let idx = id as usize;
27 if idx >= self.slots.len() {
28 self.slots.resize_with(idx + 1, || None);
29 }
30 if self.slots[idx].is_none() {
31 self.slots[idx] = Some(node);
32 }
33 }
34
35 /// Drop the slot for `id`. Does NOT recurse into children — see module
36 /// docs for the Free-cascade rationale.
37 pub(crate) fn free(&mut self, id: u32) {
38 if let Some(slot) = self.slots.get_mut(id as usize) {
39 *slot = None;
40 }
41 }
42
43 pub fn get(&self, id: u32) -> Option<&Node> {
44 self.slots.get(id as usize)?.as_ref()
45 }
46
47 pub(crate) fn get_mut(&mut self, id: u32) -> Option<&mut Node> {
48 self.slots.get_mut(id as usize)?.as_mut()
49 }
50
51 /// Return the number of live (non-None) slots.
52 #[cfg(test)]
53 pub fn live_count(&self) -> usize {
54 self.slots.iter().filter(|s| s.is_some()).count()
55 }
56}