workflow_core/wasm/
yield_executor.rs

1//!
2//! Implements `async fn yield_executor()` that internally calls
3//! `requestAnimationFrame`
4//!
5
6#![allow(dead_code)]
7#![allow(unused_imports)]
8
9use futures::task::AtomicWaker;
10use std::future::Future;
11use std::{
12    pin::Pin,
13    sync::{
14        atomic::{AtomicBool, Ordering},
15        Arc, Mutex,
16    },
17    task::{Context as FutureContext, Poll},
18};
19use wasm_bindgen::prelude::*;
20use workflow_core::runtime::is_chrome_extension;
21
22#[wasm_bindgen]
23extern "C" {
24    #[wasm_bindgen(js_name = requestAnimationFrame)]
25    fn request_animation_frame(callback: js_sys::Function) -> JsValue;
26    #[wasm_bindgen(js_name = cancelAnimationFrame)]
27    fn cancel_animation_frame(request_id: JsValue);
28}
29
30pub use async_std::task::yield_now;
31
32pub async fn yield_executor() {
33    if !is_chrome_extension() {
34        if !unsafe { REQUEST_ANIMATION_FRAME_INITIALIZED } {
35            init_request_animation_frame_fn();
36            unsafe { REQUEST_ANIMATION_FRAME_INITIALIZED = true };
37        } else {
38            Yield::new().await
39        }
40    }
41}
42
43static mut REQUEST_ANIMATION_FRAME_INITIALIZED: bool = false;
44fn init_request_animation_frame_fn() {
45    if !is_chrome_extension() {
46        let _ = js_sys::Function::new_no_args(
47            "
48            if (!this.requestAnimationFrame){
49                if (this.setImmediate)
50                    this.requestAnimationFrame = (callback)=>setImmediate(callback)
51                else
52                    this.requestAnimationFrame = (callback)=>setTimeout(callback, 0)
53            }
54        ",
55        )
56        .call0(&JsValue::undefined());
57    }
58}
59
60struct Context {
61    #[allow(dead_code)]
62    instance: JsValue,
63    #[allow(dead_code)]
64    closure: JsValue,
65}
66
67unsafe impl Sync for Context {}
68unsafe impl Send for Context {}
69
70struct Inner {
71    ready: AtomicBool,
72    waker: AtomicWaker,
73    ctx: Mutex<Option<Context>>,
74}
75
76/// `Sleep` future used by the `sleep()` function to provide a
77/// timeout future that is backed by the JavaScript `createTimeout()`
78/// and `clearTimeout()` APIs. The `Sleep` future is meant only for
79/// use in WASM32 browser environments.
80#[derive(Clone)]
81pub struct Yield {
82    inner: Arc<Inner>,
83}
84
85impl Default for Yield {
86    fn default() -> Self {
87        Self::new()
88    }
89}
90
91impl Yield {
92    /// Create a new `Sleep` future that will resolve after the given duration.
93    pub fn new() -> Self {
94        let inner = Arc::new(Inner {
95            ready: AtomicBool::new(false),
96            waker: AtomicWaker::new(),
97            ctx: Mutex::new(None),
98        });
99
100        let inner_ = inner.clone();
101        let closure = Closure::once_into_js(move || {
102            inner_.ready.store(true, Ordering::SeqCst);
103            if let Some(waker) = inner_.waker.take() {
104                waker.wake();
105            }
106        });
107
108        let instance = request_animation_frame(closure.clone().into());
109        inner
110            .ctx
111            .lock()
112            .unwrap()
113            .replace(Context { closure, instance });
114
115        Yield { inner }
116    }
117
118    #[inline]
119    fn clear(&self) {
120        if let Some(ctx) = self.inner.ctx.lock().unwrap().take() {
121            cancel_animation_frame(ctx.instance);
122        }
123    }
124
125    /// Cancel the current timeout.
126    pub fn cancel(&self) {
127        self.clear();
128    }
129}
130
131impl Future for Yield {
132    type Output = ();
133
134    fn poll(self: Pin<&mut Self>, cx: &mut FutureContext<'_>) -> Poll<Self::Output> {
135        match self.inner.ready.load(Ordering::SeqCst) {
136            true => {
137                self.inner.ctx.lock().unwrap().take();
138                Poll::Ready(())
139            }
140            false => {
141                self.inner.waker.register(cx.waker());
142                if self.inner.ready.load(Ordering::SeqCst) {
143                    Poll::Ready(())
144                } else {
145                    Poll::Pending
146                }
147            }
148        }
149    }
150}
151
152impl Drop for Yield {
153    fn drop(&mut self) {
154        self.clear();
155    }
156}