1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Provides a scheduled loop in the browser via `requestAnimationFrame`.

#![deny(missing_docs)]

use {
    futures::task::{waker, ArcWake},
    std::{
        cell::{Cell, RefCell},
        rc::Rc,
        sync::Arc,
        task::Waker,
    },
    wasm_bindgen::{prelude::*, JsCast},
    web_sys::window,
};

/// A value which can be mutably called by the scheduler.
pub trait Tick: 'static {
    /// Tick this value, indicating a new frame request is being fulfilled.
    fn tick(&mut self);
}

/// A value which can receive a waker from the scheduler that will request a new frame when woken.
pub trait Waking {
    /// Receive a waker from the scheduler that calls `requestAnimationFrame` when woken.
    fn set_waker(&mut self, wk: Waker);
}

/// Owns a `WebRuntime` and schedules its execution using `requestAnimationFrame`.
#[must_use]
pub struct AnimationFrameScheduler<Cb>(Rc<AnimationFrameState<Cb>>);

struct AnimationFrameState<Cb> {
    ticker: RefCell<Cb>,
    handle: Cell<Option<AnimationFrameHandle>>,
}

impl<T: Tick> ArcWake for AnimationFrameScheduler<T> {
    fn wake_by_ref(arc_self: &Arc<AnimationFrameScheduler<T>>) {
        arc_self.ensure_scheduled(false);
    }
}

impl<T: Tick> AnimationFrameScheduler<T> {
    /// Construct a new scheduler with the provided callback. `ticker.tick()` will be called once
    /// per fulfilled animation frame request.
    pub fn new(ticker: T) -> Self {
        AnimationFrameScheduler(Rc::new(AnimationFrameState {
            ticker: RefCell::new(ticker),
            handle: Cell::new(None),
        }))
    }

    fn ensure_scheduled(&self, immediately_again: bool) {
        let existing = self.0.handle.replace(None);
        let handle = existing.unwrap_or_else(|| {
            let self2 = AnimationFrameScheduler(Rc::clone(&self.0));
            let callback = Closure::once(Box::new(move || {
                self2.0.handle.set(None);

                self2.0.ticker.borrow_mut().tick();

                if immediately_again {
                    self2.ensure_scheduled(true);
                }
            }));

            AnimationFrameHandle::request(callback)
        });
        self.0.handle.set(Some(handle));
    }

    /// Consumes the scheduler to initiate a `requestAnimationFrame` callback loop where new
    /// animation frames are requested immmediately after the last `moxie::Revision` is completed.
    /// `WebRuntime::run_once` is called once per requested animation frame.
    pub fn run_on_every_frame(self) {
        self.ensure_scheduled(true);
    }
}

impl<T: Tick + Waking> AnimationFrameScheduler<T> {
    /// Consumes the scheduler to initiate a `requestAnimationFrame` callback loop where new
    /// animation frames are requested whenever the waker passed to the provided closure is woken.
    pub fn run_on_wake(self) {
        let state = Rc::clone(&self.0);
        let waker = waker(Arc::new(self));
        {
            // ensure we've released our mutable borrow by running it in a separate block
            state.ticker.borrow_mut().set_waker(waker.clone());
        }
        waker.wake_by_ref();
    }
}

// don't send these to workers until have a fix :P
unsafe impl<Cb> Send for AnimationFrameScheduler<Cb> {}
unsafe impl<Cb> Sync for AnimationFrameScheduler<Cb> {}

struct AnimationFrameHandle {
    raw: i32,
    /// Prefixed with an underscore because it is only read by JS, otherwise we'll get a warning.
    _callback: Closure<dyn FnMut()>,
}

impl AnimationFrameHandle {
    fn request(callback: Closure<dyn FnMut()>) -> Self {
        let raw = window()
            .unwrap()
            .request_animation_frame(callback.as_ref().unchecked_ref())
            .unwrap();

        Self {
            raw,
            _callback: callback,
        }
    }
}

impl Drop for AnimationFrameHandle {
    fn drop(&mut self) {
        window().unwrap().cancel_animation_frame(self.raw).ok();
    }
}