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 crate::platform::spawn_local;
253 static IS_SCHEDULED: AtomicBool = AtomicBool::new(false);
255 fn check_scheduled() -> bool {
256 IS_SCHEDULED.load(Ordering::Relaxed)
259 }
260 fn set_scheduled(is: bool) {
261 IS_SCHEDULED.store(is, Ordering::Relaxed)
263 }
264
265 #[cfg(any(test, feature = "test"))]
266 pub(super) fn is_scheduled() -> bool {
267 check_scheduled()
268 }
269
270 pub(crate) fn start() {
273 if check_scheduled() {
274 return;
275 }
276 set_scheduled(true);
277 spawn_local(async {
278 set_scheduled(false);
279 super::start_now();
280 });
281 }
282}
283
284#[cfg(any(
285 not(target_arch = "wasm32"),
286 target_os = "wasi",
287 feature = "not_browser_env"
288))]
289mod arch {
290 pub(crate) fn start() {
296 super::start_now();
297 }
298}
299
300pub(crate) use arch::*;
301
302#[cfg(all(
313 any(test, feature = "test"),
314 target_arch = "wasm32",
315 not(target_os = "wasi"),
316 not(feature = "not_browser_env")
317))]
318pub async fn flush() {
319 std::future::poll_fn(|cx| {
320 start_now();
321
322 if arch::is_scheduled() {
323 flush_wakers::register(cx.waker().clone());
324 std::task::Poll::Pending
325 } else {
326 std::task::Poll::Ready(())
327 }
328 })
329 .await
330}
331
332#[cfg(all(
336 any(test, feature = "test"),
337 not(all(
338 target_arch = "wasm32",
339 not(target_os = "wasi"),
340 not(feature = "not_browser_env")
341 ))
342))]
343pub async fn flush() {
344 start_now();
345}
346
347impl Scheduler {
348 fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
354 self.destroy.drain_into(to_run);
357
358 self.create.drain_into(to_run);
360
361 if !to_run.is_empty() {
364 return;
365 }
366
367 if let Some(r) = self.render_first.pop_topmost() {
373 to_run.push(r);
374 return;
375 }
376
377 self.props_update.drain_into(to_run);
378
379 if let Some(r) = self.render_priority.pop_topmost() {
383 to_run.push(r);
384 return;
385 }
386
387 self.rendered_first.drain_post_order_into(to_run);
389
390 self.update.drain_into(to_run);
395
396 self.main.drain_into(to_run);
398
399 if !to_run.is_empty() {
404 return;
405 }
406
407 if let Some(r) = self.render.pop_topmost() {
410 to_run.push(r);
411 return;
412 }
413 self.rendered.drain_post_order_into(to_run);
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423
424 #[test]
425 fn push_executes_runnables_immediately() {
426 use std::cell::Cell;
427
428 thread_local! {
429 static FLAG: Cell<bool> = Default::default();
430 }
431
432 struct Test;
433 impl Runnable for Test {
434 fn run(self: Box<Self>) {
435 FLAG.with(|v| v.set(true));
436 }
437 }
438
439 push(Box::new(Test));
440 FLAG.with(|v| assert!(v.get()));
441 }
442}