1 | use js_sys::Promise; |
2 | use std::cell::{Cell, RefCell}; |
3 | use std::collections::VecDeque; |
4 | use std::rc::Rc; |
5 | use wasm_bindgen::prelude::*; |
6 | |
7 | #[wasm_bindgen ] |
8 | extern "C" { |
9 | #[wasm_bindgen ] |
10 | fn queueMicrotask(closure: &Closure<dyn FnMut(JsValue)>); |
11 | |
12 | type Global; |
13 | |
14 | #[wasm_bindgen (method, getter, js_name = queueMicrotask)] |
15 | fn hasQueueMicrotask(this: &Global) -> JsValue; |
16 | } |
17 | |
18 | struct QueueState { |
19 | // The queue of Tasks which are to be run in order. In practice this is all the |
20 | // synchronous work of futures, and each `Task` represents calling `poll` on |
21 | // a future "at the right time". |
22 | tasks: RefCell<VecDeque<Rc<crate::task::Task>>>, |
23 | |
24 | // This flag indicates whether we've scheduled `run_all` to run in the future. |
25 | // This is used to ensure that it's only scheduled once. |
26 | is_scheduled: Cell<bool>, |
27 | } |
28 | |
29 | impl QueueState { |
30 | fn run_all(&self) { |
31 | // "consume" the schedule |
32 | let _was_scheduled: bool = self.is_scheduled.replace(val:false); |
33 | debug_assert!(_was_scheduled); |
34 | |
35 | // Stop when all tasks that have been scheduled before this tick have been run. |
36 | // Tasks that are scheduled while running tasks will run on the next tick. |
37 | let mut task_count_left: usize = self.tasks.borrow().len(); |
38 | while task_count_left > 0 { |
39 | task_count_left -= 1; |
40 | let task: Rc = match self.tasks.borrow_mut().pop_front() { |
41 | Some(task: Rc) => task, |
42 | None => break, |
43 | }; |
44 | task.run(); |
45 | } |
46 | |
47 | // All of the Tasks have been run, so it's now possible to schedule the |
48 | // next tick again |
49 | } |
50 | } |
51 | |
52 | pub(crate) struct Queue { |
53 | state: Rc<QueueState>, |
54 | promise: Promise, |
55 | closure: Closure<dyn FnMut(JsValue)>, |
56 | has_queue_microtask: bool, |
57 | } |
58 | |
59 | impl Queue { |
60 | // Schedule a task to run on the next tick |
61 | pub(crate) fn schedule_task(&self, task: Rc<crate::task::Task>) { |
62 | self.state.tasks.borrow_mut().push_back(task); |
63 | // Use queueMicrotask to execute as soon as possible. If it does not exist |
64 | // fall back to the promise resolution |
65 | if !self.state.is_scheduled.replace(val:true) { |
66 | if self.has_queue_microtask { |
67 | queueMicrotask(&self.closure); |
68 | } else { |
69 | let _ = self.promise.then(&self.closure); |
70 | } |
71 | } |
72 | } |
73 | // Append a task to the currently running queue, or schedule it |
74 | pub(crate) fn push_task(&self, task: Rc<crate::task::Task>) { |
75 | // It would make sense to run this task on the same tick. For now, we |
76 | // make the simplifying choice of always scheduling tasks for a future tick. |
77 | self.schedule_task(task) |
78 | } |
79 | } |
80 | |
81 | impl Queue { |
82 | fn new() -> Self { |
83 | let state = Rc::new(QueueState { |
84 | is_scheduled: Cell::new(false), |
85 | tasks: RefCell::new(VecDeque::new()), |
86 | }); |
87 | |
88 | let has_queue_microtask = js_sys::global() |
89 | .unchecked_into::<Global>() |
90 | .hasQueueMicrotask() |
91 | .is_function(); |
92 | |
93 | Self { |
94 | promise: Promise::resolve(&JsValue::undefined()), |
95 | |
96 | closure: { |
97 | let state = Rc::clone(&state); |
98 | |
99 | // This closure will only be called on the next microtask event |
100 | // tick |
101 | Closure::new(move |_| state.run_all()) |
102 | }, |
103 | |
104 | state, |
105 | has_queue_microtask, |
106 | } |
107 | } |
108 | } |
109 | |
110 | thread_local! { |
111 | pub(crate) static QUEUE: Queue = Queue::new(); |
112 | } |
113 | |