Skip to main content

dioxus_core/
virtual_dom.rs

1//! # Virtual DOM Implementation for Rust
2//!
3//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
4
5use crate::innerlude::{VProps, Work};
6use crate::properties::RootProps;
7use crate::root_wrapper::RootScopeWrapper;
8use crate::{
9    ComponentFunction, Element, Mutations, NoOpMutations,
10    arena::ElementId,
11    innerlude::{SchedulerMsg, ScopeOrder, ScopeState, WriteMutations},
12    mutations::append_children_to,
13    runtime::{Runtime, RuntimeGuard},
14    scopes::ScopeId,
15};
16use crate::{Task, VComponent};
17use futures_util::StreamExt;
18use slab::Slab;
19use std::collections::BTreeSet;
20use std::{any::Any, rc::Rc};
21use tracing::instrument;
22
23/// A virtual node system that progresses user events and diffs UI trees.
24///
25/// ## Guide
26///
27/// Components are defined as simple functions that take [`crate::properties::Properties`] and return an [`Element`].
28///
29/// ```rust
30/// # use dioxus::prelude::*;
31///
32/// #[derive(Props, PartialEq, Clone)]
33/// struct AppProps {
34///     title: String
35/// }
36///
37/// fn app(cx: AppProps) -> Element {
38///     rsx!(
39///         div {"hello, {cx.title}"}
40///     )
41/// }
42/// ```
43///
44/// Components may be composed to make complex apps.
45///
46/// ```rust
47/// # #![allow(unused)]
48/// # use dioxus::prelude::*;
49///
50/// # #[derive(Props, PartialEq, Clone)]
51/// # struct AppProps {
52/// #     title: String
53/// # }
54///
55/// static ROUTES: &str = "";
56///
57/// #[component]
58/// fn app(cx: AppProps) -> Element {
59///     rsx!(
60///         NavBar { routes: ROUTES }
61///         Title { "{cx.title}" }
62///         Footer {}
63///     )
64/// }
65///
66/// #[component]
67/// fn NavBar( routes: &'static str) -> Element {
68///     rsx! {
69///         div { "Routes: {routes}" }
70///     }
71/// }
72///
73/// #[component]
74/// fn Footer() -> Element {
75///     rsx! { div { "Footer" } }
76/// }
77///
78/// #[component]
79/// fn Title( children: Element) -> Element {
80///     rsx! {
81///         div { id: "title", {children} }
82///     }
83/// }
84/// ```
85///
86/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
87/// draw the UI.
88///
89/// ```rust
90/// # use dioxus::prelude::*;
91/// # use dioxus_core::*;
92/// # fn app() -> Element { rsx! { div {} } }
93///
94/// let mut vdom = VirtualDom::new(app);
95/// let edits = vdom.rebuild_to_vec();
96/// ```
97///
98/// To call listeners inside the VirtualDom, call [`Runtime::handle_event`] with the appropriate event data.
99///
100/// ```rust, no_run
101/// # use dioxus::prelude::*;
102/// # use dioxus_core::*;
103/// # fn app() -> Element { rsx! { div {} } }
104/// # let mut vdom = VirtualDom::new(app);
105/// # let runtime = vdom.runtime();
106/// let event = Event::new(std::rc::Rc::new(0) as std::rc::Rc<dyn std::any::Any>, true);
107/// runtime.handle_event_for_target(RenderTargetId::ROOT, "onclick", event, ElementId::from_raw(0));
108/// ```
109///
110/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
111///
112/// ```rust, no_run
113/// # use dioxus::prelude::*;
114/// # use dioxus_core::*;
115/// # fn app() -> Element { rsx! { div {} } }
116/// # let mut vdom = VirtualDom::new(app);
117/// tokio::runtime::Runtime::new().unwrap().block_on(async {
118///     vdom.wait_for_work().await;
119/// });
120/// ```
121///
122/// Once work is ready, call [`VirtualDom::render_immediate`] to compute the differences between the previous and
123/// current UI trees. This will write edits to a [`WriteMutations`] object you pass in that contains with edits that need to be
124/// handled by the renderer.
125///
126/// ```rust, no_run
127/// # use dioxus::prelude::*;
128/// # use dioxus_core::*;
129/// # fn app() -> Element { rsx! { div {} } }
130/// # let mut vdom = VirtualDom::new(app);
131/// let mut mutations = Mutations::default();
132///
133/// vdom.render_immediate(&mut mutations);
134/// ```
135///
136/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`].
137///
138///
139/// ## Building an event loop around Dioxus:
140///
141/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
142/// ```rust, no_run
143/// # use dioxus::prelude::*;
144/// # use dioxus_core::*;
145/// # struct RealDom;
146/// # struct Event {}
147/// # impl RealDom {
148/// #     fn new() -> Self {
149/// #         Self {}
150/// #     }
151/// #     fn apply(&mut self) -> Mutations {
152/// #         unimplemented!()
153/// #     }
154/// #     async fn wait_for_event(&mut self) -> std::rc::Rc<dyn std::any::Any> {
155/// #         unimplemented!()
156/// #     }
157/// # }
158/// #
159/// # tokio::runtime::Runtime::new().unwrap().block_on(async {
160/// let mut real_dom = RealDom::new();
161///
162/// #[component]
163/// fn app() -> Element {
164///     rsx! {
165///         div { "Hello World" }
166///     }
167/// }
168///
169/// let mut dom = VirtualDom::new(app);
170///
171/// dom.rebuild(&mut real_dom.apply());
172///
173/// loop {
174///     tokio::select! {
175///         _ = dom.wait_for_work() => {}
176///         evt = real_dom.wait_for_event() => {
177///             let evt = dioxus_core::Event::new(evt, true);
178///             dom.runtime().handle_event_for_target(RenderTargetId::ROOT, "onclick", evt, ElementId::from_raw(0))
179///         },
180///     }
181///
182///     dom.render_immediate(&mut real_dom.apply());
183/// }
184/// # });
185/// ```
186///
187/// ## Waiting for suspense
188///
189/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other use cases
190/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
191/// [`VirtualDom::wait_for_suspense`] method:
192///
193/// ```rust, no_run
194/// # use dioxus::prelude::*;
195/// # use dioxus_core::*;
196/// # fn app() -> Element { rsx! { div {} } }
197/// tokio::runtime::Runtime::new().unwrap().block_on(async {
198///     let mut dom = VirtualDom::new(app);
199///
200///     dom.rebuild_in_place();
201///     dom.wait_for_suspense().await;
202/// });
203///
204/// // Render the virtual dom
205/// ```
206pub struct VirtualDom {
207    pub(crate) scopes: Slab<ScopeState>,
208
209    pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
210
211    pub(crate) runtime: Rc<Runtime>,
212
213    // The scopes that have been resolved since the last render
214    pub(crate) resolved_scopes: Vec<ScopeId>,
215
216    rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
217}
218
219impl VirtualDom {
220    /// Create a new VirtualDom with a component that does not have special props.
221    ///
222    /// # Description
223    ///
224    /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
225    ///
226    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
227    /// to toss out the entire tree.
228    ///
229    ///
230    /// # Example
231    /// ```rust, no_run
232    /// # use dioxus::prelude::*;
233    /// # use dioxus_core::*;
234    /// fn Example() -> Element  {
235    ///     rsx!( div { "hello world" } )
236    /// }
237    ///
238    /// let dom = VirtualDom::new(Example);
239    /// ```
240    ///
241    /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
242    pub fn new(app: fn() -> Element) -> Self {
243        Self::new_with_props(app, ())
244    }
245
246    /// Create a new VirtualDom with the given properties for the root component.
247    ///
248    /// # Description
249    ///
250    /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
251    ///
252    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
253    /// to toss out the entire tree.
254    ///
255    ///
256    /// # Example
257    /// ```rust, no_run
258    /// # use dioxus::prelude::*;
259    /// # use dioxus_core::*;
260    /// #[derive(PartialEq, Props, Clone)]
261    /// struct SomeProps {
262    ///     name: &'static str
263    /// }
264    ///
265    /// fn Example(cx: SomeProps) -> Element  {
266    ///     rsx! { div { "hello {cx.name}" } }
267    /// }
268    ///
269    /// let dom = VirtualDom::new_with_props(Example, SomeProps { name: "world" });
270    /// ```
271    ///
272    /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
273    ///
274    /// ```rust, no_run
275    /// # use dioxus::prelude::*;
276    /// # use dioxus_core::*;
277    /// # #[derive(PartialEq, Props, Clone)]
278    /// # struct SomeProps {
279    /// #     name: &'static str
280    /// # }
281    /// # fn Example(cx: SomeProps) -> Element  {
282    /// #     rsx! { div { "hello {cx.name}" } }
283    /// # }
284    /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
285    /// dom.rebuild_in_place();
286    /// ```
287    pub fn new_with_props<P: Clone + 'static, M: 'static>(
288        root: impl ComponentFunction<P, M>,
289        root_props: P,
290    ) -> Self {
291        let render_fn = root.fn_ptr();
292        let props = Box::new(VProps::new(root, |_, _| true, root_props, "Root"));
293        Self::new_with_component(VComponent::new_with_driver(
294            "root",
295            render_fn,
296            Rc::new(crate::render_driver::BodyDriver),
297            props,
298        ))
299    }
300
301    /// Create a new virtualdom and build it immediately
302    pub fn prebuilt(app: fn() -> Element) -> Self {
303        let mut dom = Self::new(app);
304        dom.rebuild_in_place();
305        dom
306    }
307
308    /// Create a new VirtualDom from a VComponent
309    #[instrument(skip(root), level = "trace", name = "VirtualDom::new")]
310    pub(crate) fn new_with_component(root: VComponent) -> Self {
311        let (tx, rx) = futures_channel::mpsc::unbounded();
312
313        let mut dom = Self {
314            rx,
315            runtime: Runtime::new(tx),
316            scopes: Default::default(),
317            dirty_scopes: Default::default(),
318            resolved_scopes: Default::default(),
319        };
320
321        let root_props = Box::new(VProps::new(
322            RootScopeWrapper,
323            |_, _| true,
324            RootProps(root),
325            "RootWrapper",
326        ));
327        dom.new_scope("app", Rc::new(crate::render_driver::BodyDriver), root_props);
328
329        #[cfg(debug_assertions)]
330        dom.register_subsecond_handler();
331
332        dom
333    }
334
335    /// Get the state for any scope given its ID
336    ///
337    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
338    pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
339        self.scopes.get(id.index())
340    }
341
342    /// Get the single scope at the top of the VirtualDom tree that will always be around
343    ///
344    /// This scope has a ScopeId of 0 and is the root of the tree
345    pub fn base_scope(&self) -> &ScopeState {
346        self.get_scope(ScopeId::ROOT).unwrap()
347    }
348
349    /// Run a closure inside the dioxus runtime
350    #[instrument(skip(self, f), level = "trace", name = "VirtualDom::in_runtime")]
351    pub fn in_runtime<O>(&self, f: impl FnOnce() -> O) -> O {
352        let _runtime = RuntimeGuard::new(self.runtime.clone());
353        f()
354    }
355
356    /// Run a closure inside a specific scope
357    pub fn in_scope<T>(&self, scope: ScopeId, f: impl FnOnce() -> T) -> T {
358        self.runtime.in_scope(scope, f)
359    }
360
361    /// Build the virtualdom with a global context inserted into the base scope
362    ///
363    /// This is useful for what is essentially dependency injection when building the app
364    pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
365        self.base_scope().state().provide_context(context);
366        self
367    }
368
369    /// Provide a context to the root scope
370    pub fn provide_root_context<T: Clone + 'static>(&self, context: T) {
371        self.base_scope().state().provide_context(context);
372    }
373
374    /// Build the virtualdom with a global context inserted into the base scope
375    ///
376    /// This method is useful for when you want to provide a context in your app without knowing its type
377    pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
378        self.base_scope().state().provide_any_context(context);
379    }
380
381    /// Mark all scopes as dirty. Each scope will be re-rendered.
382    pub fn mark_all_dirty(&mut self) {
383        let mut ids = vec![];
384
385        for (_idx, scope) in self.scopes.iter() {
386            ids.push(scope.id());
387        }
388
389        for id in ids {
390            self.queue_scope(id);
391        }
392    }
393
394    /// Manually mark a scope as requiring a re-render
395    ///
396    /// Whenever the Runtime "works", it will re-render this scope
397    pub fn mark_dirty(&mut self, id: ScopeId) {
398        let Some(scope) = self.runtime.try_get_state(id) else {
399            return;
400        };
401
402        tracing::event!(tracing::Level::TRACE, "Marking scope {:?} as dirty", id);
403        drop(scope);
404        self.queue_scope(id);
405    }
406
407    /// Mark a task as dirty
408    fn mark_task_dirty(&mut self, task: Task) {
409        let Some(scope) = self.runtime.task_scope(task) else {
410            return;
411        };
412        let Some(scope) = self.runtime.try_get_state(scope) else {
413            return;
414        };
415
416        tracing::event!(
417            tracing::Level::TRACE,
418            "Marking task {:?} (spawned in {:?}) as dirty",
419            task,
420            scope.id,
421        );
422
423        let order = ScopeOrder::new(scope.height(), scope.id);
424        drop(scope);
425        self.queue_task(task, order);
426    }
427
428    /// Wait for the scheduler to have any work.
429    ///
430    /// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
431    /// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
432    /// will exit.
433    ///
434    /// This method is cancel-safe, so you're fine to discard the future in a select block.
435    ///
436    /// This lets us poll async tasks and suspended trees during idle periods without blocking the main thread.
437    ///
438    /// # Example
439    ///
440    /// ```rust, no_run
441    /// # use dioxus::prelude::*;
442    /// # fn app() -> Element { rsx! { div {} } }
443    /// let dom = VirtualDom::new(app);
444    /// ```
445    #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_work")]
446    pub async fn wait_for_work(&mut self) {
447        loop {
448            // Process all events - Scopes are marked dirty, etc
449            // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
450            self.process_events();
451
452            // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
453            if self.has_dirty_scopes() {
454                return;
455            }
456
457            // There isn't any more work we can do synchronously. Wait for any new work to be ready
458            self.wait_for_event().await;
459        }
460    }
461
462    /// Wait for the next event to trigger and add it to the queue
463    #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_event")]
464    async fn wait_for_event(&mut self) {
465        match self.rx.next().await.expect("channel should never close") {
466            SchedulerMsg::Immediate(id) => self.mark_dirty(id),
467            SchedulerMsg::TaskNotified(id) => {
468                // Instead of running the task immediately, we insert it into the runtime's task queue.
469                // The task may be marked dirty at the same time as the scope that owns the task is dropped.
470                self.mark_task_dirty(Task::from_id(id));
471            }
472            SchedulerMsg::AllDirty => self.mark_all_dirty(),
473        };
474    }
475
476    /// Queue any pending events
477    fn queue_events(&mut self) {
478        // Prevent a task from deadlocking the runtime by repeatedly queueing itself
479        while let Ok(msg) = self.rx.try_recv() {
480            match msg {
481                SchedulerMsg::Immediate(id) => self.mark_dirty(id),
482                SchedulerMsg::TaskNotified(task) => self.mark_task_dirty(Task::from_id(task)),
483                SchedulerMsg::AllDirty => self.mark_all_dirty(),
484            }
485        }
486    }
487
488    /// Process all events in the queue until there are no more left
489    #[instrument(skip(self), level = "trace", name = "VirtualDom::process_events")]
490    pub fn process_events(&mut self) {
491        self.queue_events();
492
493        // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
494        if self.has_dirty_scopes() {
495            return;
496        }
497
498        self.poll_tasks()
499    }
500
501    /// Poll any queued tasks, then drain effects if task wakeups did not make
502    /// any scope dirty. In that case no render pass is coming to flush them.
503    #[instrument(skip(self), level = "trace", name = "VirtualDom::poll_tasks")]
504    fn poll_tasks(&mut self) {
505        // Make sure we set the runtime since we're running user code
506        let _runtime = RuntimeGuard::new(self.runtime.clone());
507
508        while !self.has_dirty_scopes() {
509            let Some(task) = self.pop_task() else {
510                break;
511            };
512
513            _ = self.runtime.handle_task_wakeup(task);
514
515            self.queue_events();
516            if self.has_dirty_scopes() {
517                return;
518            }
519        }
520
521        // Effects are normally run after a render pass wakes the scheduler. If
522        // task polling queued effects without dirtying any scope, wait_for_work
523        // will not return for a render pass, so drain them before going back to
524        // sleep.
525        self.drain_remaining_effects();
526    }
527
528    /// Rebuild the virtualdom without handling any of the mutations
529    ///
530    /// This is useful for testing purposes and in cases where you render the output of the virtualdom without
531    /// handling any of its mutations.
532    ///
533    /// The rebuild's edits are not applied to any real DOM, so the renderer
534    /// cache is reset afterwards to keep the next render correct.
535    pub fn rebuild_in_place(&mut self) {
536        self.rebuild(&mut NoOpMutations);
537    }
538
539    /// [`VirtualDom::rebuild`] to a vector of mutations for testing purposes
540    pub fn rebuild_to_vec(&mut self) -> Mutations {
541        let mut mutations = Mutations::default();
542        self.rebuild(&mut mutations);
543        mutations
544    }
545
546    /// Performs a *full* rebuild of the virtual dom, writing every produced
547    /// edit into `to`.
548    ///
549    /// Pass any [`MultiWriter`](crate::MultiWriter). A single [`WriteMutations`] writer serves
550    /// the root target, and hosts with multiple render targets can pass a
551    /// `BTreeMap<RenderTargetId, W>` keyed by target id or their own
552    /// [`MultiWriter`](crate::MultiWriter) implementation.
553    ///
554    /// Tasks will not be polled with this method, nor will any events be
555    /// processed from the event queue. Instead, the root component will be run
556    /// once and then diffed.
557    ///
558    /// All state stored in components will be completely wiped away.
559    ///
560    /// Any templates previously registered will remain.
561    #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
562    pub fn rebuild(&mut self, to: &mut impl crate::MultiWriter) {
563        let _runtime = RuntimeGuard::new(self.runtime.clone());
564        // The target router is the single write boundary for a render pass. No-op
565        // hosts ignore the same mutation stream as every other renderer.
566        let mut router = crate::mutations::TargetRouter::new(to, self.runtime.clone());
567        self.rebuild_with_writer(&mut router);
568    }
569
570    /// Render whatever the VirtualDom has ready as fast as possible without
571    /// requiring an executor to progress suspended subtrees, writing edits
572    /// into `to`.
573    ///
574    /// Pass any [`MultiWriter`](crate::MultiWriter). A single [`WriteMutations`] writer serves
575    /// the root target, and hosts with multiple render targets can pass a
576    /// `BTreeMap<RenderTargetId, W>` keyed by target id or their own
577    /// [`MultiWriter`](crate::MultiWriter) implementation.
578    #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
579    pub fn render_immediate(&mut self, to: &mut impl crate::MultiWriter) {
580        self.process_events();
581        {
582            let _runtime = RuntimeGuard::new(self.runtime.clone());
583            {
584                // Keep incremental renders on the same routed writer boundary
585                // as full rebuilds.
586                let mut router = crate::mutations::TargetRouter::new(to, self.runtime.clone());
587                self.render_immediate_with_writer(&mut router);
588            }
589        }
590    }
591
592    fn rebuild_with_writer(&mut self, to: &mut dyn WriteMutations) {
593        let driver = self.runtime.get_state(ScopeId::ROOT).render_driver();
594        append_children_to(to, ElementId::ROOT, self.runtime.clone(), |to| {
595            self.runtime.clone().while_rendering(|| {
596                driver.create(
597                    self,
598                    ScopeId::ROOT,
599                    None,
600                    Some(to as &mut dyn WriteMutations),
601                )
602            })
603        });
604    }
605
606    fn render_immediate_with_writer(&mut self, to: &mut dyn WriteMutations) {
607        while let Some(work) = self.pop_work() {
608            match work {
609                Work::PollTask(task) => {
610                    _ = self.runtime.handle_task_wakeup(task);
611                }
612                Work::RerunScope(scope) => {
613                    self.runtime.clone().while_rendering(|| {
614                        self.run_and_diff_scope(Some(to as &mut dyn WriteMutations), scope.id);
615                    });
616                }
617            }
618
619            // Drain any dirty marks the work item produced (e.g. a rerun
620            // child cancelling a suspended task dirties its suspense
621            // boundary). They arrive over the scheduler channel and must
622            // land in `dirty_scopes` for `pop_work` to see them, or the
623            // render would stop before the DOM converged.
624            self.queue_events();
625        }
626    }
627
628    fn drain_remaining_effects(&mut self) {
629        loop {
630            let mut progressed = false;
631
632            while let Some(effect) = self.pop_effect() {
633                progressed = true;
634                effect.run();
635                self.queue_events();
636                if self.has_dirty_scopes() {
637                    return;
638                }
639            }
640
641            while let Some(task) = self.pop_task() {
642                progressed = true;
643                _ = self.runtime.handle_task_wakeup(task);
644                self.queue_events();
645                if self.has_dirty_scopes() {
646                    return;
647                }
648            }
649
650            if !progressed {
651                break;
652            }
653        }
654    }
655
656    /// [`Self::render_immediate`] to a vector of mutations for testing purposes
657    pub fn render_immediate_to_vec(&mut self) -> Mutations {
658        let mut mutations = Mutations::default();
659        self.render_immediate(&mut mutations);
660        mutations
661    }
662
663    /// Render the virtual dom, waiting for all suspense to be finished
664    ///
665    /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
666    ///
667    /// We don't call "flush_sync" here since there's no sync work to be done. Futures will be progressed like usual,
668    /// however any futures waiting on flush_sync will remain pending
669    #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_suspense")]
670    pub async fn wait_for_suspense(&mut self) {
671        loop {
672            self.queue_events();
673
674            if !self.suspended_tasks_remaining() && !self.has_dirty_scopes() {
675                break;
676            }
677
678            self.wait_for_suspense_work().await;
679
680            self.render_suspense_immediate().await;
681        }
682    }
683
684    /// Check if there are any suspended tasks remaining
685    pub fn suspended_tasks_remaining(&self) -> bool {
686        self.runtime.suspended_tasks.get() > 0
687    }
688
689    /// Wait for the scheduler to have any work that should be run during suspense.
690    pub async fn wait_for_suspense_work(&mut self) {
691        // Wait for a work to be ready (IE new suspense leaves to pop up)
692        loop {
693            // Process all events - Scopes are marked dirty, etc
694            // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
695            self.queue_events();
696
697            // Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
698            if self.has_dirty_scopes() {
699                break;
700            }
701
702            // Make sure we set the runtime since we're running user code
703            let _runtime = RuntimeGuard::new(self.runtime.clone());
704            let mut tasks_polled = 0;
705            while let Some(task) = self.pop_task() {
706                if self.runtime.task_runs_during_suspense(task) {
707                    let _ = self.runtime.handle_task_wakeup(task);
708                    self.queue_events();
709                    if self.has_dirty_scopes() {
710                        return;
711                    }
712                }
713                tasks_polled += 1;
714                if tasks_polled > 32 {
715                    yield_now().await;
716                    tasks_polled = 0;
717                }
718            }
719
720            self.wait_for_event().await;
721        }
722    }
723
724    /// Render any suspense-ready dirty scopes without writing renderer
725    /// mutations, returning the suspense boundaries that resolved.
726    ///
727    /// Used by SSR and tests to drive suspended subtrees to completion. Yields
728    /// to the async scheduler periodically so it doesn't starve other work.
729    pub async fn render_suspense_immediate(&mut self) -> Vec<ScopeId> {
730        // Queue any new events before we start working
731        self.queue_events();
732
733        // Render whatever work needs to be rendered, unlocking new futures and suspense leaves
734        let _runtime = RuntimeGuard::new(self.runtime.clone());
735
736        let mut work_done = 0;
737        while let Some(work) = self.pop_work() {
738            match work {
739                Work::PollTask(task) => {
740                    // During suspense, we only want to run tasks that are suspended
741                    if self.runtime.task_runs_during_suspense(task) {
742                        let _ = self.runtime.handle_task_wakeup(task);
743                    }
744                }
745                Work::RerunScope(scope) => {
746                    let scope_id: ScopeId = scope.id;
747                    let run_scope = self
748                        .runtime
749                        .try_get_state(scope_id)
750                        .filter(|scope| scope.should_run_during_suspense())
751                        .is_some();
752                    if run_scope {
753                        // Run the scope and diff it without writing mutations.
754                        self.runtime.clone().while_rendering(|| {
755                            self.run_and_diff_scope(None, scope_id);
756                        });
757
758                        tracing::trace!("Ran scope {:?} during suspense", scope_id);
759                    } else {
760                        tracing::warn!(
761                            "Scope {:?} was marked as dirty, but will not rerun during suspense. Only nodes that are under a suspense boundary rerun during suspense",
762                            scope_id
763                        );
764                    }
765                }
766            }
767
768            // Queue any new events
769            self.queue_events();
770            work_done += 1;
771
772            // Once we have polled a few work units, manually yield to the
773            // scheduler to give it a chance to run other pending work.
774            if work_done > 32 {
775                yield_now().await;
776                work_done = 0;
777            }
778        }
779
780        self.resolved_scopes
781            .sort_by_key(|&id| self.runtime.get_state(id).height);
782        std::mem::take(&mut self.resolved_scopes)
783    }
784
785    /// Get the current runtime
786    pub fn runtime(&self) -> Rc<Runtime> {
787        self.runtime.clone()
788    }
789
790    /// Handle an event with the Virtual Dom. This method is deprecated in favor of [VirtualDom::runtime().handle_event] and will be removed in a future release.
791    #[deprecated = "Use [VirtualDom::runtime().handle_event] instead"]
792    pub fn handle_event(&self, name: &str, event: Rc<dyn Any>, element: ElementId, bubbling: bool) {
793        let event = crate::Event::new(event, bubbling);
794        self.runtime().handle_event(name, event, element);
795    }
796
797    #[cfg(debug_assertions)]
798    fn register_subsecond_handler(&self) {
799        let sender = self.runtime().sender.clone();
800        subsecond::register_handler(std::sync::Arc::new(move || {
801            _ = sender.unbounded_send(SchedulerMsg::AllDirty);
802        }));
803    }
804}
805
806impl Drop for VirtualDom {
807    fn drop(&mut self) {
808        // Drop all scopes in order of height
809        drop_scopes_child_first(self.scopes.drain().collect());
810
811        // Drop the mounts, tasks, and effects, releasing any `Rc<Runtime>` references
812        self.runtime.pending_effects.borrow_mut().clear();
813        self.runtime.tasks.borrow_mut().clear();
814        self.runtime.mounts.borrow_mut().clear();
815    }
816}
817
818fn drop_scopes_child_first(mut scopes: Vec<ScopeState>) {
819    let mut previous_height = None;
820    let mut sorted_by_height = true;
821    for scope in &scopes {
822        if let Some(previous_height) = previous_height
823            && scope.height() < previous_height
824        {
825            sorted_by_height = false;
826            break;
827        }
828        previous_height = Some(scope.height());
829    }
830
831    if !sorted_by_height {
832        scopes.sort_unstable_by_key(|scope| scope.height());
833    }
834
835    for scope in scopes.into_iter().rev() {
836        drop(scope);
837    }
838}
839
840/// Yield control back to the async scheduler. This is used to give the scheduler a chance to run other pending work. Or cancel the task if the client has disconnected.
841async fn yield_now() {
842    let mut yielded = false;
843    std::future::poll_fn::<(), _>(move |cx| {
844        if !yielded {
845            cx.waker().wake_by_ref();
846            yielded = true;
847            std::task::Poll::Pending
848        } else {
849            std::task::Poll::Ready(())
850        }
851    })
852    .await;
853}