Skip to main content

azul_core/
id.rs

1//! Node tree data structures and hierarchy management.
2//!
3//! This module provides the core data structures for managing DOM-like tree hierarchies:
4//!
5//! - `NodeId`: Type-safe node identifiers with Option<NodeId> optimization
6//! - `NodeHierarchy`: Parent-child relationships between nodes
7//! - `NodeDataContainer`: Generic storage for node data with efficient indexing
8//!
9//! # Memory Layout
10//!
11//! `NodeId` stores a plain `usize` index internally. For FFI structs that need
12//! `Option<NodeId>`, a manual 1-based encoding is used (0 = None, n > 0 = Some(n-1)).
13//!
14//! # Performance
15//!
16//! - Node lookups are O(1) via direct array indexing
17//! - Parent/child traversal is O(1) via pre-computed indices
18//! - No heap allocations after initial tree construction
19
20use alloc::vec::Vec;
21use core::{
22    ops::{Index, IndexMut},
23    slice::Iter,
24};
25
26pub use self::node_id::NodeId;
27use crate::styled_dom::NodeHierarchyItem;
28
29/// Type alias for depth-first traversal results: (depth, `node_id`) pairs
30pub type NodeDepths = Vec<(usize, NodeId)>;
31
32// Simple FFI-safe NodeId - just a wrapper around usize
33pub mod node_id {
34
35    use alloc::vec::Vec;
36    use core::{
37        fmt,
38        ops::{Add, AddAssign},
39    };
40
41    /// A type-safe identifier for a node within a DOM tree.
42    ///
43    /// `NodeId` is FFI-safe (`#[repr(C)]`) and stores a **zero-based** index internally.
44    /// Use `NodeId::index()` to get the array index for direct node access.
45    ///
46    /// # Zero-based indexing
47    ///
48    /// - `NodeId::new(0)` → first node (index 0)
49    /// - `NodeId::new(5)` → sixth node (index 5)
50    /// - Use `node_id.index()` to get the array index
51    ///
52    /// # FFI Encoding (for `Option<NodeId>`)
53    ///
54    /// When storing `Option<NodeId>` in FFI structs (like `NodeHierarchyItem`),
55    /// we use a **1-based encoding** to represent None:
56    ///
57    /// - `0` means `None` (no node)
58    /// - `n > 0` means `Some(NodeId(n - 1))`
59    ///
60    /// Use [`NodeId::from_usize`] to decode and [`NodeId::into_raw`] to encode.
61    /// See also: [`crate::styled_dom::NodeHierarchyItemId`] for the FFI wrapper type.
62    ///
63    /// # Warning
64    ///
65    /// **Never manually construct raw usize values for node hierarchy fields!**
66    /// Always use the provided `from_usize`/`into_raw` functions to avoid
67    /// off-by-one errors that can cause index-out-of-bounds panics.
68    ///
69    #[repr(C)]
70    #[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
71    pub struct NodeId {
72        // Private field to prevent direct manipulation.
73        // Use NodeId::new() to create, NodeId::index() to read.
74        inner: usize,
75    }
76
77    impl NodeId {
78        /// The zero/first node ID (index 0).
79        pub const ZERO: Self = Self { inner: 0 };
80
81        /// Creates a new `NodeId` from a zero-based index.
82        #[inline]
83        #[must_use]
84        pub const fn new(value: usize) -> Self {
85            Self { inner: value }
86        }
87
88        /// Decodes a raw `usize` to `Option<NodeId>` using 1-based encoding.
89        ///
90        /// This is the inverse of [`NodeId::into_usize`].
91        ///
92        /// - `0` → `None` (no node)
93        /// - `n > 0` → `Some(NodeId(n - 1))`
94        ///
95        /// # Warning
96        ///
97        /// This function is for decoding values stored in FFI structs like
98        /// `NodeHierarchyItem`. Do not use raw usize values directly - always
99        /// decode them first!
100        #[inline]
101        #[must_use]
102        pub const fn from_usize(value: usize) -> Option<Self> {
103            match value {
104                0 => None,
105                i => Some(Self { inner: i - 1 }),
106            }
107        }
108
109        /// Encodes `Option<NodeId>` to a raw `usize` for storage in FFI structs.
110        ///
111        /// - `None` → `0`
112        /// - `Some(NodeId(n))` → `n + 1`
113        ///
114        /// The returned value uses **1-based encoding**! A value of `0` means "no node",
115        /// NOT "node at index 0". Use [`NodeId::from_usize`] to decode.
116        ///
117        #[inline]
118        #[must_use]
119        pub const fn into_raw(val: &Option<Self>) -> usize {
120            match val {
121                None => 0,
122                Some(s) => s.inner + 1,
123            }
124        }
125
126        /// Returns the **zero-based** index of this node.
127        ///
128        /// This is the actual array index where the node data is stored.
129        #[inline]
130        #[must_use]
131        pub const fn index(&self) -> usize {
132            self.inner
133        }
134    }
135
136    impl From<usize> for NodeId {
137        fn from(val: usize) -> Self {
138            Self::new(val)
139        }
140    }
141
142    impl From<NodeId> for usize {
143        fn from(val: NodeId) -> Self {
144            val.inner
145        }
146    }
147
148    impl Add<usize> for NodeId {
149        type Output = Self;
150        /// AUDIT: saturating add. A raw `self.inner + other` could overflow
151        /// (debug panic / release wrap to a bogus small index that then aliases
152        /// a real node). `NodeId` indices are bounded by the arena length, so a
153        /// saturation to `usize::MAX` is an obviously-invalid index that fails
154        /// loudly at the next bounds-checked access rather than silently aliasing.
155        #[inline]
156        fn add(self, other: usize) -> Self {
157            Self::new(self.inner.saturating_add(other))
158        }
159    }
160
161    impl AddAssign<usize> for NodeId {
162        /// AUDIT: saturating add — see [`Add`] impl above.
163        #[inline]
164        fn add_assign(&mut self, other: usize) {
165            *self = *self + other;
166        }
167    }
168
169    impl fmt::Display for NodeId {
170        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171            write!(f, "{}", self.inner)
172        }
173    }
174
175    impl fmt::Debug for NodeId {
176        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177            write!(f, "NodeId({})", self.inner)
178        }
179    }
180}
181
182/// Hierarchical information about a node (stores the indices of the parent / child nodes).
183#[derive(Debug, Default, Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Hash)]
184pub struct Node {
185    pub parent: Option<NodeId>,
186    pub previous_sibling: Option<NodeId>,
187    pub next_sibling: Option<NodeId>,
188    pub last_child: Option<NodeId>,
189    // NOTE: first_child can be calculated on the fly:
190    //
191    //   - if last_child is None, first_child is None
192    //   - if last_child is Some, first_child is parent_index + 1
193    //
194    // This makes the "Node" struct take up 4 registers instead of 5
195    //
196    // pub first_child: Option<NodeId>,
197}
198
199impl Node {
200    pub const ROOT: Self = Self {
201        parent: None,
202        previous_sibling: None,
203        next_sibling: None,
204        last_child: None,
205    };
206
207    #[inline]
208    #[must_use]
209    pub const fn has_parent(&self) -> bool {
210        self.parent.is_some()
211    }
212    #[inline]
213    #[must_use]
214    pub const fn has_previous_sibling(&self) -> bool {
215        self.previous_sibling.is_some()
216    }
217    #[inline]
218    #[must_use]
219    pub const fn has_next_sibling(&self) -> bool {
220        self.next_sibling.is_some()
221    }
222    #[inline]
223    #[must_use]
224    pub const fn has_first_child(&self) -> bool {
225        self.last_child.is_some() /* last_child and first_child are always set together */
226    }
227    #[inline]
228    #[must_use]
229    pub const fn has_last_child(&self) -> bool {
230        self.last_child.is_some()
231    }
232
233    #[inline]
234    #[must_use]
235    pub fn get_first_child(&self, current_node_id: NodeId) -> Option<NodeId> {
236        // last_child and first_child are always set together
237        self.last_child.map(|_| current_node_id + 1)
238    }
239}
240
241/// The hierarchy of nodes is stored separately from the actual node content in order
242/// to save on memory, since the hierarchy can be re-used across several DOM trees even
243/// if the content changes.
244#[derive(Debug, Default, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
245pub struct NodeHierarchy {
246    pub internal: Vec<Node>,
247}
248
249impl NodeHierarchy {
250    #[inline]
251    #[must_use]
252    pub const fn new(data: Vec<Node>) -> Self {
253        Self { internal: data }
254    }
255
256    #[inline]
257    #[must_use]
258    pub fn as_ref(&self) -> NodeHierarchyRef<'_> {
259        NodeHierarchyRef {
260            internal: &self.internal[..],
261        }
262    }
263}
264
265/// The hierarchy of nodes is stored separately from the actual node content in order
266/// to save on memory, since the hierarchy can be re-used across several DOM trees even
267/// if the content changes.
268#[derive(Debug, PartialEq, Hash, Eq)]
269pub struct NodeHierarchyRef<'a> {
270    pub internal: &'a [Node],
271}
272
273impl<'a> NodeHierarchyRef<'a> {
274    #[inline]
275    #[must_use]
276    pub const fn from_slice(data: &'a [Node]) -> Self {
277        NodeHierarchyRef { internal: data }
278    }
279
280    #[inline]
281    #[must_use]
282    pub const fn len(&self) -> usize {
283        self.internal.len()
284    }
285
286    #[inline]
287    #[must_use]
288    pub const fn is_empty(&self) -> bool {
289        self.internal.is_empty()
290    }
291
292    #[inline]
293    #[must_use]
294    pub fn get(&self, id: NodeId) -> Option<&Node> {
295        self.internal.get(id.index())
296    }
297
298    #[inline]
299    #[must_use]
300    pub const fn linear_iter(&self) -> LinearIterator {
301        LinearIterator {
302            arena_len: self.len(),
303            position: 0,
304        }
305    }
306
307    /// Returns the `(depth, NodeId)` of all parent nodes (i.e. nodes that have a
308    /// `first_child`), in depth sorted order, (i.e. `NodeId(0)` with a depth of 0) is
309    /// the first element.
310    ///
311    /// Runtime: O(n) max
312    // the `.drain(..)` calls intentionally empty current/next_children to REUSE
313    // their allocations across the BFS levels; `into_iter()` would move them.
314    #[allow(clippy::iter_with_drain)]
315    #[must_use]
316    pub fn get_parents_sorted_by_depth(&self) -> NodeDepths {
317        // AUDIT: an empty hierarchy has no root node — indexing `internal[0]`
318        // (via `self[root]` below) would panic. Bail out early.
319        if self.is_empty() {
320            return Vec::new();
321        }
322
323        let root = NodeId::new(0);
324        let mut non_leaf_nodes = Vec::new();
325
326        // AUDIT: a childless root (e.g. a single-node DOM) is a LEAF, not a
327        // parent. The old code seeded `current_children` with the root and
328        // unconditionally pushed it into `non_leaf_nodes`, mislabeling it as a
329        // parent. Only descend (and only emit the root) when it actually has a
330        // first child.
331        if !self[root].has_first_child() {
332            return non_leaf_nodes;
333        }
334
335        let mut current_children = vec![(0, root)];
336        let mut next_children = Vec::new();
337        let mut depth = 1_usize;
338
339        loop {
340            for id in &current_children {
341                for child_id in id.1.children(self).filter(|id| self[*id].has_first_child()) {
342                    next_children.push((depth, child_id));
343                }
344            }
345
346            non_leaf_nodes.extend(&mut current_children.drain(..));
347
348            if next_children.is_empty() {
349                break;
350            }
351            current_children.extend(&mut next_children.drain(..));
352            depth += 1;
353        }
354
355        non_leaf_nodes
356    }
357}
358
359#[derive(Debug, Clone, PartialEq, Hash, Eq, PartialOrd, Ord)]
360pub struct NodeDataContainer<T> {
361    pub internal: Vec<T>,
362}
363
364impl<T> From<Vec<T>> for NodeDataContainer<T> {
365    fn from(v: Vec<T>) -> Self {
366        Self { internal: v }
367    }
368}
369
370#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
371pub struct NodeDataContainerRef<'a, T> {
372    pub internal: &'a [T],
373}
374
375#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
376pub struct NodeDataContainerRefMut<'a, T> {
377    pub internal: &'a mut [T],
378}
379
380impl<T> Default for NodeDataContainer<T> {
381    fn default() -> Self {
382        Self {
383            internal: Vec::new(),
384        }
385    }
386}
387
388impl Index<NodeId> for NodeHierarchyRef<'_> {
389    type Output = Node;
390
391    #[inline]
392    fn index(&self, node_id: NodeId) -> &Node {
393        &self.internal[node_id.index()]
394    }
395}
396
397impl<T> NodeDataContainer<T> {
398    #[inline]
399    #[must_use]
400    pub const fn new(data: Vec<T>) -> Self {
401        Self { internal: data }
402    }
403
404    #[inline]
405    #[must_use]
406    pub const fn is_empty(&self) -> bool {
407        self.internal.is_empty()
408    }
409
410    #[inline]
411    #[must_use]
412    pub fn as_ref(&self) -> NodeDataContainerRef<'_, T> {
413        NodeDataContainerRef {
414            internal: &self.internal[..],
415        }
416    }
417
418    #[inline]
419    pub fn as_ref_mut(&mut self) -> NodeDataContainerRefMut<'_, T> {
420        NodeDataContainerRefMut {
421            internal: &mut self.internal[..],
422        }
423    }
424
425    #[inline]
426    #[must_use]
427    pub const fn len(&self) -> usize {
428        self.internal.len()
429    }
430}
431
432impl<'a, T: 'a> NodeDataContainerRefMut<'a, T> {
433    #[inline]
434    pub const fn from_slice(data: &'a mut [T]) -> Self {
435        NodeDataContainerRefMut { internal: data }
436    }
437}
438
439impl<'a, T: 'a> NodeDataContainerRefMut<'a, T> {
440    #[inline]
441    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
442        self.internal.get_mut(id.index())
443    }
444}
445
446impl<'a, T: Send + 'a> NodeDataContainerRef<'a, T> {
447    pub fn transform_nodeid_optional<U: Send, F>(&self, closure: F) -> NodeDataContainer<U>
448    where
449        F: Send + Sync + Fn(NodeId) -> Option<U>,
450    {
451        let len = self.len();
452        NodeDataContainer {
453            internal: (0..len)
454                .filter_map(|node_id| closure(NodeId::new(node_id)))
455                .collect::<Vec<U>>(),
456        }
457    }
458}
459
460impl<'a, T> IntoIterator for &NodeDataContainerRef<'a, T> {
461    type Item = &'a T;
462    type IntoIter = Iter<'a, T>;
463    #[inline]
464    fn into_iter(self) -> Self::IntoIter {
465        self.internal.iter()
466    }
467}
468
469impl<'a, T: 'a> NodeDataContainerRef<'a, T> {
470    #[inline]
471    pub const fn from_slice(data: &'a [T]) -> Self {
472        NodeDataContainerRef { internal: data }
473    }
474
475    #[inline]
476    #[must_use]
477    pub const fn len(&self) -> usize {
478        self.internal.len()
479    }
480
481    #[inline]
482    #[must_use]
483    pub const fn is_empty(&self) -> bool {
484        self.internal.is_empty()
485    }
486
487    #[inline]
488    #[must_use]
489    pub fn get(&self, id: NodeId) -> Option<&T> {
490        self.internal.get(id.index())
491    }
492
493    #[inline]
494    pub fn iter(&self) -> Iter<'_, T> {
495        self.internal.iter()
496    }
497
498    #[inline]
499    #[must_use]
500    pub const fn linear_iter(&self) -> LinearIterator {
501        LinearIterator {
502            arena_len: self.len(),
503            position: 0,
504        }
505    }
506}
507
508impl<T> Index<NodeId> for NodeDataContainerRef<'_, T> {
509    type Output = T;
510
511    #[inline]
512    fn index(&self, node_id: NodeId) -> &T {
513        &self.internal[node_id.index()]
514    }
515}
516
517impl<T> Index<NodeId> for NodeDataContainerRefMut<'_, T> {
518    type Output = T;
519
520    #[inline]
521    fn index(&self, node_id: NodeId) -> &T {
522        &self.internal[node_id.index()]
523    }
524}
525
526impl<T> IndexMut<NodeId> for NodeDataContainerRefMut<'_, T> {
527    #[inline]
528    fn index_mut(&mut self, node_id: NodeId) -> &mut T {
529        &mut self.internal[node_id.index()]
530    }
531}
532
533impl NodeId {
534    /// Return an iterator of references to this node and the siblings before it.
535    ///
536    /// Call `.next().unwrap()` once on the iterator to skip the node itself.
537    #[inline]
538    #[must_use]
539    pub const fn preceding_siblings<'a>(
540        self,
541        node_hierarchy: &'a NodeHierarchyRef<'a>,
542    ) -> PrecedingSiblings<'a> {
543        PrecedingSiblings {
544            node_hierarchy,
545            node: Some(self),
546        }
547    }
548
549    /// Return an iterator of references to this node's children.
550    #[inline]
551    #[must_use]
552    pub fn children<'a>(self, node_hierarchy: &'a NodeHierarchyRef<'a>) -> Children<'a> {
553        Children {
554            node_hierarchy,
555            node: node_hierarchy[self].get_first_child(self),
556        }
557    }
558}
559
560macro_rules! impl_node_iterator {
561    ($name:ident, $next:expr) => {
562        impl Iterator for $name<'_> {
563            type Item = NodeId;
564
565            fn next(&mut self) -> Option<NodeId> {
566                match self.node.take() {
567                    Some(node) => {
568                        self.node = $next(&self.node_hierarchy[node]);
569                        Some(node)
570                    }
571                    None => None,
572                }
573            }
574        }
575    };
576}
577
578/// An linear iterator, does not respect the DOM in any way,
579/// it just iterates over the nodes like a Vec
580#[derive(Debug, Clone)]
581pub struct LinearIterator {
582    arena_len: usize,
583    position: usize,
584}
585
586impl Iterator for LinearIterator {
587    type Item = NodeId;
588
589    fn next(&mut self) -> Option<NodeId> {
590        if self.arena_len < 1 || self.position > (self.arena_len - 1) {
591            None
592        } else {
593            let new_id = Some(NodeId::new(self.position));
594            self.position += 1;
595            new_id
596        }
597    }
598}
599
600/// An iterator of references to the siblings before a given node.
601#[derive(Debug)]
602pub struct PrecedingSiblings<'a> {
603    node_hierarchy: &'a NodeHierarchyRef<'a>,
604    node: Option<NodeId>,
605}
606
607impl_node_iterator!(PrecedingSiblings, |node: &Node| node.previous_sibling);
608
609/// Special iterator for using `NodeDataContainerRef`<AzNode> instead of `NodeHierarchy`
610#[derive(Debug)]
611pub struct AzChildren<'a> {
612    node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
613    node: Option<NodeId>,
614}
615
616impl Iterator for AzChildren<'_> {
617    type Item = NodeId;
618
619    fn next(&mut self) -> Option<NodeId> {
620        match self.node.take() {
621            Some(node) => {
622                self.node = self.node_hierarchy[node].next_sibling_id();
623                Some(node)
624            }
625            None => None,
626        }
627    }
628}
629
630/// Special iterator for using `NodeDataContainerRef`<AzNode> instead of `NodeHierarchy`
631#[derive(Debug)]
632pub struct AzReverseChildren<'a> {
633    node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
634    node: Option<NodeId>,
635}
636
637impl Iterator for AzReverseChildren<'_> {
638    type Item = NodeId;
639
640    fn next(&mut self) -> Option<NodeId> {
641        match self.node.take() {
642            Some(node) => {
643                self.node = self.node_hierarchy[node].previous_sibling_id();
644                Some(node)
645            }
646            None => None,
647        }
648    }
649}
650
651impl NodeId {
652    /// Traverse up through the hierarchy until a node matching the predicate is found.
653    ///
654    /// Necessary to resolve the last positioned (= relative)
655    /// element of an absolute node.
656    pub fn get_nearest_matching_parent<'a, F>(
657        self,
658        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
659        predicate: F,
660    ) -> Option<Self>
661    where
662        F: Fn(Self) -> bool,
663    {
664        // AUDIT: guard against (a) an out-of-bounds `self` and (b) a cycle in a
665        // corrupt hierarchy (a `parent_id` that points back down into a
666        // descendant). Use checked `get` and cap the walk at the node count —
667        // a valid parent chain can never be longer than the number of nodes.
668        let node_count = node_hierarchy.internal.len();
669        let mut current_node = node_hierarchy.internal.get(self.index())?.parent_id()?;
670        for _ in 0..node_count {
671            if predicate(current_node) {
672                return Some(current_node);
673            }
674            current_node = node_hierarchy
675                .internal
676                .get(current_node.index())?
677                .parent_id()?;
678        }
679        None
680    }
681
682    /// Return the children of this node (necessary for parallel iteration over children)
683    #[inline]
684    #[must_use]
685    pub fn az_children_collect<'a>(
686        self,
687        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
688    ) -> Vec<Self> {
689        self.az_children(node_hierarchy).collect()
690    }
691
692    /// Return an iterator of references to this node's children.
693    #[inline]
694    #[must_use]
695    pub fn az_children<'a>(
696        self,
697        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
698    ) -> AzChildren<'a> {
699        AzChildren {
700            node_hierarchy,
701            node: node_hierarchy[self].first_child_id(self),
702        }
703    }
704
705    /// Return an iterator of references to this node's children.
706    #[inline]
707    #[must_use]
708    pub fn az_reverse_children<'a>(
709        self,
710        node_hierarchy: &'a NodeDataContainerRef<'a, NodeHierarchyItem>,
711    ) -> AzReverseChildren<'a> {
712        AzReverseChildren {
713            node_hierarchy,
714            node: node_hierarchy[self].last_child_id(),
715        }
716    }
717}
718
719/// An iterator of references to the children of a given node.
720#[derive(Debug)]
721pub struct Children<'a> {
722    node_hierarchy: &'a NodeHierarchyRef<'a>,
723    node: Option<NodeId>,
724}
725
726impl_node_iterator!(Children, |node: &Node| node.next_sibling);
727
728#[cfg(test)]
729#[path = "id_test.rs"]
730mod id_test;
731
732// A node reference that may be absent, C-representable (9g-ii-f-i): what a
733// page break carries as its `causing_node` across the FFI boundary.
734azul_css::impl_option!(
735    NodeId,
736    OptionNodeId,
737    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
738);