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>> = const { RefCell::new(Vec::new()) };
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 const fn new() -> Self {
55 Self { inner: Vec::new() }
56 }
57
58 fn push(&mut self, task: Box<dyn Runnable>) {
59 self.inner.push(QueueEntry { task });
60 }
61
62 fn drain_into(&mut self, queue: &mut Vec<QueueEntry>) {
63 queue.append(&mut self.inner);
64 }
65}
66
67#[derive(Default)]
68
69struct TopologicalQueue {
70 inner: BTreeMap<usize, QueueEntry>,
72}
73
74impl TopologicalQueue {
75 const fn new() -> Self {
76 Self {
77 inner: BTreeMap::new(),
78 }
79 }
80
81 #[cfg(any(feature = "ssr", feature = "csr"))]
82 fn push(&mut self, component_id: usize, task: Box<dyn Runnable>) {
83 self.inner.insert(component_id, QueueEntry { task });
84 }
85
86 #[inline]
88 fn pop_topmost(&mut self) -> Option<QueueEntry> {
89 self.inner.pop_first().map(|(_, v)| v)
90 }
91
92 fn drain_post_order_into(&mut self, queue: &mut Vec<QueueEntry>) {
94 if self.inner.is_empty() {
95 return;
96 }
97 let rendered = std::mem::take(&mut self.inner);
98 queue.extend(rendered.into_values().rev());
100 }
101}
102
103#[derive(Default)]
105struct Scheduler {
106 main: FifoQueue,
108
109 destroy: FifoQueue,
111 create: FifoQueue,
112
113 props_update: FifoQueue,
114 update: FifoQueue,
115
116 render: TopologicalQueue,
117 render_first: TopologicalQueue,
118 render_priority: TopologicalQueue,
119
120 rendered_first: TopologicalQueue,
121 rendered: TopologicalQueue,
122}
123
124impl Scheduler {
125 const fn new() -> Self {
126 Self {
127 main: FifoQueue::new(),
128 destroy: FifoQueue::new(),
129 create: FifoQueue::new(),
130 props_update: FifoQueue::new(),
131 update: FifoQueue::new(),
132 render: TopologicalQueue::new(),
133 render_first: TopologicalQueue::new(),
134 render_priority: TopologicalQueue::new(),
135 rendered_first: TopologicalQueue::new(),
136 rendered: TopologicalQueue::new(),
137 }
138 }
139}
140
141#[inline]
143fn with<R>(f: impl FnOnce(&mut Scheduler) -> R) -> R {
144 thread_local! {
145 static SCHEDULER: RefCell<Scheduler> = const { RefCell::new(Scheduler::new()) };
150 }
151
152 SCHEDULER.with(|s| f(&mut s.borrow_mut()))
153}
154
155pub fn push(runnable: Box<dyn Runnable>) {
157 with(|s| s.main.push(runnable));
158 start();
161}
162
163#[cfg(any(feature = "ssr", feature = "csr"))]
164mod feat_csr_ssr {
165 use super::*;
166 pub(crate) fn push_component_create(
168 component_id: usize,
169 create: Box<dyn Runnable>,
170 first_render: Box<dyn Runnable>,
171 ) {
172 with(|s| {
173 s.create.push(create);
174 s.render_first.push(component_id, first_render);
175 });
176 }
177
178 pub(crate) fn push_component_destroy(runnable: Box<dyn Runnable>) {
180 with(|s| s.destroy.push(runnable));
181 }
182
183 pub(crate) fn push_component_render(component_id: usize, render: Box<dyn Runnable>) {
185 with(|s| {
186 s.render.push(component_id, render);
187 });
188 }
189
190 pub(crate) fn push_component_update(runnable: Box<dyn Runnable>) {
192 with(|s| s.update.push(runnable));
193 }
194}
195
196#[cfg(any(feature = "ssr", feature = "csr"))]
197pub(crate) use feat_csr_ssr::*;
198
199#[cfg(feature = "csr")]
200mod feat_csr {
201 use super::*;
202
203 pub(crate) fn push_component_rendered(
204 component_id: usize,
205 rendered: Box<dyn Runnable>,
206 first_render: bool,
207 ) {
208 with(|s| {
209 if first_render {
210 s.rendered_first.push(component_id, rendered);
211 } else {
212 s.rendered.push(component_id, rendered);
213 }
214 });
215 }
216
217 pub(crate) fn push_component_props_update(props_update: Box<dyn Runnable>) {
218 with(|s| s.props_update.push(props_update));
219 }
220}
221
222#[cfg(feature = "csr")]
223pub(crate) use feat_csr::*;
224
225#[cfg(feature = "hydration")]
226mod feat_hydration {
227 use super::*;
228
229 pub(crate) fn push_component_priority_render(component_id: usize, render: Box<dyn Runnable>) {
230 with(|s| {
231 s.render_priority.push(component_id, render);
232 });
233 }
234}
235
236#[cfg(feature = "hydration")]
237pub(crate) use feat_hydration::*;
238
239#[cfg(any(
241 not(target_arch = "wasm32"),
242 target_os = "wasi",
243 feature = "not_browser_env",
244 test,
245 feature = "test"
246))]
247pub(crate) fn start_now() {
248 #[tracing::instrument(level = tracing::Level::DEBUG)]
249 fn scheduler_loop() {
250 let mut queue = vec![];
251 loop {
252 with(|s| s.fill_queue(&mut queue));
253 if queue.is_empty() {
254 break;
255 }
256 for r in queue.drain(..) {
257 r.task.run();
258 }
259 }
260 }
261
262 thread_local! {
263 static LOCK: RefCell<()> = const { RefCell::new(()) };
266 }
267
268 LOCK.with(|l| {
269 if let Ok(_lock) = l.try_borrow_mut() {
270 scheduler_loop();
271 #[cfg(any(test, feature = "test"))]
272 flush_wakers::wake_all();
273 }
274 });
275}
276
277#[cfg(all(
278 target_arch = "wasm32",
279 not(target_os = "wasi"),
280 not(feature = "not_browser_env")
281))]
282mod arch {
283 use std::sync::atomic::{AtomicBool, Ordering};
284
285 use wasm_bindgen::prelude::*;
286
287 use crate::platform::spawn_local;
288
289 static IS_SCHEDULED: AtomicBool = AtomicBool::new(false);
291 fn check_scheduled() -> bool {
292 IS_SCHEDULED.load(Ordering::Relaxed)
295 }
296 fn set_scheduled(is: bool) {
297 IS_SCHEDULED.store(is, Ordering::Relaxed)
299 }
300
301 #[cfg(any(test, feature = "test"))]
302 pub(super) fn is_scheduled() -> bool {
303 check_scheduled()
304 }
305
306 const YIELD_DEADLINE_MS: f64 = 16.0;
307
308 #[wasm_bindgen]
309 unsafe extern "C" {
310 #[wasm_bindgen(js_name = setTimeout)]
311 fn set_timeout(handler: &js_sys::Function, timeout: i32) -> i32;
312 }
313
314 fn run_scheduler(mut queue: Vec<super::QueueEntry>) {
315 let deadline = js_sys::Date::now() + YIELD_DEADLINE_MS;
316
317 loop {
318 super::with(|s| s.fill_queue(&mut queue));
319 if queue.is_empty() {
320 break;
321 }
322 for r in queue.drain(..) {
323 r.task.run();
324 }
325 if js_sys::Date::now() >= deadline {
326 let can_yield = super::with(|s| s.can_yield());
329 if can_yield {
330 let cb = Closure::once_into_js(move || run_scheduler(queue));
331 set_timeout(cb.unchecked_ref(), 0);
332 return;
333 }
334 }
335 }
336
337 set_scheduled(false);
338 #[cfg(any(test, feature = "test"))]
339 super::flush_wakers::wake_all();
340 }
341
342 pub(crate) fn start() {
347 if check_scheduled() {
348 return;
349 }
350 set_scheduled(true);
351 spawn_local(async {
352 run_scheduler(vec![]);
353 });
354 }
355}
356
357#[cfg(any(
358 not(target_arch = "wasm32"),
359 target_os = "wasi",
360 feature = "not_browser_env"
361))]
362mod arch {
363 pub(crate) fn start() {
369 super::start_now();
370 }
371}
372
373pub(crate) use arch::*;
374
375#[cfg(all(
386 any(test, feature = "test"),
387 target_arch = "wasm32",
388 not(target_os = "wasi"),
389 not(feature = "not_browser_env")
390))]
391pub async fn flush() {
392 std::future::poll_fn(|cx| {
393 start_now();
394
395 if arch::is_scheduled() {
396 flush_wakers::register(cx.waker().clone());
397 std::task::Poll::Pending
398 } else {
399 std::task::Poll::Ready(())
400 }
401 })
402 .await
403}
404
405#[cfg(all(
409 any(test, feature = "test"),
410 not(all(
411 target_arch = "wasm32",
412 not(target_os = "wasi"),
413 not(feature = "not_browser_env")
414 ))
415))]
416pub async fn flush() {
417 start_now();
418}
419
420impl Scheduler {
421 #[cfg(all(
424 target_arch = "wasm32",
425 not(target_os = "wasi"),
426 not(feature = "not_browser_env")
427 ))]
428 fn can_yield(&self) -> bool {
429 self.destroy.inner.is_empty()
430 && self.create.inner.is_empty()
431 && self.render_first.inner.is_empty()
432 && self.render.inner.is_empty()
433 && self.render_priority.inner.is_empty()
434 }
435
436 fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
442 self.destroy.drain_into(to_run);
445
446 self.create.drain_into(to_run);
448
449 if !to_run.is_empty() {
452 return;
453 }
454
455 if let Some(r) = self.render_first.pop_topmost() {
461 to_run.push(r);
462 return;
463 }
464
465 self.props_update.drain_into(to_run);
466
467 if let Some(r) = self.render_priority.pop_topmost() {
471 to_run.push(r);
472 return;
473 }
474
475 self.rendered_first.drain_post_order_into(to_run);
477
478 self.update.drain_into(to_run);
483
484 self.main.drain_into(to_run);
486
487 if !to_run.is_empty() {
492 return;
493 }
494
495 if let Some(r) = self.render.pop_topmost() {
498 to_run.push(r);
499 return;
500 }
501 self.rendered.drain_post_order_into(to_run);
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn push_executes_runnables_immediately() {
514 use std::cell::Cell;
515
516 thread_local! {
517 static FLAG: Cell<bool> = const { Cell::new(false) };
518 }
519
520 struct Test;
521 impl Runnable for Test {
522 fn run(self: Box<Self>) {
523 FLAG.with(|v| v.set(true));
524 }
525 }
526
527 push(Box::new(Test));
528 FLAG.with(|v| assert!(v.get()));
529 }
530}