Skip to main content
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#[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
35/// Alias for `Rc<RefCell<T>>`
36pub type Shared<T> = Rc<RefCell<T>>;
37
38/// A routine which could be run.
39pub trait Runnable {
40    /// Runs a routine with a context instance.
41    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    /// The Binary Tree Map guarantees components with lower id (parent) is rendered first
67    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    /// Take a single entry, preferring parents over children
77    #[inline]
78    fn pop_topmost(&mut self) -> Option<QueueEntry> {
79        self.inner.pop_first().map(|(_, v)| v)
80    }
81
82    /// Drain all entries, such that children are queued before parents
83    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        // Children rendered lifecycle happen before parents.
89        queue.extend(rendered.into_values().rev());
90    }
91}
92
93/// This is a global scheduler suitable to schedule and run any tasks.
94#[derive(Default)]
95#[allow(missing_debug_implementations)] // todo
96struct Scheduler {
97    // Main queue
98    main: FifoQueue,
99
100    // Component queues
101    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/// Execute closure with a mutable reference to the scheduler
116#[inline]
117fn with<R>(f: impl FnOnce(&mut Scheduler) -> R) -> R {
118    thread_local! {
119        /// This is a global scheduler suitable to schedule and run any tasks.
120        ///
121        /// Exclusivity of mutable access is controlled by only accessing it through a set of public
122        /// functions.
123        static SCHEDULER: RefCell<Scheduler> = Default::default();
124    }
125
126    SCHEDULER.with(|s| f(&mut s.borrow_mut()))
127}
128
129/// Push a generic [Runnable] to be executed
130pub fn push(runnable: Box<dyn Runnable>) {
131    with(|s| s.main.push(runnable));
132    // Execute pending immediately. Necessary for runnables added outside the component lifecycle,
133    // which would otherwise be delayed.
134    start();
135}
136
137#[cfg(any(feature = "ssr", feature = "csr"))]
138mod feat_csr_ssr {
139    use super::*;
140    /// Push a component creation, first render and first rendered [Runnable]s to be executed
141    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    /// Push a component destruction [Runnable] to be executed
153    pub(crate) fn push_component_destroy(runnable: Box<dyn Runnable>) {
154        with(|s| s.destroy.push(runnable));
155    }
156
157    /// Push a component render [Runnable]s to be executed
158    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    /// Push a component update [Runnable] to be executed
165    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
213/// Execute any pending [Runnable]s
214pub(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        // The lock is used to prevent recursion. If the lock cannot be acquired, it is because the
231        // `start()` method is being called recursively as part of a `runnable.run()`.
232        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    // Really only used as a `Cell<bool>` that is also `Sync`
254    static IS_SCHEDULED: AtomicBool = AtomicBool::new(false);
255    fn check_scheduled() -> bool {
256        // Since we can tolerate starting too many times, and we don't need to "see" any stores
257        // done in the scheduler, Relaxed ordering is fine
258        IS_SCHEDULED.load(Ordering::Relaxed)
259    }
260    fn set_scheduled(is: bool) {
261        // See comment in check_scheduled why Relaxed ordering is fine
262        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    /// We delay the start of the scheduler to the end of the micro task queue.
271    /// So any messages that needs to be queued can be queued.
272    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    // Delayed rendering is not very useful in the context of server-side rendering.
291    // There are no event listeners or other high priority events that need to be
292    // processed and we risk of having a future un-finished.
293    // Until scheduler is future-capable which means we can join inside a future,
294    // it can remain synchronous.
295    pub(crate) fn start() {
296        super::start_now();
297    }
298}
299
300pub(crate) use arch::*;
301
302/// Flush all pending scheduler work, ensuring all rendering and lifecycle callbacks complete.
303///
304/// On browser WebAssembly targets, the scheduler defers its work to the microtask queue.
305/// This function registers a waker that is notified when `start_now()` finishes draining all
306/// queues, providing proper event-driven render-complete notification without arbitrary sleeps.
307///
308/// On non-browser targets, the scheduler runs synchronously so this simply drains pending work.
309///
310/// Use this in tests after mounting or updating a component to ensure all rendering has
311/// completed before making assertions.
312#[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/// Flush all pending scheduler work, ensuring all rendering and lifecycle callbacks complete.
333///
334/// On non-browser targets, the scheduler runs synchronously so this simply drains pending work.
335#[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    /// Fill vector with tasks to be executed according to Runnable type execution priority
349    ///
350    /// This method is optimized for typical usage, where possible, but does not break on
351    /// non-typical usage (like scheduling renders in [crate::Component::create()] or
352    /// [crate::Component::rendered()] calls).
353    fn fill_queue(&mut self, to_run: &mut Vec<QueueEntry>) {
354        // Placed first to avoid as much needless work as possible, handling all the other events.
355        // Drained completely, because they are the highest priority events anyway.
356        self.destroy.drain_into(to_run);
357
358        // Create events can be batched, as they are typically just for object creation
359        self.create.drain_into(to_run);
360
361        // These typically do nothing and don't spawn any other events - can be batched.
362        // Should be run only after all first renders have finished.
363        if !to_run.is_empty() {
364            return;
365        }
366
367        // First render must never be skipped and takes priority over main, because it may need
368        // to init `NodeRef`s
369        //
370        // Should be processed one at time, because they can spawn more create and rendered events
371        // for their children.
372        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        // Priority rendering
380        //
381        // This is needed for hydration subsequent render to fix node refs.
382        if let Some(r) = self.render_priority.pop_topmost() {
383            to_run.push(r);
384            return;
385        }
386
387        // Children rendered lifecycle happen before parents.
388        self.rendered_first.drain_post_order_into(to_run);
389
390        // Updates are after the first render to ensure we always have the entire child tree
391        // rendered, once an update is processed.
392        //
393        // Can be batched, as they can cause only non-first renders.
394        self.update.drain_into(to_run);
395
396        // Likely to cause duplicate renders via component updates, so placed before them
397        self.main.drain_into(to_run);
398
399        // Run after all possible updates to avoid duplicate renders.
400        //
401        // Should be processed one at time, because they can spawn more create and first render
402        // events for their children.
403        if !to_run.is_empty() {
404            return;
405        }
406
407        // Should be processed one at time, because they can spawn more create and rendered events
408        // for their children.
409        if let Some(r) = self.render.pop_topmost() {
410            to_run.push(r);
411            return;
412        }
413        // These typically do nothing and don't spawn any other events - can be batched.
414        // Should be run only after all renders have finished.
415        // Children rendered lifecycle happen before parents.
416        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}