Menu
Public repository / master
AkurAI-Framework / crates/ui/src/callbacks.rs
Latest change 2ebaebff3945c75127afb6a9524ac1192993158b - akurai-ui: EventSource bridge (http::event_source, stream callbacks, host.js + native fake, testing helpers) by Ólafur Búi Ólafsson
//! The callback registry: Rust closures addressed by `u32` ids so the host can
//! call back into them (DOM events, fetch completions, timers).
use crate::dom::Event;
use crate::host::EventRef;
use crate::reactive::batch;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
type Completion = Box<dyn FnOnce(u16, String)>;
enum Callback {
Event(Box<dyn FnMut(Event)>),
Once(Completion),
/// A stream (e.g. an `EventSource`): called for every message until removed.
Stream(Box<dyn FnMut(u16, String)>),
}
thread_local! {
// `None` marks a callback that is currently executing (taken out).
static REGISTRY: RefCell<HashMap<u32, Option<Callback>>> = RefCell::new(HashMap::new());
static NEXT: Cell<u32> = const { Cell::new(1) };
}
fn insert(cb: Callback) -> u32 {
let id = NEXT.with(|n| {
let id = n.get();
n.set(id + 1);
id
});
REGISTRY.with(|r| r.borrow_mut().insert(id, Some(cb)));
id
}
/// Register a repeating event handler.
pub(crate) fn on_event(f: impl FnMut(Event) + 'static) -> u32 {
insert(Callback::Event(Box::new(f)))
}
/// Register a one-shot completion (`status`, `body`).
pub(crate) fn once(f: impl FnOnce(u16, String) + 'static) -> u32 {
insert(Callback::Once(Box::new(f)))
}
/// Register a repeating `(status, body)` stream handler.
pub(crate) fn stream(f: impl FnMut(u16, String) + 'static) -> u32 {
insert(Callback::Stream(Box::new(f)))
}
/// Forget a callback. Safe to call while it is executing.
pub(crate) fn remove(id: u32) {
REGISTRY.with(|r| r.borrow_mut().remove(&id));
}
/// Number of live callbacks (leak checks in tests).
pub fn live() -> usize {
REGISTRY.with(|r| r.borrow().len())
}
/// Run an event handler. Writes inside it are batched into one flush.
pub(crate) fn dispatch(id: u32, ev: EventRef) {
let taken = REGISTRY.with(|r| r.borrow_mut().get_mut(&id).and_then(Option::take));
let Some(Callback::Event(mut f)) = taken else {
return;
};
batch(|| f(Event(ev)));
REGISTRY.with(|r| {
if let Some(slot) = r.borrow_mut().get_mut(&id) {
*slot = Some(Callback::Event(f));
}
});
}
/// Run a completion: a one-shot is dropped afterwards, a stream is kept.
pub(crate) fn complete(id: u32, status: u16, body: String) {
let is_stream = REGISTRY.with(|r| {
matches!(
r.borrow().get(&id),
Some(Some(Callback::Stream(_))) | Some(None)
)
});
if is_stream {
let taken = REGISTRY.with(|r| r.borrow_mut().get_mut(&id).and_then(Option::take));
let Some(Callback::Stream(mut f)) = taken else {
return;
};
batch(|| f(status, body));
REGISTRY.with(|r| {
if let Some(slot) = r.borrow_mut().get_mut(&id) {
*slot = Some(Callback::Stream(f));
}
});
return;
}
let taken = REGISTRY.with(|r| r.borrow_mut().remove(&id).flatten());
if let Some(Callback::Once(f)) = taken {
batch(|| f(status, body));
}
}