use std::cell::RefCell;
use std::collections::HashMap;
use wasm_bindgen::prelude::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Priority {
Critical = 0,
High = 1,
Normal = 2,
Low = 3,
}
struct ScheduledWork {
key: String,
priority: Priority,
work: Box<dyn FnOnce()>,
}
thread_local! {
static QUEUE: RefCell<Vec<ScheduledWork>> = RefCell::new(vec![]);
static RAF_PENDING: RefCell<bool> = RefCell::new(false);
static COALESCED: RefCell<HashMap<String, u32>> = RefCell::new(HashMap::new());
}
pub fn schedule<F>(key: &str, priority: Priority, work: F)
where F: FnOnce() + 'static
{
QUEUE.with(|q| {
let mut queue = q.borrow_mut();
queue.retain(|w| w.key != key);
queue.push(ScheduledWork {
key: key.to_string(),
priority,
work: Box::new(work),
});
queue.sort_by_key(|w| w.priority);
});
request_flush(priority);
}
pub fn raf<F>(key: &str, work: F)
where F: FnOnce() + 'static
{
schedule(key, Priority::High, work);
}
pub fn batch<F>(key: &str, work: F)
where F: FnOnce() + 'static
{
schedule(key, Priority::Normal, work);
}
fn request_flush(priority: Priority) {
if priority == Priority::Critical {
flush();
return;
}
let already_pending = RAF_PENDING.with(|p| {
if *p.borrow() { return true; }
*p.borrow_mut() = true;
false
});
if already_pending { return; }
let cb = Closure::once(Box::new(|| {
RAF_PENDING.with(|p| *p.borrow_mut() = false);
flush();
}) as Box<dyn FnOnce()>);
if let Some(win) = web_sys::window() {
let _ = win.request_animation_frame(cb.as_ref().unchecked_ref());
}
cb.forget();
}
pub fn flush() {
let work: Vec<ScheduledWork> = QUEUE.with(|q| {
let mut queue = q.borrow_mut();
std::mem::take(&mut *queue)
});
for item in work {
(item.work)();
}
}
pub fn pending_count() -> usize {
QUEUE.with(|q| q.borrow().len())
}