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 crate::platform::spawn_local;
221
222 pub(crate) fn start() {
225 spawn_local(async {
226 super::start_now();
227 });
228 }
229}
230
231#[cfg(any(
232 not(target_arch = "wasm32"),
233 target_os = "wasi",
234 feature = "not_browser_env"
235))]
236mod arch {
237 pub(crate) fn start() {
243 super::start_now();
244 }
245}
246
247pub(crate) use arch::*;
248
249impl Scheduler {
250 fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
256 self.destroy.drain_into(to_run);
259
260 self.create.drain_into(to_run);
262
263 if !to_run.is_empty() {
266 return;
267 }
268
269 if let Some(r) = self.render_first.pop_topmost() {
275 to_run.push(r);
276 return;
277 }
278
279 self.props_update.drain_into(to_run);
280
281 if let Some(r) = self.render_priority.pop_topmost() {
285 to_run.push(r);
286 return;
287 }
288
289 self.rendered_first.drain_post_order_into(to_run);
291
292 self.update.drain_into(to_run);
297
298 self.main.drain_into(to_run);
300
301 if !to_run.is_empty() {
306 return;
307 }
308
309 if let Some(r) = self.render.pop_topmost() {
312 to_run.push(r);
313 return;
314 }
315 self.rendered.drain_post_order_into(to_run);
319 }
320}
321
322#[cfg(test)]
323mod tests {
324 use super::*;
325
326 #[test]
327 fn push_executes_runnables_immediately() {
328 use std::cell::Cell;
329
330 thread_local! {
331 static FLAG: Cell<bool> = Default::default();
332 }
333
334 struct Test;
335 impl Runnable for Test {
336 fn run(self: Box<Self>) {
337 FLAG.with(|v| v.set(true));
338 }
339 }
340
341 push(Box::new(Test));
342 FLAG.with(|v| assert!(v.get()));
343 }
344}