use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use web_time::{Duration, Instant};
use crate::{Signal, reactive, request_frame, signal};
thread_local! {
static REGISTRY: RefCell<HashMap<usize, Entry>> = RefCell::new(HashMap::new());
static NEXT_ID: RefCell<usize> = const { RefCell::new(1usize) };
}
struct Entry {
deadline: Instant,
callback: Rc<dyn Fn()>,
}
fn next_id() -> usize {
NEXT_ID.with(|c| {
let mut v = c.borrow_mut();
let id = *v;
*v += 1;
id
})
}
pub fn next_deadline() -> Option<Instant> {
REGISTRY.with(|r| r.borrow().values().map(|e| e.deadline).min())
}
pub fn poll() {
let now = Instant::now();
let due: Vec<Rc<dyn Fn()>> = REGISTRY.with(|r| {
let mut reg = r.borrow_mut();
let mut due = Vec::new();
let mut to_remove = Vec::new();
for (id, entry) in reg.iter() {
if entry.deadline <= now {
due.push(entry.callback.clone());
to_remove.push(*id);
}
}
for id in to_remove {
reg.remove(&id);
}
due
});
for cb in due {
cb();
}
}
fn schedule(id: usize, deadline: Instant, cb: Rc<dyn Fn()>) {
REGISTRY.with(|r| {
r.borrow_mut().insert(
id,
Entry {
deadline,
callback: cb,
},
);
});
request_frame();
}
fn cancel(id: usize) {
REGISTRY.with(|r| {
r.borrow_mut().remove(&id);
});
}
pub fn debounced_signal<T>(source: Signal<T>, delay: Duration) -> Signal<T>
where
T: Clone + PartialEq + 'static,
{
let out = signal(source.get());
let out_clone = out.clone();
let pending: Rc<RefCell<Option<T>>> = Rc::new(RefCell::new(None));
let delay_c = delay;
let id = next_id();
let obs_id = reactive::new_observer({
let source = source.clone();
let out_clone2 = out_clone.clone();
let pending2 = pending.clone();
move || {
let v = source.get();
*pending2.borrow_mut() = Some(v.clone());
let deadline = Instant::now() + delay_c;
let out_c = out_clone2.clone();
let pending_c = pending2.clone();
let cb: Rc<dyn Fn()> = Rc::new(move || {
if let Some(val) = pending_c.borrow_mut().take() {
out_c.set_neq(val);
}
});
schedule(id, deadline, cb);
}
});
reactive::run_observer_now(obs_id);
crate::scoped_effect(move || {
crate::on_unmount(move || {
reactive::remove_observer(obs_id);
cancel(id);
})
});
out
}
pub fn debounced_signal_with_key<T>(
key: impl Into<String>,
source: Signal<T>,
delay: Duration,
) -> Signal<T>
where
T: Clone + PartialEq + 'static,
{
let key = key.into();
crate::remember_with_key(key, || debounced_signal(source, delay))
.as_ref()
.clone()
}