Skip to main content

cranpose_core/
node.rs

1use std::any::{Any, TypeId};
2
3use smallvec::SmallVec;
4
5use crate::{
6    Applier, Command, Composer, DirtyBubble, NodeError, NodeId, composer_context,
7    with_current_composer,
8};
9
10/// Runs `f` on the node `id` as an `N`, using the current composer. Returns an error if the node
11/// does not exist or is not an `N`. Must be called during composition.
12pub fn with_node_mut<N: Node + 'static, R>(
13    id: NodeId,
14    f: impl FnOnce(&mut N) -> R,
15) -> Result<R, NodeError> {
16    with_current_composer(|composer| composer.with_node_mut(id, f))
17}
18
19/// Makes the node `id` the parent of the nodes emitted next, using the current composer. Needs a
20/// matching `pop_parent`. Must be called during composition.
21pub fn push_parent(id: NodeId) {
22    with_current_composer(|composer| composer.push_parent(id));
23}
24
25/// Closes the parent that the matching `push_parent` opened, using the current composer. Must be
26/// called during composition.
27pub fn pop_parent() {
28    with_current_composer(Composer::pop_parent);
29}
30
31/// A node of the tree that a composition builds. An applier stores the nodes by id. Every method
32/// has a default, so a simple node implements none of them.
33pub trait Node: Any {
34    /// Called once the node has its id, when the composition applies the node's creation.
35    fn mount(&mut self) {}
36    /// Called just before the node is removed from the applier.
37    fn unmount(&mut self) {}
38    /// Adds `child` to this node's child list, returning whether the list
39    /// actually changed. A node that already holds the child returns `false`:
40    /// callers record a structural change from this answer, and a structural
41    /// change re-lowers the whole subtree, so claiming one for a no-op costs a
42    /// full rebuild per call.
43    fn insert_child(&mut self, _child: NodeId) -> bool {
44        false
45    }
46    /// Removes `child` from this node's child list, returning whether the list
47    /// actually changed. Only this node can answer that: a caller inspecting
48    /// the child's parent pointer gets it wrong when the child was reparented
49    /// while still listed here.
50    fn remove_child(&mut self, _child: NodeId) -> bool {
51        false
52    }
53    /// Moves the child at the first index to the second index in the child list.
54    fn move_child(&mut self, _from: usize, _to: usize) {}
55    /// Replaces the child list with `children`, in order.
56    fn update_children(&mut self, _children: &[NodeId]) {}
57    /// Whether the node adds and drops its children itself, as a
58    /// subcomposition host does with its slot hosts: the child sync a
59    /// composition pass ends with then leaves its list alone. The default
60    /// keeps the list the passes compose.
61    fn keeps_own_children(&self) -> bool {
62        false
63    }
64    /// Copies child IDs into the provided scratch buffer without allocating a
65    /// fresh container for every traversal. A node without children leaves
66    /// the buffer empty.
67    fn collect_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
68        out.clear();
69    }
70    /// Runs `f` on each child id in the order [`Node::collect_children_into`]
71    /// lists them. A node that holds its list in place overrides this to
72    /// visit it without a copy.
73    fn for_each_child(&self, f: &mut dyn FnMut(NodeId)) {
74        let mut children = SmallVec::<[NodeId; 8]>::new();
75        self.collect_children_into(&mut children);
76        for child in children {
77            f(child);
78        }
79    }
80    /// Copies the ids of the children this node owns, in order, into `out`. A node whose
81    /// `collect_children_into` leaves some children out overrides this to list them all. The
82    /// default calls `collect_children_into`.
83    fn collect_owned_children_into(&self, out: &mut SmallVec<[NodeId; 8]>) {
84        self.collect_children_into(out);
85    }
86    /// Finds a child in the order returned by [`Node::collect_owned_children_into`].
87    /// Nodes with slice-backed child storage should override this to avoid
88    /// materializing the whole list for a single lookup.
89    ///
90    /// ```
91    /// use cranpose_core::{Node, NodeId};
92    ///
93    /// fn child_is_first(parent: &dyn Node, child: NodeId) -> bool {
94    ///     parent.owned_child_index(child) == Some(0)
95    /// }
96    /// ```
97    fn owned_child_index(&self, child: NodeId) -> Option<usize> {
98        let mut children = SmallVec::<[NodeId; 8]>::new();
99        self.collect_owned_children_into(&mut children);
100        children.iter().position(|&id| id == child)
101    }
102    /// Called after the node is created to record its own ID.
103    /// Useful for nodes that need to store their ID for later operations.
104    fn set_node_id(&mut self, _id: NodeId) {}
105    /// Called when this node is attached to a parent.
106    /// Nodes with parent tracking should set their parent reference here.
107    fn on_attached_to_parent(&mut self, _parent: NodeId) {}
108    /// Called when this node is removed from its parent.
109    /// Nodes with parent tracking should clear their parent reference here.
110    fn on_removed_from_parent(&mut self) {}
111    /// Get this node's parent ID (for nodes that track parents).
112    /// Returns None if node has no parent or doesn't track parents.
113    fn parent(&self) -> Option<NodeId> {
114        None
115    }
116    /// Mark this node as needing layout (for nodes with dirty flags).
117    /// Called during bubbling to propagate dirtiness up the tree.
118    fn mark_needs_layout(&self) {}
119    /// Check if this node needs layout (for nodes with dirty flags).
120    fn needs_layout(&self) -> bool {
121        false
122    }
123    /// Mark this node as needing measure (size may have changed).
124    /// Called during bubbling when children are added/removed.
125    fn mark_needs_measure(&self) {}
126    /// Check if this node needs measure (for nodes with dirty flags).
127    fn needs_measure(&self) -> bool {
128        false
129    }
130    /// Mark that a node below this one needs layout, or measure when
131    /// `measure` is set: a layout pass has to visit this node to reach it,
132    /// while this node's own measurement may still hold. A node that does not
133    /// track the difference marks itself dirty.
134    fn mark_descendant_needs_layout(&self, measure: bool) {
135        if measure {
136            self.mark_needs_measure();
137        } else {
138            self.mark_needs_layout();
139        }
140    }
141    /// Check if a node below this one needs layout or measure.
142    fn descendant_needs_layout(&self) -> bool {
143        false
144    }
145    /// Check if a node below this one needs measure.
146    fn descendant_needs_measure(&self) -> bool {
147        false
148    }
149    /// Check if a layout pass has to visit this node: the node or a node
150    /// below it needs layout or measure.
151    fn layout_dirty(&self) -> bool {
152        self.needs_measure() || self.needs_layout() || self.descendant_needs_layout()
153    }
154    /// Whether the node lays out nothing itself and its parent lays out its
155    /// children, so a change of its own is a change of its parent's.
156    fn is_virtual(&self) -> bool {
157        false
158    }
159    /// Mark this node as needing semantics recomputation.
160    fn mark_needs_semantics(&self) {}
161    /// Mark that the semantics tree has to read this node again while its
162    /// own semantics are unchanged: a node below it changed its semantics,
163    /// or its own placement or children changed.
164    fn mark_descendant_needs_semantics(&self) {
165        self.mark_needs_semantics();
166    }
167    /// Check if this node or a node below it needs semantics recomputation.
168    fn needs_semantics(&self) -> bool {
169        false
170    }
171    /// Set parent reference for dirty flag bubbling ONLY.
172    /// This is a minimal version of on_attached_to_parent that doesn't trigger
173    /// registry updates or other side effects. Used during measurement when we
174    /// need to establish parent connections for bubble_measure_dirty without
175    /// causing the full attachment lifecycle.
176    ///
177    /// An existing parent is preserved until the structural attach operation
178    /// removes the node from its previous container.
179    fn set_parent_for_bubbling(&mut self, parent: NodeId) {
180        if self.parent().is_none() {
181            self.on_attached_to_parent(parent);
182        }
183    }
184
185    /// Returns a recycle pool key when this node supports shell reuse.
186    fn recycle_key(&self) -> Option<TypeId> {
187        None
188    }
189
190    /// Bounds how many recyclable shells of this node type should be retained.
191    fn recycle_pool_limit(&self) -> Option<usize> {
192        None
193    }
194
195    /// Clears live attachments before the node shell enters a recycle pool.
196    fn prepare_for_recycle(&mut self) {}
197
198    /// Optionally provides a compact replacement box for this recycled shell.
199    ///
200    /// Returning `Some` lets nodes move pooled survivors onto fresh compact
201    /// storage so the recycle pool does not pin large spike-era allocations.
202    fn rehouse_for_recycle(&self) -> Option<Box<dyn Node>> {
203        None
204    }
205
206    /// Optionally moves a live node onto a fresh box during applier compaction.
207    ///
208    /// This is used after large-majority teardowns where a small surviving live
209    /// tree can otherwise pin allocator pages from a much larger spike-era node
210    /// population. Implementations must preserve the node's live state.
211    fn rehouse_for_live_compaction(&mut self) -> Option<Box<dyn Node>> {
212        None
213    }
214
215    /// Returns the node-owned heap retained beyond the node's own box allocation.
216    fn debug_heap_bytes(&self) -> usize {
217        0
218    }
219}
220
221// forwards on purpose: DirtyBubble is crate-private, and this is the public entry
222/// Unified API for bubbling layout dirty flags from a node to the root (Applier context).
223///
224/// This is the canonical function for dirty bubbling during the apply phase (structural changes).
225/// Call this after mutations like insert/remove/move that happen during apply.
226///
227/// # Behavior
228/// 1. Marks the starting node as needing layout
229/// 2. Walks up the parent chain, marking each ancestor
230/// 3. Stops at the root (node with no parent)
231///
232/// # Performance
233/// This function is O(height): a scoped layout repass can leave a dirty node
234/// under a clean ancestor, so the walk does not stop at a dirty ancestor.
235///
236/// # Usage
237/// - Call from composer mutations (insert/remove/move) during apply phase
238/// - Call from applier-level operations that modify the tree structure
239pub fn bubble_layout_dirty(applier: &mut dyn Applier, node_id: NodeId) {
240    DirtyBubble::LAYOUT.apply(applier, node_id);
241}
242
243// forwards on purpose: DirtyBubble is crate-private, and this is the public entry
244/// Unified API for bubbling measure dirty flags from a node to the root (Applier context).
245///
246/// Call this when a node's size may have changed (children added/removed, modifier changed).
247/// This ensures that measure_layout will increment the cache epoch and re-measure the subtree.
248///
249/// # Behavior
250/// 1. Marks the starting node as needing measure
251/// 2. Walks up the parent chain, marking each ancestor
252/// 3. Stops at the root (node with no parent)
253pub fn bubble_measure_dirty(applier: &mut dyn Applier, node_id: NodeId) {
254    DirtyBubble::MEASURE.apply(applier, node_id);
255}
256
257// forwards on purpose: DirtyBubble is crate-private, and this is the public entry
258/// Unified API for bubbling semantics dirty flags from a node to the root (Applier context).
259///
260/// This mirrors [`bubble_layout_dirty`] but toggles semantics-specific dirty
261/// flags instead of layout ones, allowing semantics updates to propagate during
262/// the apply phase without forcing layout work.
263pub fn bubble_semantics_dirty(applier: &mut dyn Applier, node_id: NodeId) {
264    DirtyBubble::SEMANTICS.apply(applier, node_id);
265}
266
267/// Schedules semantics bubbling for a node using the active composer if present.
268///
269/// This defers the work to the apply phase where we can safely mutate the
270/// applier tree without re-entrantly borrowing the composer during composition.
271pub fn queue_semantics_invalidation(node_id: NodeId) {
272    let _ = composer_context::try_with_composer(|composer| {
273        composer.enqueue_semantics_invalidation(node_id);
274    });
275}
276
277/// Unified API for bubbling layout dirty flags from a node to the root (Composer context).
278///
279/// This is the canonical function for dirty bubbling during composition (property changes).
280/// Call this after property changes that happen during composition via with_node_mut.
281///
282/// # Behavior
283/// 1. Marks the starting node as needing layout
284/// 2. Walks up the parent chain, marking each ancestor
285/// 3. Stops at the root (node with no parent)
286///
287/// # Performance
288/// This function is O(height): a scoped layout repass can leave a dirty node
289/// under a clean ancestor, so the walk does not stop at a dirty ancestor.
290///
291/// # Type Requirements
292/// The node type N must implement Node (which includes mark_needs_layout, parent, etc.).
293/// Typically this will be LayoutNode or similar layout-aware node types.
294///
295/// # Usage
296/// - Call from property setters during composition (e.g., set_modifier, set_measure_policy)
297/// - Call from widget composition when layout-affecting state changes
298pub fn bubble_layout_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
299    bubble_layout_dirty_composer::<N>(node_id);
300}
301
302/// Unified API for bubbling measure dirty flags from a node to the root during composition.
303///
304/// This queues a dirty-bubble command on the active composer so measure invalidation
305/// runs during the apply phase, avoiding re-entrant applier borrows while widgets are
306/// mutating nodes via `with_node_mut`.
307pub fn bubble_measure_dirty_in_composer(node_id: NodeId) {
308    with_current_composer(|composer| {
309        composer.commands_mut().push(Command::BubbleDirty {
310            node_id,
311            bubble: DirtyBubble {
312                layout: false,
313                measure: true,
314                semantics: false,
315            },
316        });
317    });
318}
319
320/// Unified API for bubbling semantics dirty flags from a node to the root (Composer context).
321///
322/// This mirrors [`bubble_layout_dirty_in_composer`] but routes through the semantics
323/// dirty flag instead of the layout one. Modifier nodes can request semantics
324/// invalidations without triggering measure/layout work, and the runtime can
325/// query the root to determine whether the semantics tree needs rebuilding.
326pub fn bubble_semantics_dirty_in_composer<N: Node + 'static>(node_id: NodeId) {
327    bubble_semantics_dirty_composer::<N>(node_id);
328}
329
330fn bubble_layout_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
331    let _ = with_node_mut(node_id, |node: &mut N| {
332        node.mark_needs_layout();
333    });
334    with_current_composer(|composer| composer.borrow_applier().note_layout_dirt(node_id));
335
336    while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
337        let parent_id = pid;
338
339        let advanced = with_node_mut(parent_id, |node: &mut N| {
340            node.mark_descendant_needs_layout(false);
341            true
342        })
343        .unwrap_or(false);
344
345        if advanced {
346            node_id = parent_id;
347        } else {
348            break;
349        }
350    }
351}
352
353fn bubble_semantics_dirty_composer<N: Node + 'static>(mut node_id: NodeId) {
354    let _ = with_node_mut(node_id, |node: &mut N| {
355        node.mark_needs_semantics();
356    });
357
358    while let Ok(Some(pid)) = with_node_mut(node_id, |node: &mut N| node.parent()) {
359        let parent_id = pid;
360
361        let advanced = with_node_mut(parent_id, |node: &mut N| {
362            if !node.needs_semantics() {
363                node.mark_descendant_needs_semantics();
364            }
365            true
366        })
367        .unwrap_or(false);
368
369        if advanced {
370            node_id = parent_id;
371        } else {
372            break;
373        }
374    }
375}
376
377impl dyn Node {
378    /// Returns the node as `Any`, for downcasting to its concrete type.
379    pub fn as_any_mut(&mut self) -> &mut dyn Any {
380        self
381    }
382}