1use std::cell::RefCell;
4use std::collections::BTreeMap;
5use std::rc::Rc;
6#[cfg(any(test, feature = "test"))]
7mod flush_wakers {
8 use std::cell::RefCell;
9 use std::task::Waker;
10
11 thread_local! {
12 static FLUSH_WAKERS: RefCell<Vec<Waker>> = Default::default();
13 }
14
15 #[cfg(all(
16 target_arch = "wasm32",
17 not(target_os = "wasi"),
18 not(feature = "not_browser_env")
19 ))]
20 pub(super) fn register(waker: Waker) {
21 FLUSH_WAKERS.with(|w| {
22 w.borrow_mut().push(waker);
23 });
24 }
25
26 pub(super) fn wake_all() {
27 FLUSH_WAKERS.with(|w| {
28 for waker in w.borrow_mut().drain(..) {
29 waker.wake();
30 }
31 });
32 }
33}
34
35pub type Shared<T> = Rc<RefCell<T>>;
37
38pub trait Runnable {
40 fn run(self: Box<Self>);
42}
43
44struct QueueEntry {
45 task: Box<dyn Runnable>,
46}
47
48#[derive(Default)]
49struct FifoQueue {
50 inner: Vec<QueueEntry>,
51}
52
53impl FifoQueue {
54 fn push(&mut self, task: Box<dyn Runnable>) {
55 self.inner.push(QueueEntry { task });
56 }
57
58 fn drain_into(&mut self, queue: &mut Vec<QueueEntry>) {
59 queue.append(&mut self.inner);
60 }
61}
62
63#[derive(Default)]
64
65struct TopologicalQueue {
66 inner: BTreeMap<usize, QueueEntry>,
68}
69
70impl TopologicalQueue {
71 #[cfg(any(feature = "ssr", feature = "csr"))]
72 fn push(&mut self, component_id: usize, task: Box<dyn Runnable>) {
73 self.inner.insert(component_id, QueueEntry { task });
74 }
75
76 #[inline]
78 fn pop_topmost(&mut self) -> Option<QueueEntry> {
79 self.inner.pop_first().map(|(_, v)| v)
80 }
81
82 fn drain_post_order_into(&mut self, queue: &mut Vec<QueueEntry>) {
84 if self.inner.is_empty() {
85 return;
86 }
87 let rendered = std::mem::take(&mut self.inner);
88 queue.extend(rendered.into_values().rev());
90 }
91}
92
93#[derive(Default)]
95#[allow(missing_debug_implementations)] struct Scheduler {
97 main: FifoQueue,
99
100 destroy: FifoQueue,
102 create: FifoQueue,
103
104 props_update: FifoQueue,
105 update: FifoQueue,
106
107 render: TopologicalQueue,
108 render_first: TopologicalQueue,
109 render_priority: TopologicalQueue,
110
111 rendered_first: TopologicalQueue,
112 rendered: TopologicalQueue,
113}
114
115#[inline]
117fn with<R>(f: impl FnOnce(&mut Scheduler) -> R) -> R {
118 thread_local! {
119 static SCHEDULER: RefCell<Scheduler> = Default::default();
124 }
125
126 SCHEDULER.with(|s| f(&mut s.borrow_mut()))
127}
128
129pub fn push(runnable: Box<dyn Runnable>) {
131 with(|s| s.main.push(runnable));
132 start();
135}
136
137#[cfg(any(feature = "ssr", feature = "csr"))]
138mod feat_csr_ssr {
139 use super::*;
140 pub(crate) fn push_component_create(
142 component_id: usize,
143 create: Box<dyn Runnable>,
144 first_render: Box<dyn Runnable>,
145 ) {
146 with(|s| {
147 s.create.push(create);
148 s.render_first.push(component_id, first_render);
149 });
150 }
151
152 pub(crate) fn push_component_destroy(runnable: Box<dyn Runnable>) {
154 with(|s| s.destroy.push(runnable));
155 }
156
157 pub(crate) fn push_component_render(component_id: usize, render: Box<dyn Runnable>) {
159 with(|s| {
160 s.render.push(component_id, render);
161 });
162 }
163
164 pub(crate) fn push_component_update(runnable: Box<dyn Runnable>) {
166 with(|s| s.update.push(runnable));
167 }
168}
169
170#[cfg(any(feature = "ssr", feature = "csr"))]
171pub(crate) use feat_csr_ssr::*;
172
173#[cfg(feature = "csr")]
174mod feat_csr {
175 use super::*;
176
177 pub(crate) fn push_component_rendered(
178 component_id: usize,
179 rendered: Box<dyn Runnable>,
180 first_render: bool,
181 ) {
182 with(|s| {
183 if first_render {
184 s.rendered_first.push(component_id, rendered);
185 } else {
186 s.rendered.push(component_id, rendered);
187 }
188 });
189 }
190
191 pub(crate) fn push_component_props_update(props_update: Box<dyn Runnable>) {
192 with(|s| s.props_update.push(props_update));
193 }
194}
195
196#[cfg(feature = "csr")]
197pub(crate) use feat_csr::*;
198
199#[cfg(feature = "hydration")]
200mod feat_hydration {
201 use super::*;
202
203 pub(crate) fn push_component_priority_render(component_id: usize, render: Box<dyn Runnable>) {
204 with(|s| {
205 s.render_priority.push(component_id, render);
206 });
207 }
208}
209
210#[cfg(feature = "hydration")]
211pub(crate) use feat_hydration::*;
212
213pub(crate) fn start_now() {
215 #[tracing::instrument(level = tracing::Level::DEBUG)]
216 fn scheduler_loop() {
217 let mut queue = vec![];
218 loop {
219 with(|s| s.fill_queue(&mut queue));
220 if queue.is_empty() {
221 break;
222 }
223 for r in queue.drain(..) {
224 r.task.run();
225 }
226 }
227 }
228
229 thread_local! {
230 static LOCK: RefCell<()> = Default::default();
233 }
234
235 LOCK.with(|l| {
236 if let Ok(_lock) = l.try_borrow_mut() {
237 scheduler_loop();
238 #[cfg(any(test, feature = "test"))]
239 flush_wakers::wake_all();
240 }
241 });
242}
243
244#[cfg(all(
245 target_arch = "wasm32",
246 not(target_os = "wasi"),
247 not(feature = "not_browser_env")
248))]
249mod arch {
250 use std::sync::atomic::{AtomicBool, Ordering};
251
252 use wasm_bindgen::prelude::*;
253
254 use crate::platform::spawn_local;
255
256 static IS_SCHEDULED: AtomicBool = AtomicBool::new(false);
258 fn check_scheduled() -> bool {
259 IS_SCHEDULED.load(Ordering::Relaxed)
262 }
263 fn set_scheduled(is: bool) {
264 IS_SCHEDULED.store(is, Ordering::Relaxed)
266 }
267
268 #[cfg(any(test, feature = "test"))]
269 pub(super) fn is_scheduled() -> bool {
270 check_scheduled()
271 }
272
273 const YIELD_DEADLINE_MS: f64 = 16.0;
274
275 #[wasm_bindgen]
276 extern "C" {
277 #[wasm_bindgen(js_name = setTimeout)]
278 fn set_timeout(handler: &js_sys::Function, timeout: i32) -> i32;
279 }
280
281 fn run_scheduler(mut queue: Vec<super::QueueEntry>) {
282 let deadline = js_sys::Date::now() + YIELD_DEADLINE_MS;
283
284 loop {
285 super::with(|s| s.fill_queue(&mut queue));
286 if queue.is_empty() {
287 break;
288 }
289 for r in queue.drain(..) {
290 r.task.run();
291 }
292 if js_sys::Date::now() >= deadline {
293 let can_yield = super::with(|s| s.can_yield());
296 if can_yield {
297 let cb = Closure::once_into_js(move || run_scheduler(queue));
298 set_timeout(cb.unchecked_ref(), 0);
299 return;
300 }
301 }
302 }
303
304 set_scheduled(false);
305 #[cfg(any(test, feature = "test"))]
306 super::flush_wakers::wake_all();
307 }
308
309 pub(crate) fn start() {
314 if check_scheduled() {
315 return;
316 }
317 set_scheduled(true);
318 spawn_local(async {
319 run_scheduler(vec![]);
320 });
321 }
322}
323
324#[cfg(any(
325 not(target_arch = "wasm32"),
326 target_os = "wasi",
327 feature = "not_browser_env"
328))]
329mod arch {
330 pub(crate) fn start() {
336 super::start_now();
337 }
338}
339
340pub(crate) use arch::*;
341
342#[cfg(all(
353 any(test, feature = "test"),
354 target_arch = "wasm32",
355 not(target_os = "wasi"),
356 not(feature = "not_browser_env")
357))]
358pub async fn flush() {
359 std::future::poll_fn(|cx| {
360 start_now();
361
362 if arch::is_scheduled() {
363 flush_wakers::register(cx.waker().clone());
364 std::task::Poll::Pending
365 } else {
366 std::task::Poll::Ready(())
367 }
368 })
369 .await
370}
371
372#[cfg(all(
376 any(test, feature = "test"),
377 not(all(
378 target_arch = "wasm32",
379 not(target_os = "wasi"),
380 not(feature = "not_browser_env")
381 ))
382))]
383pub async fn flush() {
384 start_now();
385}
386
387impl Scheduler {
388 #[cfg(all(
391 target_arch = "wasm32",
392 not(target_os = "wasi"),
393 not(feature = "not_browser_env")
394 ))]
395 fn can_yield(&self) -> bool {
396 self.destroy.inner.is_empty()
397 && self.create.inner.is_empty()
398 && self.render_first.inner.is_empty()
399 && self.render.inner.is_empty()
400 && self.render_priority.inner.is_empty()
401 }
402
403 fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
409 self.destroy.drain_into(to_run);
412
413 self.create.drain_into(to_run);
415
416 if !to_run.is_empty() {
419 return;
420 }
421
422 if let Some(r) = self.render_first.pop_topmost() {
428 to_run.push(r);
429 return;
430 }
431
432 self.props_update.drain_into(to_run);
433
434 if let Some(r) = self.render_priority.pop_topmost() {
438 to_run.push(r);
439 return;
440 }
441
442 self.rendered_first.drain_post_order_into(to_run);
444
445 self.update.drain_into(to_run);
450
451 self.main.drain_into(to_run);
453
454 if !to_run.is_empty() {
459 return;
460 }
461
462 if let Some(r) = self.render.pop_topmost() {
465 to_run.push(r);
466 return;
467 }
468 self.rendered.drain_post_order_into(to_run);
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn push_executes_runnables_immediately() {
481 use std::cell::Cell;
482
483 thread_local! {
484 static FLAG: Cell<bool> = Default::default();
485 }
486
487 struct Test;
488 impl Runnable for Test {
489 fn run(self: Box<Self>) {
490 FLAG.with(|v| v.set(true));
491 }
492 }
493
494 push(Box::new(Test));
495 FLAG.with(|v| assert!(v.get()));
496 }
497}