git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

src/scene/tree.rs (16.1K)

  1 //! `WidgetTree` — `UiContext`'s widget tree (`UiContext::tree`): the widgets themselves and
  2 //! their parent/child links, in one generational [`Arena`] keyed through a
  3 //! `WidgetId → NodeId` index, so the context's `WidgetId`-based API (`link_ids`,
  4 //! `clear_hierarchy`, …) and the apps' handles name nodes by id. Links are **symmetric** by
  5 //! construction: `set_parent` / `unlink` update both ends. (The two `HashMap`s it replaced —
  6 //! an id → pointer registry and a parents/children pair kept in step by hand — were sometimes
  7 //! left asymmetric; `docs/rfc-core-rebuild.md` Phase 1b.)
  8 //!
  9 //! ## The tree owns its widgets
 10 //!
 11 //! Every widget in the tree was moved into it (`UiContext::insert`) and lives in an allocation
 12 //! the tree keeps, held as a raw ROOT (never a `Box` across accesses: a `Box` is a unique
 13 //! pointer to the language, and every reborrow through it would invalidate the references the
 14 //! context hands out). Every access — the app's through a handle, the context's dispatch —
 15 //! derives from that root, and a widget out on loan (`UiContext::lend`) resolves to nothing,
 16 //! so no two `&mut`s to one widget ever coexist. Until 2026-10-08 the tree held raw pointers
 17 //! to widgets the APP owned, each watched by a liveness token; that path is gone
 18 //! (`docs/rfc-owning-registry.md`, phase 5).
 19 use std::any::TypeId;
 20 use std::collections::HashMap;
 21 use std::ptr::NonNull;
 22 
 23 use crate::scene::arena::{Arena, NodeId};
 24 use crate::widget::{WidgetHost, WidgetId};
 25 
 26 /// One arena node's payload: the widget's stable id and, once it is inserted, the widget. A
 27 /// node can be LINKED (as a parent or child) before its widget is inserted, as an app may
 28 /// link ids before it builds the widgets; such a node resolves to nothing.
 29 struct Entry {
 30     id: WidgetId,
 31     slot: Option<Slot>,
 32     /// Out on loan (`UiContext::lend`): while set, nothing in the tree resolves the widget,
 33     /// so a re-entrant reach for it is a miss rather than a second `&mut`.
 34     lent: bool,
 35 }
 36 
 37 /// A widget the tree owns: its allocation, held as the raw root every access derives from,
 38 /// and its type for typed access. Dropping the slot drops the widget.
 39 struct Slot {
 40     root: NonNull<dyn WidgetHost>,
 41     type_id: TypeId,
 42 }
 43 
 44 impl Drop for Slot {
 45     fn drop(&mut self) {
 46         // SAFETY: `root` was made by `Box::into_raw` in `insert_owned` and is freed only here,
 47         // once (`take_owned` forgets the slot it frees itself).
 48         unsafe { drop(Box::from_raw(self.root.as_ptr())) };
 49     }
 50 }
 51 
 52 /// The entry's widget, unless there is none yet or it is out on loan.
 53 #[inline]
 54 fn live_ptr(entry: &Entry) -> Option<*mut (dyn WidgetHost + 'static)> {
 55     if entry.lent {
 56         return None;
 57     }
 58     entry.slot.as_ref().map(|s| s.root.as_ptr())
 59 }
 60 
 61 /// The consolidated, generational widget tree. See the module docs.
 62 pub struct WidgetTree {
 63     arena: Arena<Entry>,
 64     by_id: HashMap<WidgetId, NodeId>,
 65 }
 66 
 67 impl Default for WidgetTree {
 68     fn default() -> Self {
 69         Self::new()
 70     }
 71 }
 72 
 73 impl WidgetTree {
 74     pub fn new() -> Self {
 75         WidgetTree { arena: Arena::new(), by_id: HashMap::new() }
 76     }
 77 
 78     /// Number of nodes known to the tree (inserted or link-only).
 79     pub fn len(&self) -> usize {
 80         self.arena.len()
 81     }
 82 
 83     pub fn is_empty(&self) -> bool {
 84         self.arena.is_empty()
 85     }
 86 
 87     /// Get (or lazily create) the arena node for `id`. A freshly created node has no widget
 88     /// until [`insert_owned`](WidgetTree::insert_owned) supplies one. Re-creates the node if a
 89     /// stale `by_id` entry points at a removed slot.
 90     fn ensure_node(&mut self, id: WidgetId) -> NodeId {
 91         if let Some(&node) = self.by_id.get(&id) {
 92             if self.arena.contains(node) {
 93                 return node;
 94             }
 95         }
 96         let node = self.arena.insert(Entry { id, slot: None, lent: false });
 97         self.by_id.insert(id, node);
 98         node
 99     }
100 
101     /// Make `child` a child of `parent` (deduped, reparenting from any previous parent). Keeps
102     /// both ends of the edge consistent. No-op (rather than panic) if the link would form a
103     /// cycle.
104     pub fn link(&mut self, parent: WidgetId, child: WidgetId) {
105         let parent_node = self.ensure_node(parent);
106         let child_node = self.ensure_node(child);
107         if parent_node == child_node || self.arena.is_ancestor(child_node, parent_node) {
108             return;
109         }
110         self.arena.append_child(parent_node, child_node);
111     }
112 
113     /// Set or clear `child`'s parent. `Some(p)` links symmetrically (as [`link`](WidgetTree::link));
114     /// `None` detaches `child` from its current parent.
115     pub fn set_parent(&mut self, child: WidgetId, parent: Option<WidgetId>) {
116         match parent {
117             Some(p) => self.link(p, child),
118             None => {
119                 if let Some(&node) = self.by_id.get(&child) {
120                     self.arena.detach(node);
121                 }
122             }
123         }
124     }
125 
126     /// Remove `child` from `parent` if it is currently a child of it.
127     pub fn unlink(&mut self, parent: WidgetId, child: WidgetId) {
128         if let (Some(&child_node), Some(&parent_node)) =
129             (self.by_id.get(&child), self.by_id.get(&parent))
130         {
131             if self.arena.parent(child_node) == Some(parent_node) {
132                 self.arena.detach(child_node);
133             }
134         }
135     }
136 
137     /// Detach all of `parent`'s children, leaving them as roots. Non-recursive.
138     pub fn clear_children(&mut self, parent: WidgetId) {
139         if let Some(&parent_node) = self.by_id.get(&parent) {
140             let children: Vec<NodeId> = self.arena.children(parent_node).to_vec();
141             for child in children {
142                 self.arena.detach(child);
143             }
144         }
145     }
146 
147     /// Drop every link, and every node that is only linked: the widgets stay, unlinked, as
148     /// roots — an app that rebuilds its links every frame does not hand its widgets back by
149     /// doing so.
150     pub fn clear_all(&mut self) {
151         let nodes: Vec<NodeId> = self.by_id.values().copied().collect();
152         for &node in &nodes {
153             if self.arena.contains(node) {
154                 self.arena.detach(node);
155             }
156         }
157         for node in nodes {
158             let keep = self.arena.value(node).is_some_and(|e| e.slot.is_some());
159             if !keep && self.arena.contains(node) {
160                 let id = self.arena.value(node).map(|e| e.id);
161                 self.arena.remove_subtree(node);
162                 if let Some(id) = id {
163                     self.by_id.remove(&id);
164                 }
165             }
166         }
167         self.by_id.retain(|_, n| self.arena.contains(*n));
168     }
169 
170     /// Take ownership of `widget`: it moves into an allocation the tree keeps, under its own
171     /// id (keeping any links made to that id already), and is dropped when it is taken back
172     /// or the tree is. Returns the id.
173     pub fn insert_owned<W: WidgetHost + 'static>(&mut self, widget: W) -> WidgetId {
174         let id = widget.base().id();
175         let raw: *mut (dyn WidgetHost + 'static) = Box::into_raw(Box::new(widget));
176         // SAFETY: `Box::into_raw` never returns null.
177         let root = unsafe { NonNull::new_unchecked(raw) };
178         let node = self.ensure_node(id);
179         let entry = self.arena.value_mut(node).unwrap();
180         // Two widgets with one id are a clone of a widget whose id was already drawn (the id
181         // cell is copied): the second would replace — and drop — the first.
182         debug_assert!(entry.slot.is_none(), "insert: {id:?} is already in the context (a clone of a widget that is?)");
183         entry.lent = false;
184         entry.slot = Some(Slot { root, type_id: TypeId::of::<W>() });
185         id
186     }
187 
188     /// The tree's widget `id` as a `W`: its root, when it is there, it is a `W`, and it is
189     /// not out on loan.
190     pub fn owned_root<W: WidgetHost + 'static>(&self, id: WidgetId) -> Option<NonNull<W>> {
191         let entry = self.arena.value(*self.by_id.get(&id)?)?;
192         let slot = entry.slot.as_ref()?;
193         if entry.lent || slot.type_id != TypeId::of::<W>() {
194             return None;
195         }
196         Some(slot.root.cast::<W>())
197     }
198 
199     /// Give the tree's widget `id` back by value. Its node goes; its children stay, as roots.
200     pub fn take_owned<W: WidgetHost + 'static>(&mut self, id: WidgetId) -> Option<W> {
201         let node = *self.by_id.get(&id)?;
202         let entry = self.arena.value_mut(node)?;
203         if entry.lent || entry.slot.as_ref()?.type_id != TypeId::of::<W>() {
204             return None;
205         }
206         let slot = std::mem::ManuallyDrop::new(entry.slot.take()?);
207         // SAFETY: the slot's root was made by `Box::into_raw` of a `W` (its type id says so);
208         // the slot is forgotten (ManuallyDrop) so it is freed only here, by the `Box` the
209         // widget is moved out of.
210         let widget = unsafe { *Box::from_raw(slot.root.cast::<W>().as_ptr()) };
211         self.clear_children(id);
212         self.arena.remove_subtree(node);
213         self.by_id.remove(&id);
214         Some(widget)
215     }
216 
217     /// Mark `id` lent (or back). Returns whether it was free to lend: false for an unknown id,
218     /// a link-only one, or one already out.
219     pub fn set_lent(&mut self, id: WidgetId, lent: bool) -> bool {
220         let Some(&node) = self.by_id.get(&id) else { return false };
221         let Some(entry) = self.arena.value_mut(node) else { return false };
222         if lent && (entry.lent || entry.slot.is_none()) {
223             return false;
224         }
225         entry.lent = lent;
226         true
227     }
228 
229     /// Whether `id` names a widget the tree holds and has not lent out.
230     pub fn is_registered(&self, id: WidgetId) -> bool {
231         self.get_ptr(id).is_some()
232     }
233 
234     /// The widget `id` names, or `None` if unknown, link-only or out on loan. For the
235     /// context, which hands out references derived from it under its own borrow rules.
236     pub(crate) fn get_ptr(&self, id: WidgetId) -> Option<*mut (dyn WidgetHost + 'static)> {
237         let node = *self.by_id.get(&id)?;
238         live_ptr(self.arena.value(node)?)
239     }
240 
241     /// `id`'s parent id, if any.
242     pub fn parent_id(&self, id: WidgetId) -> Option<WidgetId> {
243         let node = *self.by_id.get(&id)?;
244         let parent = self.arena.parent(node)?;
245         Some(self.arena.value(parent)?.id)
246     }
247 
248     /// `id`'s child ids in order (including link-only children not yet inserted).
249     pub fn child_ids(&self, id: WidgetId) -> Vec<WidgetId> {
250         let Some(&node) = self.by_id.get(&id) else { return Vec::new() };
251         self.arena.children(node).iter().filter_map(|&c| self.arena.value(c).map(|e| e.id)).collect()
252     }
253 
254     /// `id`'s children in order, skipping any that is link-only or out on loan.
255     pub(crate) fn children_ptrs(&self, id: WidgetId) -> Vec<*mut (dyn WidgetHost + 'static)> {
256         let Some(&node) = self.by_id.get(&id) else { return Vec::new() };
257         self.arena
258             .children(node)
259             .iter()
260             .filter_map(|&c| live_ptr(self.arena.value(c)?))
261             .collect()
262     }
263 
264     /// Every widget the tree holds and has not lent out, for the passes that sweep the whole
265     /// registry (`clear_dirty`, `rebuild_spatial_grid`, the focus walk).
266     pub(crate) fn iter_registered(&self) -> impl Iterator<Item = (WidgetId, *mut (dyn WidgetHost + 'static))> + '_ {
267         self.by_id.values().filter_map(move |&node| {
268             let entry = self.arena.value(node)?;
269             live_ptr(entry).map(|p| (entry.id, p))
270         })
271     }
272 
273     /// The ids of every widget the tree holds and has not lent out, in no particular order.
274     pub fn registered_ids(&self) -> Vec<WidgetId> {
275         self.iter_registered().map(|(id, _)| id).collect()
276     }
277 }
278 
279 #[cfg(test)]
280 mod tests {
281     use super::*;
282 
283     /// A minimal widget whose drops are counted.
284     struct Marker {
285         base: crate::widget::Widget,
286         drops: std::rc::Rc<std::cell::Cell<u32>>,
287     }
288     impl Drop for Marker {
289         fn drop(&mut self) {
290             self.drops.set(self.drops.get() + 1);
291         }
292     }
293     impl WidgetHost for Marker {
294         crate::impl_widget_base!(Marker);
295     }
296 
297     fn marker(drops: &std::rc::Rc<std::cell::Cell<u32>>) -> Marker {
298         Marker { base: crate::widget::Widget::new(), drops: drops.clone() }
299     }
300 
301     fn tree_of(n: usize) -> (WidgetTree, Vec<WidgetId>, std::rc::Rc<std::cell::Cell<u32>>) {
302         let drops = std::rc::Rc::new(std::cell::Cell::new(0));
303         let mut tree = WidgetTree::new();
304         let ids = (0..n).map(|_| tree.insert_owned(marker(&drops))).collect();
305         (tree, ids, drops)
306     }
307 
308     #[test]
309     fn insert_and_resolve() {
310         let (tree, ids, _) = tree_of(1);
311         assert!(tree.is_registered(ids[0]));
312         assert!(tree.owned_root::<Marker>(ids[0]).is_some());
313         assert!(tree.owned_root::<crate::widget::Adapted<crate::widget::Slider>>(ids[0]).is_none(), "typed by what was inserted");
314         assert!(!tree.is_registered(WidgetId(usize::MAX)), "unknown id resolves to nothing");
315     }
316 
317     #[test]
318     fn link_is_symmetric_and_deduped() {
319         let (mut tree, ids, _) = tree_of(2);
320         let (p, c) = (ids[0], ids[1]);
321         tree.link(p, c);
322         tree.link(p, c); // duplicate link is a no-op
323         assert_eq!(tree.parent_id(c), Some(p));
324         assert_eq!(tree.child_ids(p), vec![c]);
325         assert_eq!(tree.children_ptrs(p), vec![tree.get_ptr(c).unwrap()]);
326     }
327 
328     #[test]
329     fn reparenting_removes_from_old_parent() {
330         let (mut tree, ids, _) = tree_of(3);
331         let (a, b, c) = (ids[0], ids[1], ids[2]);
332         tree.link(a, c);
333         tree.link(b, c);
334         assert!(tree.child_ids(a).is_empty(), "old parent drops the child");
335         assert_eq!(tree.child_ids(b), vec![c]);
336         assert_eq!(tree.parent_id(c), Some(b));
337         tree.link(c, b);
338         assert_eq!(tree.parent_id(b), None, "a link that would close a cycle is refused");
339     }
340 
341     #[test]
342     fn a_link_can_come_before_the_widget() {
343         let (mut tree, ids, drops) = tree_of(1);
344         let p = ids[0];
345         let w = marker(&drops);
346         let child = w.base.id();
347         tree.link(p, child);
348         assert_eq!(tree.child_ids(p), vec![child]);
349         assert!(tree.children_ptrs(p).is_empty(), "a link-only child resolves to nothing");
350         tree.insert_owned(w);
351         assert_eq!(tree.child_ids(p), vec![child], "inserting keeps the link");
352         assert_eq!(tree.children_ptrs(p).len(), 1);
353     }
354 
355     #[test]
356     fn detaching_and_clearing_keep_the_widgets() {
357         let (mut tree, ids, drops) = tree_of(3);
358         let (p, c1, c2) = (ids[0], ids[1], ids[2]);
359         tree.link(p, c1);
360         tree.link(p, c2);
361         tree.set_parent(c1, None);
362         assert_eq!(tree.child_ids(p), vec![c2], "a symmetric detach");
363         tree.clear_children(p);
364         assert!(tree.child_ids(p).is_empty() && tree.parent_id(c2).is_none());
365         tree.link(p, c1);
366         tree.link(p, WidgetId(usize::MAX)); // link-only
367         tree.clear_all();
368         assert_eq!(tree.len(), 3, "the link-only node goes, the widgets stay");
369         assert!(ids.iter().all(|&id| tree.is_registered(id) && tree.parent_id(id).is_none()));
370         assert_eq!(drops.get(), 0);
371     }
372 
373     #[test]
374     fn a_widget_taken_back_leaves_its_children() {
375         let (mut tree, ids, drops) = tree_of(2);
376         let (p, c) = (ids[0], ids[1]);
377         tree.link(p, c);
378         let back = tree.take_owned::<Marker>(p).expect("given back");
379         assert_eq!(back.base.id(), p);
380         assert!(!tree.is_registered(p) && tree.take_owned::<Marker>(p).is_none());
381         assert!(tree.is_registered(c) && tree.parent_id(c).is_none(), "the child stays, a root");
382         assert_eq!(drops.get(), 0);
383         drop(back);
384         drop(tree);
385         assert_eq!(drops.get(), 2, "the tree drops what it holds");
386     }
387 
388     #[test]
389     fn a_lent_widget_resolves_to_nothing() {
390         let (mut tree, ids, _) = tree_of(2);
391         let (p, c) = (ids[0], ids[1]);
392         tree.link(p, c);
393         assert!(tree.set_lent(c, true));
394         assert!(!tree.set_lent(c, true), "not lent twice");
395         assert!(tree.get_ptr(c).is_none() && tree.children_ptrs(p).is_empty());
396         assert!(tree.owned_root::<Marker>(c).is_none() && tree.take_owned::<Marker>(c).is_none());
397         assert_eq!(tree.registered_ids(), vec![p]);
398         tree.set_lent(c, false);
399         assert!(tree.is_registered(c));
400         assert!(!tree.set_lent(WidgetId(usize::MAX), true), "nothing to lend");
401     }
402 }