| 1 | use alloc::collections::VecDeque; |
| 2 | use alloc::rc::Rc; |
| 3 | use core::cell::{Cell, RefCell}; |
| 4 | use js_sys::Promise; |
| 5 | use wasm_bindgen::prelude::*; |
| 6 | |
| 7 | #[wasm_bindgen ] |
| 8 | unsafeextern "C" { |
| 9 | #[wasm_bindgen ] |
| 10 | unsafefn queueMicrotask(closure: &Closure<dyn FnMut(JsValue)>); |
| 11 | |
| 12 | type Global; |
| 13 | |
| 14 | #[wasm_bindgen (method, getter, js_name = queueMicrotask)] |
| 15 | unsafefn 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 | #[cfg (not(target_feature = "atomics" ))] |
| 75 | pub(crate) fn push_task(&self, task: Rc<crate::task::Task>) { |
| 76 | // It would make sense to run this task on the same tick. For now, we |
| 77 | // make the simplifying choice of always scheduling tasks for a future tick. |
| 78 | self.schedule_task(task) |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | impl Queue { |
| 83 | fn new() -> Self { |
| 84 | let state = Rc::new(QueueState { |
| 85 | is_scheduled: Cell::new(false), |
| 86 | tasks: RefCell::new(VecDeque::new()), |
| 87 | }); |
| 88 | |
| 89 | let has_queue_microtask = js_sys::global() |
| 90 | .unchecked_into::<Global>() |
| 91 | .hasQueueMicrotask() |
| 92 | .is_function(); |
| 93 | |
| 94 | Self { |
| 95 | promise: Promise::resolve(&JsValue::undefined()), |
| 96 | |
| 97 | closure: { |
| 98 | let state = Rc::clone(&state); |
| 99 | |
| 100 | // This closure will only be called on the next microtask event |
| 101 | // tick |
| 102 | Closure::new(move |_| state.run_all()) |
| 103 | }, |
| 104 | |
| 105 | state, |
| 106 | has_queue_microtask, |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | pub(crate) fn with<R>(f: impl FnOnce(&Self) -> R) -> R { |
| 111 | use once_cell::unsync::Lazy; |
| 112 | |
| 113 | struct Wrapper<T>(Lazy<T>); |
| 114 | |
| 115 | #[cfg (not(target_feature = "atomics" ))] |
| 116 | unsafe impl<T> Sync for Wrapper<T> {} |
| 117 | |
| 118 | #[cfg (not(target_feature = "atomics" ))] |
| 119 | unsafe impl<T> Send for Wrapper<T> {} |
| 120 | |
| 121 | #[cfg_attr (target_feature = "atomics" , thread_local)] |
| 122 | static QUEUE: Wrapper<Queue> = Wrapper(Lazy::new(Queue::new)); |
| 123 | |
| 124 | f(&QUEUE.0) |
| 125 | } |
| 126 | } |
| 127 | |