use std::{cell::RefCell, rc::Rc, time::Duration};
use wasm_bindgen::{JsCast, prelude::*};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TimerId(u32);
impl TimerId {
pub fn new(id: u32) -> Self {
Self(id)
}
pub fn as_u32(&self) -> u32 {
self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FrameId(u32);
impl FrameId {
pub fn new(id: u32) -> Self {
Self(id)
}
pub fn as_u32(&self) -> u32 {
self.0
}
}
pub type TimerCallback = Rc<dyn Fn()>;
pub type FrameCallback = Rc<RefCell<dyn FnMut(f64)>>;
#[derive(Clone)]
pub struct TimerManager {
next_id: Rc<RefCell<u32>>,
timers: Rc<RefCell<std::collections::HashMap<TimerId, i32>>>,
animation_frames: Rc<RefCell<std::collections::HashMap<FrameId, i32>>>,
}
impl TimerManager {
pub fn new() -> Self {
Self {
next_id: Rc::new(RefCell::new(0)),
timers: Rc::new(RefCell::new(std::collections::HashMap::new())),
animation_frames: Rc::new(RefCell::new(std::collections::HashMap::new())),
}
}
pub fn set_timeout(&self, callback: TimerCallback, delay: Duration) -> TimerId {
let window = match web_sys::window() {
Some(w) => w,
None => return TimerId(0),
};
let id = {
let mut next_id = self.next_id.borrow_mut();
let id = TimerId(*next_id);
*next_id = next_id.wrapping_add(1);
id
};
let callback_clone = callback.clone();
let callback_js = Closure::wrap(Box::new(move || {
callback_clone();
}) as Box<dyn FnMut()>);
let handle = window.set_timeout_with_callback_and_timeout_and_arguments_0(
callback_js.as_ref().unchecked_ref(),
delay.as_millis() as i32,
);
match handle {
Ok(handle) => {
self.timers.borrow_mut().insert(id, handle);
callback_js.forget();
}
Err(_) => {
callback_js.forget();
}
}
id
}
pub fn clear_timeout(&self, id: TimerId) {
let window = match web_sys::window() {
Some(w) => w,
None => return,
};
if let Some(handle) = self.timers.borrow_mut().remove(&id) {
window.clear_timeout_with_handle(handle);
}
}
pub fn set_interval(&self, callback: TimerCallback, interval: Duration) -> TimerId {
let window = match web_sys::window() {
Some(w) => w,
None => return TimerId(0),
};
let id = {
let mut next_id = self.next_id.borrow_mut();
let id = TimerId(*next_id);
*next_id = next_id.wrapping_add(1);
id
};
let callback_js = Closure::wrap(Box::new(move || {
callback();
}) as Box<dyn FnMut()>);
let handle = window.set_interval_with_callback_and_timeout_and_arguments_0(
callback_js.as_ref().unchecked_ref(),
interval.as_millis() as i32,
);
match handle {
Ok(handle) => {
self.timers.borrow_mut().insert(id, handle);
callback_js.forget();
}
Err(_) => {
callback_js.forget();
}
}
id
}
pub fn clear_interval(&self, id: TimerId) {
self.clear_timeout(id); }
pub fn clear_all(&self) {
let window = match web_sys::window() {
Some(w) => w,
None => return,
};
let mut timers = self.timers.borrow_mut();
for (_id, handle) in timers.iter() {
window.clear_timeout_with_handle(*handle);
}
timers.clear();
}
pub fn request_animation_frame(&self, callback: FrameCallback) -> FrameId {
let window = match web_sys::window() {
Some(w) => w,
None => return FrameId(0),
};
let id = {
let mut next_id = self.next_id.borrow_mut();
let id = FrameId(*next_id);
*next_id = next_id.wrapping_add(1);
id
};
let callback_clone = callback.clone();
let callback_js = Closure::wrap(Box::new(move |timestamp: f64| {
if let Ok(mut cb) = callback_clone.try_borrow_mut() {
cb(timestamp);
}
}) as Box<dyn FnMut(f64)>);
let handle = window.request_animation_frame(callback_js.as_ref().unchecked_ref());
match handle {
Ok(handle) => {
let mut frames = self.animation_frames.borrow_mut();
frames.insert(id, handle);
callback_js.forget();
}
Err(_) => {
callback_js.forget();
}
}
id
}
pub fn cancel_animation_frame(&self, id: FrameId) {
let window = match web_sys::window() {
Some(w) => w,
None => return,
};
if let Some(handle) = self.animation_frames.borrow_mut().remove(&id) {
let _ = window.cancel_animation_frame(handle);
}
}
pub fn clear_all_animation_frames(&self) {
let window = match web_sys::window() {
Some(w) => w,
None => return,
};
let mut frames = self.animation_frames.borrow_mut();
for (_id, handle) in frames.iter() {
let _ = window.cancel_animation_frame(*handle);
}
frames.clear();
}
}
impl Default for TimerManager {
fn default() -> Self {
Self::new()
}
}
pub fn debounce(
manager: TimerManager,
delay: Duration,
callback: TimerCallback,
) -> Rc<RefCell<dyn Fn()>> {
struct DebounceState {
timer_id: Option<TimerId>,
}
let state = Rc::new(RefCell::new(DebounceState { timer_id: None }));
let state_clone = state.clone();
let debounced = Rc::new(RefCell::new(move || {
if let Some(timer_id) = state_clone.borrow_mut().timer_id.take() {
manager.clear_timeout(timer_id);
}
let callback_clone = callback.clone();
let state_for_timer = state.clone();
let timer_id = manager.set_timeout(
Rc::new(move || {
callback_clone();
state_for_timer.borrow_mut().timer_id = None;
}),
delay,
);
state_clone.borrow_mut().timer_id = Some(timer_id);
}));
debounced
}
pub fn throttle(
_manager: TimerManager,
interval: Duration,
callback: TimerCallback,
) -> Rc<RefCell<dyn Fn()>> {
struct ThrottleState {
last_call: Option<f64>,
}
let state = Rc::new(RefCell::new(ThrottleState { last_call: None }));
let throttled = Rc::new(RefCell::new(move || {
let now = js_sys::Date::now();
let mut state_ref = state.borrow_mut();
if let Some(last_time) = state_ref.last_call {
if now - last_time < interval.as_millis() as f64 {
return; }
}
state_ref.last_call = Some(now);
callback();
}));
throttled
}