This is unreleased documentation for Yew Next version.
For up-to-date documentation, see the latest version on docs.rs.

yew/
scheduler.rs

1//! This module contains a scheduler.
2
3use std::cell::RefCell;
4use std::collections::BTreeMap;
5use std::rc::Rc;
6
7/// Alias for `Rc<RefCell<T>>`
8pub type Shared<T> = Rc<RefCell<T>>;
9
10/// A routine which could be run.
11pub trait Runnable {
12    /// Runs a routine with a context instance.
13    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    /// The Binary Tree Map guarantees components with lower id (parent) is rendered first
39    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    /// Take a single entry, preferring parents over children
49    #[inline]
50    fn pop_topmost(&mut self) -> Option<QueueEntry> {
51        self.inner.pop_first().map(|(_, v)| v)
52    }
53
54    /// Drain all entries, such that children are queued before parents
55    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        // Children rendered lifecycle happen before parents.
61        queue.extend(rendered.into_values().rev());
62    }
63}
64
65/// This is a global scheduler suitable to schedule and run any tasks.
66#[derive(Default)]
67#[allow(missing_debug_implementations)] // todo
68struct Scheduler {
69    // Main queue
70    main: FifoQueue,
71
72    // Component queues
73    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/// Execute closure with a mutable reference to the scheduler
88#[inline]
89fn with<R>(f: impl FnOnce(&mut Scheduler) -> R) -> R {
90    thread_local! {
91        /// This is a global scheduler suitable to schedule and run any tasks.
92        ///
93        /// Exclusivity of mutable access is controlled by only accessing it through a set of public
94        /// functions.
95        static SCHEDULER: RefCell<Scheduler> = Default::default();
96    }
97
98    SCHEDULER.with(|s| f(&mut s.borrow_mut()))
99}
100
101/// Push a generic [Runnable] to be executed
102pub fn push(runnable: Box<dyn Runnable>) {
103    with(|s| s.main.push(runnable));
104    // Execute pending immediately. Necessary for runnables added outside the component lifecycle,
105    // which would otherwise be delayed.
106    start();
107}
108
109#[cfg(any(feature = "ssr", feature = "csr"))]
110mod feat_csr_ssr {
111    use super::*;
112    /// Push a component creation, first render and first rendered [Runnable]s to be executed
113    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    /// Push a component destruction [Runnable] to be executed
125    pub(crate) fn push_component_destroy(runnable: Box<dyn Runnable>) {
126        with(|s| s.destroy.push(runnable));
127    }
128
129    /// Push a component render [Runnable]s to be executed
130    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    /// Push a component update [Runnable] to be executed
137    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
185/// Execute any pending [Runnable]s
186pub(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        // The lock is used to prevent recursion. If the lock cannot be acquired, it is because the
203        // `start()` method is being called recursively as part of a `runnable.run()`.
204        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    /// We delay the start of the scheduler to the end of the micro task queue.
223    /// So any messages that needs to be queued can be queued.
224    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    // Delayed rendering is not very useful in the context of server-side rendering.
238    // There are no event listeners or other high priority events that need to be
239    // processed and we risk of having a future un-finished.
240    // Until scheduler is future-capable which means we can join inside a future,
241    // it can remain synchronous.
242    pub(crate) fn start() {
243        super::start_now();
244    }
245}
246
247pub(crate) use arch::*;
248
249impl Scheduler {
250    /// Fill vector with tasks to be executed according to Runnable type execution priority
251    ///
252    /// This method is optimized for typical usage, where possible, but does not break on
253    /// non-typical usage (like scheduling renders in [crate::Component::create()] or
254    /// [crate::Component::rendered()] calls).
255    fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
256        // Placed first to avoid as much needless work as possible, handling all the other events.
257        // Drained completely, because they are the highest priority events anyway.
258        self.destroy.drain_into(to_run);
259
260        // Create events can be batched, as they are typically just for object creation
261        self.create.drain_into(to_run);
262
263        // These typically do nothing and don't spawn any other events - can be batched.
264        // Should be run only after all first renders have finished.
265        if !to_run.is_empty() {
266            return;
267        }
268
269        // First render must never be skipped and takes priority over main, because it may need
270        // to init `NodeRef`s
271        //
272        // Should be processed one at time, because they can spawn more create and rendered events
273        // for their children.
274        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        // Priority rendering
282        //
283        // This is needed for hydration susequent render to fix node refs.
284        if let Some(r) = self.render_priority.pop_topmost() {
285            to_run.push(r);
286            return;
287        }
288
289        // Children rendered lifecycle happen before parents.
290        self.rendered_first.drain_post_order_into(to_run);
291
292        // Updates are after the first render to ensure we always have the entire child tree
293        // rendered, once an update is processed.
294        //
295        // Can be batched, as they can cause only non-first renders.
296        self.update.drain_into(to_run);
297
298        // Likely to cause duplicate renders via component updates, so placed before them
299        self.main.drain_into(to_run);
300
301        // Run after all possible updates to avoid duplicate renders.
302        //
303        // Should be processed one at time, because they can spawn more create and first render
304        // events for their children.
305        if !to_run.is_empty() {
306            return;
307        }
308
309        // Should be processed one at time, because they can spawn more create and rendered events
310        // for their children.
311        if let Some(r) = self.render.pop_topmost() {
312            to_run.push(r);
313            return;
314        }
315        // These typically do nothing and don't spawn any other events - can be batched.
316        // Should be run only after all renders have finished.
317        // Children rendered lifecycle happen before parents.
318        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}