use async_channel::Receiver;
use async_channel::TryRecvError;
use async_channel::unbounded;
use futures_lite::Stream;
use std::cell::RefCell;
use std::pin::Pin;
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::Closure;
use web_sys::window;
struct AnimationFrameInner {
handle: Rc<RefCell<i32>>,
_closure: Rc<RefCell<Option<Closure<dyn FnMut(f64)>>>>,
}
impl Drop for AnimationFrameInner {
fn drop(&mut self) {
let id = *self.handle.borrow();
let _ = window().unwrap().cancel_animation_frame(id);
}
}
pub struct AnimationFrame {
receiver: Receiver<f64>,
_inner: Rc<AnimationFrameInner>,
}
impl Unpin for AnimationFrame {}
impl AnimationFrame {
pub fn new() -> Self {
let (sender, receiver) = unbounded::<f64>();
let handle: Rc<RefCell<i32>> = Rc::new(RefCell::new(0));
let handle_for_closure = handle.clone();
let closure_cell: Rc<RefCell<Option<Closure<dyn FnMut(f64)>>>> =
Rc::new(RefCell::new(None));
let closure_cell_for_init = closure_cell.clone();
let closure_cell_for_closure = closure_cell.clone();
*closure_cell_for_init.borrow_mut() =
Some(Closure::wrap(Box::new(move |timestamp_ms: f64| {
let _ = sender.try_send(timestamp_ms);
let borrow = closure_cell_for_closure.borrow();
let closure_ref = borrow.as_ref().unwrap();
*handle_for_closure.borrow_mut() =
request_animation_frame(closure_ref);
}) as Box<dyn FnMut(f64)>));
{
let closure_ref = closure_cell.borrow();
let closure_ref = closure_ref.as_ref().unwrap();
*handle.borrow_mut() = request_animation_frame(closure_ref);
}
let inner = Rc::new(AnimationFrameInner {
handle,
_closure: closure_cell,
});
Self {
receiver,
_inner: inner,
}
}
pub fn forget(self) { std::mem::forget(self); }
}
impl Stream for AnimationFrame {
type Item = f64;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match this.receiver.try_recv() {
Ok(ts) => return Poll::Ready(Some(ts)),
Err(TryRecvError::Closed) => return Poll::Ready(None),
Err(TryRecvError::Empty) => {}
}
let recv = this.receiver.clone();
let fut = recv.recv();
futures_lite::pin!(fut);
match fut.poll(cx) {
Poll::Ready(Ok(ts)) => Poll::Ready(Some(ts)),
Poll::Ready(Err(_closed)) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
}
}
}
fn request_animation_frame(f: &Closure<dyn FnMut(f64)>) -> i32 {
window()
.unwrap()
.request_animation_frame(f.as_ref().unchecked_ref())
.unwrap()
}
#[cfg(test)]
#[cfg(target_arch = "wasm32")]
mod tests {
use super::AnimationFrame;
use crate::prelude::*;
#[ignore = "requires dom"]
#[test]
fn works() {
let _ = document_ext::document();
let _ = document_ext::head();
let _ = document_ext::body();
}
#[ignore = "requires dom"]
#[crate::test]
async fn yields_timestamps() {
document_ext::clear_body();
let mut raf = AnimationFrame::new();
let a = raf.next().await.unwrap();
let b = raf.next().await.unwrap();
(b > a).xpect_true();
}
}