cranpose_ui/
pointer_dispatch.rs1use cranpose_core::NodeId;
9use std::cell::RefCell;
10use std::collections::HashSet;
11
12thread_local! {
13 static POINTER_DISPATCH_MANAGER: RefCell<PointerDispatchManager> =
14 RefCell::new(PointerDispatchManager::new());
15}
16
17struct PointerDispatchManager {
27 dirty_nodes: HashSet<NodeId>,
28 is_processing: bool,
29}
30
31impl PointerDispatchManager {
32 fn new() -> Self {
33 Self {
34 dirty_nodes: HashSet::new(),
35 is_processing: false,
36 }
37 }
38
39 fn schedule_repass(&mut self, node_id: NodeId) {
40 self.dirty_nodes.insert(node_id);
41 }
42
43 fn has_pending_repass(&self) -> bool {
44 !self.dirty_nodes.is_empty()
45 }
46
47 fn process_repasses<F>(&mut self, mut processor: F)
48 where
49 F: FnMut(NodeId),
50 {
51 if self.is_processing {
52 return;
53 }
54
55 self.is_processing = true;
56
57 let nodes: Vec<NodeId> = self.dirty_nodes.drain().collect();
59 for node_id in nodes {
60 processor(node_id);
61 }
62
63 self.is_processing = false;
64 }
65
66 fn clear(&mut self) {
67 self.dirty_nodes.clear();
68 }
69}
70
71pub fn schedule_pointer_repass(node_id: NodeId) {
76 POINTER_DISPATCH_MANAGER.with(|manager| {
77 manager.borrow_mut().schedule_repass(node_id);
78 });
79}
80
81pub fn has_pending_pointer_repasses() -> bool {
83 POINTER_DISPATCH_MANAGER.with(|manager| manager.borrow().has_pending_repass())
84}
85
86pub fn process_pointer_repasses<F>(processor: F)
92where
93 F: FnMut(NodeId),
94{
95 POINTER_DISPATCH_MANAGER.with(|manager| {
96 manager.borrow_mut().process_repasses(processor);
97 });
98}
99
100pub fn clear_pointer_repasses() {
102 POINTER_DISPATCH_MANAGER.with(|manager| {
103 manager.borrow_mut().clear();
104 });
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110
111 #[test]
112 fn schedule_and_process_repasses() {
113 clear_pointer_repasses();
114
115 let node1: NodeId = 1;
116 let node2: NodeId = 2;
117
118 schedule_pointer_repass(node1);
119 schedule_pointer_repass(node2);
120
121 assert!(has_pending_pointer_repasses());
122
123 let mut processed = Vec::new();
124 process_pointer_repasses(|node_id| {
125 processed.push(node_id);
126 });
127
128 assert_eq!(processed.len(), 2);
129 assert!(processed.contains(&node1));
130 assert!(processed.contains(&node2));
131 assert!(!has_pending_pointer_repasses());
132 }
133
134 #[test]
135 fn duplicate_schedules_deduplicated() {
136 clear_pointer_repasses();
137
138 let node: NodeId = 42;
139 schedule_pointer_repass(node);
140 schedule_pointer_repass(node);
141 schedule_pointer_repass(node);
142
143 let mut count = 0;
144 process_pointer_repasses(|_| {
145 count += 1;
146 });
147
148 assert_eq!(count, 1);
149 }
150}