1use std::cell::RefCell;
4use std::collections::BTreeMap;
5use std::rc::Rc;
6
7pub type Shared<T> = Rc<RefCell<T>>;
9
10pub trait Runnable {
12 fn run(self: Box<Self>);
14}
15
16struct QueueEntry {
17 task: Box<dyn Runnable>,
18}
19
20#[derive(Default)]
21struct FifoQueue {
22 inner: Vec<QueueEntry>,
23}
24
25impl FifoQueue {
26 fn push(&mut self, task: Box<dyn Runnable>) {
27 self.inner.push(QueueEntry { task });
28 }
29
30 fn drain_into(&mut self, queue: &mut Vec<QueueEntry>) {
31 queue.append(&mut self.inner);
32 }
33}
34
35#[derive(Default)]
36
37struct TopologicalQueue {
38 inner: BTreeMap<usize, QueueEntry>,
40}
41
42impl TopologicalQueue {
43 #[cfg(any(feature = "ssr", feature = "csr"))]
44 fn push(&mut self, component_id: usize, task: Box<dyn Runnable>) {
45 self.inner.insert(component_id, QueueEntry { task });
46 }
47
48 #[inline]
50 fn pop_topmost(&mut self) -> Option<QueueEntry> {
51 self.inner.pop_first().map(|(_, v)| v)
52 }
53
54 fn drain_post_order_into(&mut self, queue: &mut Vec<QueueEntry>) {
56 if self.inner.is_empty() {
57 return;
58 }
59 let rendered = std::mem::take(&mut self.inner);
60 queue.extend(rendered.into_values().rev());
62 }
63}
64
65#[derive(Default)]
67#[allow(missing_debug_implementations)] struct Scheduler {
69 main: FifoQueue,
71
72 destroy: FifoQueue,
74 create: FifoQueue,
75
76 props_update: FifoQueue,
77 update: FifoQueue,
78
79 render: TopologicalQueue,
80 render_first: TopologicalQueue,
81 render_priority: TopologicalQueue,
82
83 rendered_first: TopologicalQueue,
84 rendered: TopologicalQueue,
85}
86
87#[inline]
89fn with<R>(f: impl FnOnce(&mut Scheduler) -> R) -> R {
90 thread_local! {
91 static SCHEDULER: RefCell<Scheduler> = Default::default();
96 }
97
98 SCHEDULER.with(|s| f(&mut s.borrow_mut()))
99}
100
101pub fn push(runnable: Box<dyn Runnable>) {
103 with(|s| s.main.push(runnable));
104 start();
107}
108
109#[cfg(any(feature = "ssr", feature = "csr"))]
110mod feat_csr_ssr {
111 use super::*;
112 pub(crate) fn push_component_create(
114 component_id: usize,
115 create: Box<dyn Runnable>,
116 first_render: Box<dyn Runnable>,
117 ) {
118 with(|s| {
119 s.create.push(create);
120 s.render_first.push(component_id, first_render);
121 });
122 }
123
124 pub(crate) fn push_component_destroy(runnable: Box<dyn Runnable>) {
126 with(|s| s.destroy.push(runnable));
127 }
128
129 pub(crate) fn push_component_render(component_id: usize, render: Box<dyn Runnable>) {
131 with(|s| {
132 s.render.push(component_id, render);
133 });
134 }
135
136 pub(crate) fn push_component_update(runnable: Box<dyn Runnable>) {
138 with(|s| s.update.push(runnable));
139 }
140}
141
142#[cfg(any(feature = "ssr", feature = "csr"))]
143pub(crate) use feat_csr_ssr::*;
144
145#[cfg(feature = "csr")]
146mod feat_csr {
147 use super::*;
148
149 pub(crate) fn push_component_rendered(
150 component_id: usize,
151 rendered: Box<dyn Runnable>,
152 first_render: bool,
153 ) {
154 with(|s| {
155 if first_render {
156 s.rendered_first.push(component_id, rendered);
157 } else {
158 s.rendered.push(component_id, rendered);
159 }
160 });
161 }
162
163 pub(crate) fn push_component_props_update(props_update: Box<dyn Runnable>) {
164 with(|s| s.props_update.push(props_update));
165 }
166}
167
168#[cfg(feature = "csr")]
169pub(crate) use feat_csr::*;
170
171#[cfg(feature = "hydration")]
172mod feat_hydration {
173 use super::*;
174
175 pub(crate) fn push_component_priority_render(component_id: usize, render: Box<dyn Runnable>) {
176 with(|s| {
177 s.render_priority.push(component_id, render);
178 });
179 }
180}
181
182#[cfg(feature = "hydration")]
183pub(crate) use feat_hydration::*;
184
185pub(crate) fn start_now() {
187 #[tracing::instrument(level = tracing::Level::DEBUG)]
188 fn scheduler_loop() {
189 let mut queue = vec![];
190 loop {
191 with(|s| s.fill_queue(&mut queue));
192 if queue.is_empty() {
193 break;
194 }
195 for r in queue.drain(..) {
196 r.task.run();
197 }
198 }
199 }
200
201 thread_local! {
202 static LOCK: RefCell<()> = Default::default();
205 }
206
207 LOCK.with(|l| {
208 if let Ok(_lock) = l.try_borrow_mut() {
209 scheduler_loop();
210 }
211 });
212}
213
214#[cfg(all(
215 target_arch = "wasm32",
216 not(target_os = "wasi"),
217 not(feature = "not_browser_env")
218))]
219mod arch {
220 use std::sync::atomic::{AtomicBool, Ordering};
221
222 use crate::platform::spawn_local;
223 static IS_SCHEDULED: AtomicBool = AtomicBool::new(false);
225 fn check_scheduled() -> bool {
226 IS_SCHEDULED.load(Ordering::Relaxed)
229 }
230 fn set_scheduled(is: bool) {
231 IS_SCHEDULED.store(is, Ordering::Relaxed)
233 }
234
235 pub(crate) fn start() {
238 if check_scheduled() {
239 return;
240 }
241 set_scheduled(true);
242 spawn_local(async {
243 set_scheduled(false);
244 super::start_now();
245 });
246 }
247}
248
249#[cfg(any(
250 not(target_arch = "wasm32"),
251 target_os = "wasi",
252 feature = "not_browser_env"
253))]
254mod arch {
255 pub(crate) fn start() {
261 super::start_now();
262 }
263}
264
265pub(crate) use arch::*;
266
267impl Scheduler {
268 fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
274 self.destroy.drain_into(to_run);
277
278 self.create.drain_into(to_run);
280
281 if !to_run.is_empty() {
284 return;
285 }
286
287 if let Some(r) = self.render_first.pop_topmost() {
293 to_run.push(r);
294 return;
295 }
296
297 self.props_update.drain_into(to_run);
298
299 if let Some(r) = self.render_priority.pop_topmost() {
303 to_run.push(r);
304 return;
305 }
306
307 self.rendered_first.drain_post_order_into(to_run);
309
310 self.update.drain_into(to_run);
315
316 self.main.drain_into(to_run);
318
319 if !to_run.is_empty() {
324 return;
325 }
326
327 if let Some(r) = self.render.pop_topmost() {
330 to_run.push(r);
331 return;
332 }
333 self.rendered.drain_post_order_into(to_run);
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343
344 #[test]
345 fn push_executes_runnables_immediately() {
346 use std::cell::Cell;
347
348 thread_local! {
349 static FLAG: Cell<bool> = Default::default();
350 }
351
352 struct Test;
353 impl Runnable for Test {
354 fn run(self: Box<Self>) {
355 FLAG.with(|v| v.set(true));
356 }
357 }
358
359 push(Box::new(Test));
360 FLAG.with(|v| assert!(v.get()));
361 }
362}