Skip to content

Commit 10722a7

Browse files
committed
add wasm global
1 parent 17b80a1 commit 10722a7

File tree

4 files changed

+112
-15
lines changed

4 files changed

+112
-15
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ Timeouts for futures.
1616

1717
[target.wasm32-unknown-unknown.dependencies]
1818
instant = { version = "0.1.2", features = ["wasm-bindgen"] }
19+
wasm-bindgen = "0.2.55"
20+
futures = "0.3.1"
21+
web-sys = "0.3.32"
22+
parking_lot = "0.10.0"
1923

2024
[dev-dependencies]
2125
async-std = { version = "1.0.1", features = ["attributes"] }

src/global_wasm.rs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
use futures::task::{self, ArcWake};
2+
use parking_lot::Mutex;
3+
use std::convert::TryFrom;
4+
use std::future::Future;
5+
use std::pin::Pin;
6+
use std::sync::Arc;
7+
use std::task::Context;
8+
use std::time::Duration;
9+
use wasm_bindgen::{JsCast, closure::Closure};
10+
11+
use crate::{Instant, Timer, TimerHandle};
12+
13+
/// Starts a background task, creates a `Timer`, and returns a handle to it.
14+
///
15+
/// > **Note**: Contrary to the original `futures-timer` crate, we don't have
16+
/// > any `forget()` method, as the task is automatically considered
17+
/// > as "forgotten".
18+
pub(crate) fn run() -> TimerHandle {
19+
let timer = Timer::new();
20+
let handle = timer.handle();
21+
schedule_callback(Arc::new(Mutex::new(timer)), Duration::new(0, 0));
22+
handle
23+
}
24+
25+
/// Calls `Window::setTimeout` with the given `Duration`. The callback wakes up the timer and
26+
/// processes everything.
27+
fn schedule_callback(timer: Arc<Mutex<Timer>>, when: Duration) {
28+
let window = web_sys::window().expect("Unable to access Window");
29+
let _ = window.set_timeout_with_callback_and_timeout_and_arguments_0(
30+
&Closure::once_into_js(move || {
31+
let mut timer_lock = timer.lock();
32+
33+
// We start by polling the timer. If any new `Delay` is created, the waker will be used
34+
// to wake up this task pre-emptively. As such, we pass a `Waker` that calls
35+
// `schedule_callback` with a delay of `0`.
36+
let waker = task::waker(Arc::new(Waker { timer: timer.clone() }));
37+
let _ = Future::poll(Pin::new(&mut *timer_lock), &mut Context::from_waker(&waker));
38+
39+
// Notify the timers that are ready.
40+
let now = Instant::now();
41+
timer_lock.advance_to(now);
42+
43+
// Each call to `schedule_callback` calls `schedule_callback` again, but also leaves
44+
// the possibility for `schedule_callback` to be called in parallel. Since we don't
45+
// want too many useless callbacks, we...
46+
// TODO: ugh, that's a hack
47+
if Arc::strong_count(&timer) > 20 {
48+
return;
49+
}
50+
51+
// We call `schedule_callback` again for the next event.
52+
let sleep_dur = timer_lock.next_event()
53+
.map(|next_event| {
54+
if next_event > now {
55+
next_event - now
56+
} else {
57+
Duration::new(0, 0)
58+
}
59+
})
60+
.unwrap_or(Duration::from_secs(5));
61+
drop(timer_lock);
62+
schedule_callback(timer, sleep_dur);
63+
64+
}).unchecked_ref(),
65+
i32::try_from(when.as_millis()).unwrap_or(0)
66+
).unwrap();
67+
}
68+
69+
struct Waker {
70+
timer: Arc<Mutex<Timer>>,
71+
}
72+
73+
impl ArcWake for Waker {
74+
fn wake_by_ref(arc_self: &Arc<Self>) {
75+
schedule_callback(arc_self.timer.clone(), Duration::new(0, 0));
76+
}
77+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ mod global;
2323
mod heap;
2424
mod heap_timer;
2525
mod timer;
26+
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
27+
mod global_wasm;
2628

2729
use arc_list::{ArcList, Node};
2830
use atomic_waker::AtomicWaker;

src/timer.rs

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -277,23 +277,37 @@ impl Default for TimerHandle {
277277
// handle which will return errors when timer objects are attempted to
278278
// be associated.
279279
if fallback == 0 {
280-
let helper = match global::HelperThread::new() {
281-
Ok(helper) => helper,
282-
Err(_) => return TimerHandle { inner: Weak::new() },
283-
};
280+
#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
281+
{
282+
let helper = match global::HelperThread::new() {
283+
Ok(helper) => helper,
284+
Err(_) => return TimerHandle { inner: Weak::new() },
285+
};
286+
287+
288+
// If we successfully set ourselves as the actual fallback then we
289+
// want to `forget` the helper thread to ensure that it persists
290+
// globally. If we fail to set ourselves as the fallback that means
291+
// that someone was racing with this call to
292+
// `TimerHandle::default`. They ended up winning so we'll destroy
293+
// our helper thread (which shuts down the thread) and reload the
294+
// fallback.
295+
if helper.handle().set_as_global_fallback().is_ok() {
296+
let ret = helper.handle();
297+
helper.forget();
298+
return ret;
299+
}
300+
}
284301

285-
// If we successfully set ourselves as the actual fallback then we
286-
// want to `forget` the helper thread to ensure that it persists
287-
// globally. If we fail to set ourselves as the fallback that means
288-
// that someone was racing with this call to
289-
// `TimerHandle::default`. They ended up winning so we'll destroy
290-
// our helper thread (which shuts down the thread) and reload the
291-
// fallback.
292-
if helper.handle().set_as_global_fallback().is_ok() {
293-
let ret = helper.handle();
294-
helper.forget();
295-
return ret;
302+
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
303+
{
304+
let handle = crate::global_wasm::run();
305+
306+
if handle.clone().set_as_global_fallback().is_ok() {
307+
return handle;
308+
}
296309
}
310+
297311
fallback = HANDLE_FALLBACK.load(SeqCst);
298312
}
299313

0 commit comments

Comments
 (0)