Skip to main content

cranpose_ui/
semantics_dispatch.rs

1use std::{cell::RefCell, collections::HashSet};
2
3use cranpose_core::NodeId;
4
5struct SemanticsInvalidationManager {
6    dirty_nodes: HashSet<NodeId>,
7    is_processing: bool,
8}
9
10impl SemanticsInvalidationManager {
11    fn new() -> Self {
12        Self {
13            dirty_nodes: HashSet::new(),
14            is_processing: false,
15        }
16    }
17
18    fn schedule_invalidation(&mut self, node_id: NodeId) {
19        self.dirty_nodes.insert(node_id);
20    }
21
22    fn has_pending_invalidation(&self) -> bool {
23        !self.dirty_nodes.is_empty()
24    }
25
26    fn take_pending_for_processing(&mut self) -> Option<Vec<NodeId>> {
27        if self.is_processing {
28            return None;
29        }
30
31        self.is_processing = true;
32        Some(self.dirty_nodes.drain().collect())
33    }
34
35    fn finish_processing<I>(&mut self, remaining: I)
36    where
37        I: IntoIterator<Item = NodeId>,
38    {
39        self.dirty_nodes.extend(remaining);
40        self.is_processing = false;
41    }
42
43    fn clear(&mut self) {
44        self.dirty_nodes.clear();
45    }
46}
47
48pub(crate) struct SemanticsInvalidationState {
49    manager: RefCell<SemanticsInvalidationManager>,
50}
51
52impl SemanticsInvalidationState {
53    pub(crate) fn new() -> Self {
54        Self {
55            manager: RefCell::new(SemanticsInvalidationManager::new()),
56        }
57    }
58
59    fn schedule_invalidation(&self, node_id: NodeId) {
60        self.manager.borrow_mut().schedule_invalidation(node_id);
61    }
62
63    fn has_pending_invalidation(&self) -> bool {
64        self.manager.borrow().has_pending_invalidation()
65    }
66
67    fn process_invalidations<F>(&self, processor: F)
68    where
69        F: FnMut(NodeId),
70    {
71        let Some(nodes) = self.manager.borrow_mut().take_pending_for_processing() else {
72            return;
73        };
74
75        self.process_pending_nodes(nodes, processor);
76    }
77
78    fn clear(&self) {
79        self.manager.borrow_mut().clear();
80    }
81
82    fn process_pending_nodes<F>(&self, nodes: Vec<NodeId>, mut processor: F)
83    where
84        F: FnMut(NodeId),
85    {
86        let mut remaining = nodes.into_iter();
87        let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
88            for node_id in remaining.by_ref() {
89                processor(node_id);
90            }
91        }));
92
93        self.manager.borrow_mut().finish_processing(remaining);
94
95        if let Err(payload) = result {
96            std::panic::resume_unwind(payload);
97        }
98    }
99}
100
101/// Marks `node_id`'s semantics for re-collection on the next frame of the app
102/// context that is current.
103///
104/// [`crate::SemanticsRequester::invalidate`] uses the by-id form instead: it
105/// remembers the context its node attached in, so a request raised from a frame
106/// loop or an event callback outside any context still reaches the right queue.
107pub fn schedule_semantics_invalidation(node_id: NodeId) {
108    crate::render_state::with_semantics_dispatch(|state| {
109        state.schedule_invalidation(node_id);
110    });
111}
112
113pub(crate) fn schedule_semantics_invalidation_in(
114    app_context: crate::render_state::AppContextId,
115    node_id: NodeId,
116) {
117    crate::render_state::with_semantics_dispatch_by_app_context(app_context, |state| {
118        state.schedule_invalidation(node_id);
119    });
120}
121
122/// Whether any semantics invalidations are waiting to be serviced.
123pub fn has_pending_semantics_invalidations() -> bool {
124    crate::render_state::with_semantics_dispatch(
125        SemanticsInvalidationState::has_pending_invalidation,
126    )
127}
128
129/// Services every pending semantics invalidation.
130///
131/// The host calls this once per frame with the applier available, and bubbles
132/// the dirty flag from each node to the root.
133pub fn process_semantics_invalidations<F>(processor: F)
134where
135    F: FnMut(NodeId),
136{
137    crate::render_state::with_semantics_dispatch(|state| state.process_invalidations(processor));
138}
139
140/// Drops every pending semantics invalidation without servicing it.
141pub fn clear_semantics_invalidations() {
142    crate::render_state::with_semantics_dispatch(SemanticsInvalidationState::clear);
143}
144
145#[cfg(test)]
146#[path = "tests/semantics_dispatch_tests.rs"]
147mod tests;