workflow_core/wasm/
yield_executor.rs1#![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#[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 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 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}