inkferro-core 0.1.0

Layout, text measurement, ANSI render, and frame-diff engine for inkferro — a Rust-backed, byte-for-byte drop-in for the ink terminal UI library.
Documentation
//! Arena: externally-indexed `Vec<Option<Node>>`.
//!
//! See module docs (`dom/mod.rs`) for the id-storage rationale.

use super::node::Node;

/// Slab storage for DOM nodes, indexed by externally-supplied `u32` ids.
///
/// `Vec<Option<Node>>` is grown on demand; the allocator trusts that the
/// JS reconciler uses dense sequential ids.  An adversarial gap id just
/// over-allocates — acceptable here.
#[derive(Debug, Default)]
pub struct Arena {
    slots: Vec<Option<Node>>,
}

impl Arena {
    pub fn new() -> Self {
        Self::default()
    }

    /// Insert `node` at `id`, growing the backing vec if necessary.
    /// If the slot is already occupied the call is a no-op (React allocates
    /// each id exactly once; a double-create indicates a protocol error).
    pub(crate) fn insert(&mut self, id: u32, node: Node) {
        let idx = id as usize;
        if idx >= self.slots.len() {
            self.slots.resize_with(idx + 1, || None);
        }
        if self.slots[idx].is_none() {
            self.slots[idx] = Some(node);
        }
    }

    /// Drop the slot for `id`.  Does NOT recurse into children — see module
    /// docs for the Free-cascade rationale.
    pub(crate) fn free(&mut self, id: u32) {
        if let Some(slot) = self.slots.get_mut(id as usize) {
            *slot = None;
        }
    }

    pub fn get(&self, id: u32) -> Option<&Node> {
        self.slots.get(id as usize)?.as_ref()
    }

    pub(crate) fn get_mut(&mut self, id: u32) -> Option<&mut Node> {
        self.slots.get_mut(id as usize)?.as_mut()
    }

    /// Return the number of live (non-None) slots.
    #[cfg(test)]
    pub fn live_count(&self) -> usize {
        self.slots.iter().filter(|s| s.is_some()).count()
    }
}