teksilo_core/
async_completion.rs1use std::cell::RefCell;
25use std::collections::HashMap;
26use std::rc::Rc;
27
28use crate::widget::EventContext;
29use crate::window::TeksiloWindowId;
30
31type CompletionCallback = Box<dyn FnOnce(&mut EventContext)>;
32
33struct Pending {
34 window_id: TeksiloWindowId,
35 callback: CompletionCallback,
36}
37
38struct CompletionState {
39 next_id: u64,
40 pending: HashMap<u64, Pending>,
41}
42
43#[derive(Clone)]
48pub struct AsyncCompletionHandle {
49 inner: Rc<RefCell<CompletionState>>,
50}
51
52impl Default for AsyncCompletionHandle {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl AsyncCompletionHandle {
59 pub fn new() -> Self {
60 Self {
61 inner: Rc::new(RefCell::new(CompletionState {
62 next_id: 0,
63 pending: HashMap::new(),
64 })),
65 }
66 }
67
68 pub fn register(&self, window_id: TeksiloWindowId, callback: CompletionCallback) -> u64 {
72 let mut state = self.inner.borrow_mut();
73 let id = state.next_id;
74 state.next_id = state.next_id.wrapping_add(1);
75 state.pending.insert(
76 id,
77 Pending {
78 window_id,
79 callback,
80 },
81 );
82 id
83 }
84
85 pub fn deliver(&self, id: u64, window_id: TeksiloWindowId, ctx: &mut EventContext) {
91 let entry = self.inner.borrow_mut().pending.remove(&id);
92 if let Some(pending) = entry
93 && pending.window_id == window_id
94 {
95 (pending.callback)(ctx);
96 }
97 }
98
99 pub fn purge_window(&self, window_id: TeksiloWindowId) {
103 self.inner
104 .borrow_mut()
105 .pending
106 .retain(|_, p| p.window_id != window_id);
107 }
108
109 pub fn pending_len(&self) -> usize {
111 self.inner.borrow().pending.len()
112 }
113}
114
115#[derive(Debug, Clone, Copy)]
120pub struct AsyncCompletionPayload {
121 pub id: u64,
122 pub window_id: TeksiloWindowId,
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use crate::widget_tree::WidgetTree;
129 use crate::window::NoopWindowOps;
130 use std::cell::Cell;
131 use std::rc::Rc;
132
133 #[test]
134 fn deliver_runs_callback_with_fresh_context() {
135 let handle = AsyncCompletionHandle::new();
136 let win = TeksiloWindowId::new(1);
137 let ran = Rc::new(Cell::new(false));
138 let flag = ran.clone();
139 let id = handle.register(win, Box::new(move |_ctx| flag.set(true)));
140 assert_eq!(handle.pending_len(), 1);
141
142 let mut tree = WidgetTree::new();
143 tree.run_with_event_context(&mut NoopWindowOps, |ctx| handle.deliver(id, win, ctx));
144
145 assert!(ran.get(), "callback must run with the fresh context");
146 assert_eq!(
147 handle.pending_len(),
148 0,
149 "delivered completion must be removed"
150 );
151 }
152
153 #[test]
154 fn purge_window_drops_only_that_windows_completions() {
155 let handle = AsyncCompletionHandle::new();
156 let win = TeksiloWindowId::new(7);
157 handle.register(win, Box::new(|_ctx| {}));
158 handle.register(win, Box::new(|_ctx| {}));
159 handle.register(TeksiloWindowId::new(8), Box::new(|_ctx| {}));
160 assert_eq!(handle.pending_len(), 3);
161
162 handle.purge_window(win);
163 assert_eq!(
164 handle.pending_len(),
165 1,
166 "only the other window's completion survives"
167 );
168 }
169
170 #[test]
171 fn deliver_to_mismatched_window_does_not_run() {
172 let handle = AsyncCompletionHandle::new();
173 let win = TeksiloWindowId::new(3);
174 let ran = Rc::new(Cell::new(false));
175 let flag = ran.clone();
176 let id = handle.register(win, Box::new(move |_ctx| flag.set(true)));
177
178 let mut tree = WidgetTree::new();
179 tree.run_with_event_context(&mut NoopWindowOps, |ctx| {
180 handle.deliver(id, TeksiloWindowId::new(999), ctx)
181 });
182 assert!(
183 !ran.get(),
184 "a window-mismatched delivery must not run the callback"
185 );
186 }
187}