use futures::task::{self, ArcWake};
use parking_lot::Mutex;
use std::convert::TryFrom;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::task::Context;
use std::time::Duration;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::WorkerGlobalScope;
use crate::{Instant, Timer, TimerHandle};
pub(crate) fn run() -> TimerHandle {
let timer = Timer::new();
let handle = timer.handle();
schedule_callback(Arc::new(Mutex::new(timer)), Duration::new(0, 0));
handle
}
fn schedule_callback(timer: Arc<Mutex<Timer>>, when: Duration) {
let callback = Closure::once_into_js(move || {
let mut timer_lock = timer.lock();
let waker = task::waker(Arc::new(Waker {
timer: timer.clone(),
}));
let _ = Future::poll(Pin::new(&mut *timer_lock), &mut Context::from_waker(&waker));
let now = Instant::now();
timer_lock.advance_to(now);
if Arc::strong_count(&timer) > 20 {
return;
}
let sleep_dur = timer_lock
.next_event()
.map(|next_event| {
if next_event > now {
next_event - now
} else {
Duration::new(0, 0)
}
})
.unwrap_or(Duration::from_secs(5));
drop(timer_lock);
schedule_callback(timer, sleep_dur);
});
if let Some(window) = web_sys::window() {
let _ = window
.set_timeout_with_callback_and_timeout_and_arguments_0(
callback.unchecked_ref(),
i32::try_from(when.as_millis()).unwrap_or(0),
)
.unwrap();
} else {
let _ = js_sys::global()
.dyn_into::<WorkerGlobalScope>()
.expect("no supported global object available")
.set_timeout_with_callback_and_timeout_and_arguments_0(
callback.unchecked_ref(),
i32::try_from(when.as_millis()).unwrap_or(0),
)
.unwrap();
}
}
struct Waker {
timer: Arc<Mutex<Timer>>,
}
impl ArcWake for Waker {
fn wake_by_ref(arc_self: &Arc<Self>) {
schedule_callback(arc_self.timer.clone(), Duration::new(0, 0));
}
}