Skip to main content

esoc_scene/
arena.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Generational-index arena scene graph.
3
4use crate::node::{Node, NodeContent, NodeId};
5use crate::transform::Affine2D;
6
7/// Slot in the arena: either occupied or free.
8#[allow(clippy::large_enum_variant)]
9enum Slot {
10    Occupied(Node),
11    Free { next_free: Option<u32> },
12}
13
14/// Arena-based scene graph with generational indices.
15pub struct SceneGraph {
16    slots: Vec<Slot>,
17    generation: Vec<u32>,
18    free_head: Option<u32>,
19    root: Option<NodeId>,
20}
21
22impl SceneGraph {
23    /// Create an empty scene graph.
24    pub fn new() -> Self {
25        Self {
26            slots: Vec::new(),
27            generation: Vec::new(),
28            free_head: None,
29            root: None,
30        }
31    }
32
33    /// Create a scene graph with a root container node.
34    pub fn with_root() -> Self {
35        let mut sg = Self::new();
36        let root = sg.insert(Node::container());
37        sg.root = Some(root);
38        sg
39    }
40
41    /// The root node, if any.
42    pub fn root(&self) -> Option<NodeId> {
43        self.root
44    }
45
46    /// Set the root node.
47    pub fn set_root(&mut self, id: NodeId) {
48        self.root = Some(id);
49    }
50
51    /// Insert a node into the arena, returning its ID.
52    pub fn insert(&mut self, node: Node) -> NodeId {
53        if let Some(idx) = self.free_head {
54            let i = idx as usize;
55            if let Slot::Free { next_free } = self.slots[i] {
56                self.free_head = next_free;
57            }
58            self.generation[i] += 1;
59            self.slots[i] = Slot::Occupied(node);
60            NodeId {
61                index: idx,
62                generation: self.generation[i],
63            }
64        } else {
65            let idx = self.slots.len() as u32;
66            self.slots.push(Slot::Occupied(node));
67            self.generation.push(0);
68            NodeId {
69                index: idx,
70                generation: 0,
71            }
72        }
73    }
74
75    /// Remove a node by ID. Returns the node if it existed.
76    pub fn remove(&mut self, id: NodeId) -> Option<Node> {
77        let i = id.index as usize;
78        if i >= self.slots.len() || self.generation[i] != id.generation {
79            return None;
80        }
81        if let Slot::Occupied(_) = &self.slots[i] {
82            let old = std::mem::replace(
83                &mut self.slots[i],
84                Slot::Free {
85                    next_free: self.free_head,
86                },
87            );
88            self.free_head = Some(id.index);
89            match old {
90                Slot::Occupied(node) => Some(node),
91                Slot::Free { .. } => None,
92            }
93        } else {
94            None
95        }
96    }
97
98    /// Get a reference to a node.
99    pub fn get(&self, id: NodeId) -> Option<&Node> {
100        let i = id.index as usize;
101        if i >= self.slots.len() || self.generation[i] != id.generation {
102            return None;
103        }
104        match &self.slots[i] {
105            Slot::Occupied(node) => Some(node),
106            Slot::Free { .. } => None,
107        }
108    }
109
110    /// Get a mutable reference to a node.
111    pub fn get_mut(&mut self, id: NodeId) -> Option<&mut Node> {
112        let i = id.index as usize;
113        if i >= self.slots.len() || self.generation[i] != id.generation {
114            return None;
115        }
116        match &mut self.slots[i] {
117            Slot::Occupied(node) => Some(node),
118            Slot::Free { .. } => None,
119        }
120    }
121
122    /// Add a child to a parent node.
123    pub fn add_child(&mut self, parent: NodeId, child: NodeId) {
124        // Set parent on child
125        if let Some(c) = self.get_mut(child) {
126            c.parent = Some(parent);
127        }
128        // Add to parent's children list
129        if let Some(p) = self.get_mut(parent) {
130            p.children.push(child);
131        }
132    }
133
134    /// Insert a node as a child of `parent`.
135    pub fn insert_child(&mut self, parent: NodeId, node: Node) -> NodeId {
136        let id = self.insert(node);
137        self.add_child(parent, id);
138        id
139    }
140
141    /// Number of live nodes.
142    pub fn len(&self) -> usize {
143        self.slots
144            .iter()
145            .filter(|s| matches!(s, Slot::Occupied(_)))
146            .count()
147    }
148
149    /// Whether the graph is empty.
150    pub fn is_empty(&self) -> bool {
151        self.len() == 0
152    }
153
154    /// Iterate over all live (`NodeId`, `&Node`) pairs.
155    pub fn iter(&self) -> impl Iterator<Item = (NodeId, &Node)> {
156        self.slots
157            .iter()
158            .enumerate()
159            .filter_map(|(i, slot)| match slot {
160                Slot::Occupied(node) => Some((
161                    NodeId {
162                        index: i as u32,
163                        generation: self.generation[i],
164                    },
165                    node,
166                )),
167                Slot::Free { .. } => None,
168            })
169    }
170
171    /// Compute the world transform for a node by walking up the parent chain.
172    pub fn world_transform(&self, id: NodeId) -> Affine2D {
173        let mut chain = Vec::new();
174        let mut current = Some(id);
175        while let Some(cid) = current {
176            if let Some(node) = self.get(cid) {
177                chain.push(node.transform);
178                current = node.parent;
179            } else {
180                break;
181            }
182        }
183        let mut result = Affine2D::IDENTITY;
184        for t in chain.into_iter().rev() {
185            result = result.then(t);
186        }
187        result
188    }
189
190    /// Collect all leaf marks in z-order (depth-first, sorted by `z_order` within siblings).
191    pub fn collect_marks(&self) -> Vec<(NodeId, Affine2D)> {
192        let mut result = Vec::new();
193        if let Some(root) = self.root {
194            self.collect_marks_recursive(root, Affine2D::IDENTITY, &mut result);
195        }
196        result
197    }
198
199    fn collect_marks_recursive(
200        &self,
201        id: NodeId,
202        parent_transform: Affine2D,
203        result: &mut Vec<(NodeId, Affine2D)>,
204    ) {
205        let Some(node) = self.get(id) else { return };
206        let world = parent_transform.then(node.transform);
207
208        match &node.content {
209            NodeContent::Container => {}
210            NodeContent::Mark(_) | NodeContent::Batch(_) => {
211                result.push((id, world));
212            }
213        }
214
215        // Sort children indices by z_order without cloning
216        let mut indices: Vec<usize> = (0..node.children.len()).collect();
217        indices.sort_by_key(|&i| self.get(node.children[i]).map_or(0, |n| n.z_order));
218
219        for i in indices {
220            // Re-fetch node since we can't hold a reference across recursive calls
221            let child_id = self.get(id).map(|n| n.children[i]);
222            if let Some(cid) = child_id {
223                self.collect_marks_recursive(cid, world, result);
224            }
225        }
226    }
227}
228
229impl Default for SceneGraph {
230    fn default() -> Self {
231        Self::new()
232    }
233}
234
235#[cfg(test)]
236mod tests {
237    use super::*;
238    use crate::mark::Mark;
239    use crate::style::StrokeStyle;
240
241    #[test]
242    fn insert_and_get() {
243        let mut sg = SceneGraph::new();
244        let id = sg.insert(Node::container());
245        assert!(sg.get(id).is_some());
246        assert_eq!(sg.len(), 1);
247    }
248
249    #[test]
250    fn remove_and_reuse() {
251        let mut sg = SceneGraph::new();
252        let id1 = sg.insert(Node::container());
253        sg.remove(id1);
254        assert!(sg.get(id1).is_none());
255        assert_eq!(sg.len(), 0);
256
257        // Next insert reuses the slot
258        let id2 = sg.insert(Node::container());
259        assert_eq!(id2.index, id1.index);
260        assert_ne!(id2.generation, id1.generation);
261    }
262
263    #[test]
264    fn parent_child() {
265        let mut sg = SceneGraph::with_root();
266        let root = sg.root().unwrap();
267        let child = sg.insert_child(root, Node::container());
268
269        let root_node = sg.get(root).unwrap();
270        assert_eq!(root_node.children.len(), 1);
271        assert_eq!(root_node.children[0], child);
272
273        let child_node = sg.get(child).unwrap();
274        assert_eq!(child_node.parent, Some(root));
275    }
276
277    #[test]
278    fn collect_marks_ordering() {
279        let mut sg = SceneGraph::with_root();
280        let root = sg.root().unwrap();
281
282        let mut n1 = Node::with_mark(Mark::Rule(crate::mark::RuleMark {
283            segments: vec![],
284            stroke: StrokeStyle::default(),
285        }));
286        n1.z_order = 2;
287
288        let mut n2 = Node::with_mark(Mark::Rule(crate::mark::RuleMark {
289            segments: vec![],
290            stroke: StrokeStyle::default(),
291        }));
292        n2.z_order = 1;
293
294        sg.insert_child(root, n1);
295        sg.insert_child(root, n2);
296
297        let marks = sg.collect_marks();
298        assert_eq!(marks.len(), 2);
299        // z_order=1 should come first
300        let first = sg.get(marks[0].0).unwrap();
301        let second = sg.get(marks[1].0).unwrap();
302        assert!(first.z_order <= second.z_order);
303    }
304}