use futures_channel::oneshot::{channel, Receiver};
use futures_core::Stream;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::IdleDeadline;
use web_sys::IdleRequestOptions;
use std::fmt::{self, Debug};
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use std::time::Duration;
pub struct Idle {
options: Option<IdleRequestOptions>,
receiver: Option<Receiver<IdleDeadline>>,
f: Option<Closure<dyn std::ops::FnMut(IdleDeadline)>>,
id: Option<u32>,
}
impl Idle {
pub fn new() -> Self {
Self {
options: None,
receiver: None,
id: None,
f: None,
}
}
pub fn with_deadline(deadline: Duration) -> Self {
let mut options = IdleRequestOptions::new();
options.timeout(deadline.as_millis() as u32);
Self {
options: Some(options),
receiver: None,
id: None,
f: None,
}
}
}
impl Stream for Idle {
type Item = Deadline;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.receiver.is_none() {
let window = crate::utils::window();
let (sender, receiver) = channel();
let f = Closure::once(move |deadline| sender.send(deadline).unwrap_throw());
let id = match &self.options {
Some(options) => {
let callback = &f.as_ref().unchecked_ref();
window.request_idle_callback_with_options(callback, options)
}
None => {
let callback = &f.as_ref().unchecked_ref();
window.request_idle_callback(callback)
}
}
.unwrap_throw();
self.f = Some(f);
self.id = Some(id);
self.receiver = Some(receiver);
}
match Pin::new(self.receiver.as_mut().unwrap()).poll(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Ok(deadline)) => {
self.receiver = None;
self.f = None;
self.id = None;
let deadline = Deadline { inner: deadline };
Poll::Ready(Some(deadline))
}
Poll::Ready(Err(_)) => panic!("error in Idle"),
}
}
}
impl Default for Idle {
fn default() -> Self {
Self::new()
}
}
impl Debug for Idle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Idle").finish()
}
}
impl Drop for Idle {
fn drop(&mut self) {
if let Some(id) = self.id {
crate::utils::window().cancel_idle_callback(id);
}
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Deadline {
inner: IdleDeadline,
}
impl Deadline {
pub fn time_remaining(&self) -> Duration {
let remaining = self.inner.time_remaining() * 1000.0;
Duration::from_secs_f64(remaining)
}
pub fn did_timeout(&self) -> bool {
self.inner.did_timeout()
}
}