use std::{time::Duration, ops::{Add, AddAssign, Sub, SubAssign}, fmt::Debug};
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn setTimeout(closure: &Closure<dyn FnMut()>, millis: u32) -> f64;
fn clearTimeout(token: i32);
}
#[wasm_bindgen(module = "browser.js")]
extern "C" {
#[wasm_bindgen(js_name = waitInJSPromise)]
fn wait_in_js_promise(ms: f64) -> js_sys::Promise;
pub type JSTicker;
#[wasm_bindgen(constructor)]
fn new(for_animation: bool, ms: f64) -> JSTicker;
#[wasm_bindgen(method, js_name = tickInJSPromise)]
fn tick_in_js_promise(this: &JSTicker) -> js_sys::Promise;
}
impl Debug for JSTicker {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("JSTicker()")
}
}
impl JSTicker {
async fn tick_in_future(&self) -> Duration {
let delta = wasm_bindgen_futures::JsFuture::from(self.tick_in_js_promise()).await;
Duration::from_millis(unsafe { delta.unwrap().as_f64().unwrap().to_int_unchecked::<u64>() })
}
}
pub async fn wait(duration: Duration) {
let ms: u32 = duration.as_millis().try_into().expect("Developer has given too large period for wait duration");
wasm_bindgen_futures::JsFuture::from(wait_in_js_promise(ms.into())).await.unwrap();
}
pub async fn wait_until(instant: crate::timer::Instant) {
wait(instant.since(crate::timer::Instant::now())).await;
}
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
pub struct Instant {
epoch_ms: u128,
}
impl Instant {
pub fn since(&self, other: Instant) -> Duration {
*self - other
}
pub fn now() -> Self {
let epoch_ms: u64 = unsafe { js_sys::Date::now().to_int_unchecked() };
Self {
epoch_ms: epoch_ms.into(),
}
}
pub fn try_add(&self, duration: Duration) -> Option<Instant> {
Some(Instant { epoch_ms: self.epoch_ms.checked_add(duration.as_millis())? })
}
pub fn try_subtract(&self, duration: Duration) -> Option<Instant> {
Some(Instant { epoch_ms: self.epoch_ms.checked_sub(duration.as_millis())? })
}
}
impl Add<Duration> for Instant {
type Output = Instant;
fn add(self, rhs: Duration) -> Self::Output {
Instant { epoch_ms: self.epoch_ms.checked_add(rhs.as_millis()).expect("Overflow when adding duration to instant") }
}
}
impl AddAssign<Duration> for Instant {
fn add_assign(&mut self, rhs: Duration) {
self.epoch_ms = self.epoch_ms.checked_add(rhs.as_millis()).expect("Overflow when adding duration to instant");
}
}
impl Sub<Duration> for Instant {
type Output = Instant;
fn sub(self, rhs: Duration) -> Self::Output {
Instant { epoch_ms: self.epoch_ms.checked_sub(rhs.as_millis()).expect("Overflow when subtracting duration from instant") }
}
}
impl Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, rhs: Instant) -> Self::Output {
Duration::from_millis(if self.epoch_ms < rhs.epoch_ms { 0 } else { (self.epoch_ms - rhs.epoch_ms).try_into().unwrap_or(u64::MAX) })
}
}
impl SubAssign<Duration> for Instant {
fn sub_assign(&mut self, rhs: Duration) {
self.epoch_ms = self.epoch_ms.checked_sub(rhs.as_millis()).expect("Overflow when subtracting duration from instant");
}
}
#[derive(Debug)]
pub struct Ticker {
pub for_animation: bool,
pub period: Duration,
pub start: crate::timer::Instant,
pub ticker: Option<JSTicker>,
}
impl Ticker {
pub async fn tick(&mut self) -> Duration {
match self.ticker.as_ref() {
Some(ticker) => ticker.tick_in_future().await,
None => {
wait_until(self.start).await;
let ms: u32 = self.period.as_millis().try_into().expect("Developer has given too large period for interval");
self.ticker = Some(JSTicker::new(self.for_animation, ms.into()));
return Duration::from_millis(0);
},
}
}
}