use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use crate::widget::EventContext;
use crate::window::TeksiloWindowId;
type CompletionCallback = Box<dyn FnOnce(&mut EventContext)>;
struct Pending {
window_id: TeksiloWindowId,
callback: CompletionCallback,
}
struct CompletionState {
next_id: u64,
pending: HashMap<u64, Pending>,
}
#[derive(Clone)]
pub struct AsyncCompletionHandle {
inner: Rc<RefCell<CompletionState>>,
}
impl Default for AsyncCompletionHandle {
fn default() -> Self {
Self::new()
}
}
impl AsyncCompletionHandle {
pub fn new() -> Self {
Self {
inner: Rc::new(RefCell::new(CompletionState {
next_id: 0,
pending: HashMap::new(),
})),
}
}
pub fn register(&self, window_id: TeksiloWindowId, callback: CompletionCallback) -> u64 {
let mut state = self.inner.borrow_mut();
let id = state.next_id;
state.next_id = state.next_id.wrapping_add(1);
state.pending.insert(
id,
Pending {
window_id,
callback,
},
);
id
}
pub fn deliver(&self, id: u64, window_id: TeksiloWindowId, ctx: &mut EventContext) {
let entry = self.inner.borrow_mut().pending.remove(&id);
if let Some(pending) = entry
&& pending.window_id == window_id
{
(pending.callback)(ctx);
}
}
pub fn purge_window(&self, window_id: TeksiloWindowId) {
self.inner
.borrow_mut()
.pending
.retain(|_, p| p.window_id != window_id);
}
pub fn pending_len(&self) -> usize {
self.inner.borrow().pending.len()
}
}
#[derive(Debug, Clone, Copy)]
pub struct AsyncCompletionPayload {
pub id: u64,
pub window_id: TeksiloWindowId,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::widget_tree::WidgetTree;
use crate::window::NoopWindowOps;
use std::cell::Cell;
use std::rc::Rc;
#[test]
fn deliver_runs_callback_with_fresh_context() {
let handle = AsyncCompletionHandle::new();
let win = TeksiloWindowId::new(1);
let ran = Rc::new(Cell::new(false));
let flag = ran.clone();
let id = handle.register(win, Box::new(move |_ctx| flag.set(true)));
assert_eq!(handle.pending_len(), 1);
let mut tree = WidgetTree::new();
tree.run_with_event_context(&mut NoopWindowOps, |ctx| handle.deliver(id, win, ctx));
assert!(ran.get(), "callback must run with the fresh context");
assert_eq!(
handle.pending_len(),
0,
"delivered completion must be removed"
);
}
#[test]
fn purge_window_drops_only_that_windows_completions() {
let handle = AsyncCompletionHandle::new();
let win = TeksiloWindowId::new(7);
handle.register(win, Box::new(|_ctx| {}));
handle.register(win, Box::new(|_ctx| {}));
handle.register(TeksiloWindowId::new(8), Box::new(|_ctx| {}));
assert_eq!(handle.pending_len(), 3);
handle.purge_window(win);
assert_eq!(
handle.pending_len(),
1,
"only the other window's completion survives"
);
}
#[test]
fn deliver_to_mismatched_window_does_not_run() {
let handle = AsyncCompletionHandle::new();
let win = TeksiloWindowId::new(3);
let ran = Rc::new(Cell::new(false));
let flag = ran.clone();
let id = handle.register(win, Box::new(move |_ctx| flag.set(true)));
let mut tree = WidgetTree::new();
tree.run_with_event_context(&mut NoopWindowOps, |ctx| {
handle.deliver(id, TeksiloWindowId::new(999), ctx)
});
assert!(
!ran.get(),
"a window-mismatched delivery must not run the callback"
);
}
}