1use std::{
2 cell::Cell,
3 future::Future,
4 pin::Pin,
5 rc::Rc,
6 task::{Context, Poll},
7 time::Duration,
8};
9
10use wasm_bindgen::{prelude::Closure, JsCast};
11
12#[derive(Debug)]
25#[pin_project::pin_project(PinnedDrop)]
26pub struct Delay {
27 inner: Duration,
28 closure: Option<Closure<dyn FnMut()>>,
29 timeout_id: Option<i32>,
30 awoken: Rc<Cell<bool>>,
31}
32
33impl Future for Delay {
34 type Output = ();
35
36 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
37 let this = self.project();
38
39 if !this.awoken.get() {
40 if this.closure.is_none() {
41 let awoken = this.awoken.clone();
42 let callback_ref = this.closure.get_or_insert_with(move || {
43 let waker = cx.waker().clone();
44 let wake = Box::new(move || {
45 waker.wake_by_ref();
46 awoken.set(true);
47 });
48
49 Closure::wrap(wake as _)
50 });
51
52 let global: web_sys::WorkerGlobalScope = js_sys::global().unchecked_into();
54 let timeout_id = global
55 .set_timeout_with_callback_and_timeout_and_arguments_0(
56 callback_ref.as_ref().unchecked_ref::<js_sys::Function>(),
57 this.inner.as_millis() as i32,
58 )
59 .unwrap();
60 *this.timeout_id = Some(timeout_id);
61 }
62
63 Poll::Pending
64 } else {
65 Poll::Ready(())
66 }
67 }
68}
69
70impl From<Duration> for Delay {
71 fn from(inner: Duration) -> Self {
72 Self {
73 inner,
74 closure: None,
75 timeout_id: None,
76 awoken: Rc::new(Cell::default()),
77 }
78 }
79}
80
81#[pin_project::pinned_drop]
85impl PinnedDrop for Delay {
86 fn drop(self: Pin<&'_ mut Self>) {
87 let this = self.project();
88
89 if this.awoken.get() {
91 return;
92 }
93
94 if let Some(id) = this.timeout_id {
95 crate::console_debug!("{:#?} has been dropped", &this.inner);
96 let global: web_sys::WorkerGlobalScope = js_sys::global().unchecked_into();
97 global.clear_timeout_with_handle(*id);
98 }
99 }
100}