teksilo_core/
async_completion.rs1use std::cell::RefCell;
25use std::collections::HashMap;
26use std::rc::Rc;
27
28use crate::widget::EventContext;
29use crate::widget_tree::WidgetTree;
30use crate::window::{NoopWindowOps, TeksiloWindowId};
31
32type CompletionCallback = Box<dyn FnOnce(&mut EventContext)>;
33
34struct Pending {
35 window_id: TeksiloWindowId,
36 callback: CompletionCallback,
37}
38
39struct CompletionState {
40 next_id: u64,
41 pending: HashMap<u64, Pending>,
42}
43
44#[derive(Clone)]
49pub struct AsyncCompletionHandle {
50 inner: Rc<RefCell<CompletionState>>,
51}
52
53impl Default for AsyncCompletionHandle {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl AsyncCompletionHandle {
60 pub fn new() -> Self {
61 Self {
62 inner: Rc::new(RefCell::new(CompletionState {
63 next_id: 0,
64 pending: HashMap::new(),
65 })),
66 }
67 }
68
69 pub fn register(&self, window_id: TeksiloWindowId, callback: CompletionCallback) -> u64 {
73 let mut state = self.inner.borrow_mut();
74 let id = state.next_id;
75 state.next_id = state.next_id.wrapping_add(1);
76 state.pending.insert(
77 id,
78 Pending {
79 window_id,
80 callback,
81 },
82 );
83 id
84 }
85
86 pub fn deliver(&self, id: u64, window_id: TeksiloWindowId, ctx: &mut EventContext) {
92 let entry = self.inner.borrow_mut().pending.remove(&id);
93 if let Some(pending) = entry
94 && pending.window_id == window_id
95 {
96 (pending.callback)(ctx);
97 }
98 }
99
100 pub fn purge_window(&self, window_id: TeksiloWindowId) {
104 self.inner
105 .borrow_mut()
106 .pending
107 .retain(|_, p| p.window_id != window_id);
108 }
109
110 pub fn drain_into(&self, tree: &mut WidgetTree) -> usize {
125 let mut ready: Vec<(u64, TeksiloWindowId)> = self
126 .inner
127 .borrow()
128 .pending
129 .iter()
130 .map(|(id, p)| (*id, p.window_id))
131 .collect();
132 ready.sort_by_key(|(id, _)| *id);
133 for (id, window_id) in &ready {
134 tree.run_with_event_context(&mut NoopWindowOps, |ctx| {
135 self.deliver(*id, *window_id, ctx)
136 });
137 }
138 ready.len()
139 }
140
141 pub fn pending_len(&self) -> usize {
143 self.inner.borrow().pending.len()
144 }
145}
146
147#[derive(Debug, Clone, Copy)]
152pub struct AsyncCompletionPayload {
153 pub id: u64,
154 pub window_id: TeksiloWindowId,
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::widget_tree::WidgetTree;
161 use crate::window::NoopWindowOps;
162 use std::cell::Cell;
163 use std::rc::Rc;
164
165 #[test]
166 fn deliver_runs_callback_with_fresh_context() {
167 let handle = AsyncCompletionHandle::new();
168 let win = TeksiloWindowId::new(1);
169 let ran = Rc::new(Cell::new(false));
170 let flag = ran.clone();
171 let id = handle.register(win, Box::new(move |_ctx| flag.set(true)));
172 assert_eq!(handle.pending_len(), 1);
173
174 let mut tree = WidgetTree::new();
175 tree.run_with_event_context(&mut NoopWindowOps, |ctx| handle.deliver(id, win, ctx));
176
177 assert!(ran.get(), "callback must run with the fresh context");
178 assert_eq!(
179 handle.pending_len(),
180 0,
181 "delivered completion must be removed"
182 );
183 }
184
185 #[test]
186 fn drain_into_delivers_every_pending_completion_in_order() {
187 let handle = AsyncCompletionHandle::new();
188 let order = Rc::new(RefCell::new(Vec::new()));
189 for n in 0..3 {
190 let log = order.clone();
191 handle.register(
192 TeksiloWindowId::new(n),
193 Box::new(move |_ctx| log.borrow_mut().push(n)),
194 );
195 }
196 let mut tree = WidgetTree::new();
197 assert_eq!(handle.drain_into(&mut tree), 3);
198 assert_eq!(*order.borrow(), vec![0, 1, 2]);
199 assert_eq!(handle.pending_len(), 0);
200 assert_eq!(handle.drain_into(&mut tree), 0, "nothing left to deliver");
201 }
202
203 #[test]
204 fn purge_window_drops_only_that_windows_completions() {
205 let handle = AsyncCompletionHandle::new();
206 let win = TeksiloWindowId::new(7);
207 handle.register(win, Box::new(|_ctx| {}));
208 handle.register(win, Box::new(|_ctx| {}));
209 handle.register(TeksiloWindowId::new(8), Box::new(|_ctx| {}));
210 assert_eq!(handle.pending_len(), 3);
211
212 handle.purge_window(win);
213 assert_eq!(
214 handle.pending_len(),
215 1,
216 "only the other window's completion survives"
217 );
218 }
219
220 #[test]
221 fn deliver_to_mismatched_window_does_not_run() {
222 let handle = AsyncCompletionHandle::new();
223 let win = TeksiloWindowId::new(3);
224 let ran = Rc::new(Cell::new(false));
225 let flag = ran.clone();
226 let id = handle.register(win, Box::new(move |_ctx| flag.set(true)));
227
228 let mut tree = WidgetTree::new();
229 tree.run_with_event_context(&mut NoopWindowOps, |ctx| {
230 handle.deliver(id, TeksiloWindowId::new(999), ctx)
231 });
232 assert!(
233 !ran.get(),
234 "a window-mismatched delivery must not run the callback"
235 );
236 }
237}