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