1 //!
2 //! The polyfill was kindly borrowed from https://github.com/tc39/proposal-atomics-wait-async
3 //! and ported to Rust
4 //!
5 
6 /* This Source Code Form is subject to the terms of the Mozilla Public
7  * License, v. 2.0. If a copy of the MPL was not distributed with this
8  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Author: Lars T Hansen, lhansen@mozilla.com
11  */
12 
13 /* Polyfill for Atomics.waitAsync() for web browsers.
14  *
15  * Any kind of agent that is able to create a new Worker can use this polyfill.
16  *
17  * Load this file in all agents that will use Atomics.waitAsync.
18  *
19  * Agents that don't call Atomics.waitAsync need do nothing special.
20  *
21  * Any kind of agent can wake another agent that is sleeping in
22  * Atomics.waitAsync by just calling Atomics.wake for the location being slept
23  * on, as normal.
24  *
25  * The implementation is not completely faithful to the proposed semantics: in
26  * the case where an agent first asyncWaits and then waits on the same location:
27  * when it is woken, the two waits will be woken in order, while in the real
28  * semantics, the sync wait will be woken first.
29  *
30  * In this polyfill Atomics.waitAsync is not very fast.
31  */
32 
33 /* Implementation:
34  *
35  * For every wait we fork off a Worker to perform the wait.  Workers are reused
36  * when possible.  The worker communicates with its parent using postMessage.
37  */
38 
39 use js_sys::{encode_uri_component, Array, Promise};
40 use std::cell::RefCell;
41 use std::sync::atomic::AtomicI32;
42 use wasm_bindgen::prelude::*;
43 use wasm_bindgen::JsCast;
44 use web_sys::{MessageEvent, Worker};
45 
46 const HELPER_CODE: &'static str = "
47 onmessage = function (ev) {
48     let [ia, index, value] = ev.data;
49     ia = new Int32Array(ia.buffer);
50     let result = Atomics.wait(ia, index, value);
51     postMessage(result);
52 };
53 ";
54 
55 thread_local! {
56     static HELPERS: RefCell<Vec<Worker>> = RefCell::new(vec![]);
57 }
58 
alloc_helper() -> Worker59 fn alloc_helper() -> Worker {
60     HELPERS.with(|helpers| {
61         if let Some(helper) = helpers.borrow_mut().pop() {
62             return helper;
63         }
64 
65         let mut initialization_string = "data:application/javascript,".to_owned();
66         let encoded: String = encode_uri_component(HELPER_CODE).into();
67         initialization_string.push_str(&encoded);
68 
69         Worker::new(&initialization_string).unwrap_or_else(|js| wasm_bindgen::throw_val(js))
70     })
71 }
72 
free_helper(helper: Worker)73 fn free_helper(helper: Worker) {
74     HELPERS.with(move |helpers| {
75         let mut helpers = helpers.borrow_mut();
76         helpers.push(helper.clone());
77         helpers.truncate(10); // random arbitrary limit chosen here
78     });
79 }
80 
wait_async(ptr: &AtomicI32, value: i32) -> Promise81 pub fn wait_async(ptr: &AtomicI32, value: i32) -> Promise {
82     Promise::new(&mut |resolve, _reject| {
83         let helper = alloc_helper();
84         let helper_ref = helper.clone();
85 
86         let onmessage_callback = Closure::once_into_js(move |e: MessageEvent| {
87             // Our helper is done waiting so it's available to wait on a
88             // different location, so return it to the free list.
89             free_helper(helper_ref);
90             drop(resolve.call1(&JsValue::NULL, &e.data()));
91         });
92         helper.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
93 
94         let data = Array::of3(
95             &wasm_bindgen::memory(),
96             &JsValue::from(ptr as *const AtomicI32 as i32 / 4),
97             &JsValue::from(value),
98         );
99 
100         helper
101             .post_message(&data)
102             .unwrap_or_else(|js| wasm_bindgen::throw_val(js));
103     })
104 }
105