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(app, ())
243    }
244
245    /// Create a new VirtualDom with the given properties for the root component.
246    ///
247    /// # Description
248    ///
249    /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
250    ///
251    /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
252    /// to toss out the entire tree.
253    ///
254    ///
255    /// # Example
256    /// ```rust, no_run
257    /// # use dioxus::prelude::*;
258    /// # use dioxus_core::*;
259    /// #[derive(PartialEq, Props, Clone)]
260    /// struct SomeProps {
261    ///     name: &'static str
262    /// }
263    ///
264    /// fn Example(cx: SomeProps) -> Element  {
265    ///     rsx! { div { "hello {cx.name}" } }
266    /// }
267    ///
268    /// let dom = VirtualDom::new_with_props(Example, SomeProps { name: "world" });
269    /// ```
270    ///
271    /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
272    ///
273    /// ```rust, no_run
274    /// # use dioxus::prelude::*;
275    /// # use dioxus_core::*;
276    /// # #[derive(PartialEq, Props, Clone)]
277    /// # struct SomeProps {
278    /// #     name: &'static str
279    /// # }
280    /// # fn Example(cx: SomeProps) -> Element  {
281    /// #     rsx! { div { "hello {cx.name}" } }
282    /// # }
283    /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
284    /// dom.rebuild_in_place();
285    /// ```
286    pub fn new_with_props<P: Clone + 'static, M: 'static>(
287        root: impl ComponentFunction<P, M>,
288        root_props: P,
289    ) -> Self {
290        let render_fn = root.fn_ptr();
291        let props = VProps::new(root, |_, _| true, root_props, "Root");
292        Self::new_with_component(VComponent {
293            name: "root",
294            render_fn,
295            props: Box::new(props),
296        })
297    }
298
299    /// Create a new virtualdom and build it immediately
300    pub fn prebuilt(app: fn() -> Element) -> Self {
301        let mut dom = Self::new(app);
302        dom.rebuild_in_place();
303        dom
304    }
305
306    /// Create a new VirtualDom from a VComponent
307    #[instrument(skip(root), level = "trace", name = "VirtualDom::new")]
308    pub(crate) fn new_with_component(root: VComponent) -> Self {
309        let (tx, rx) = futures_channel::mpsc::unbounded();
310
311        let mut dom = Self {
312            rx,
313            runtime: Runtime::new(tx),
314            scopes: Default::default(),
315            dirty_scopes: Default::default(),
316            resolved_scopes: Default::default(),
317        };
318
319        let root = VProps::new(
320            RootScopeWrapper,
321            |_, _| true,
322            RootProps(root),
323            "RootWrapper",
324        );
325        dom.new_scope(Box::new(root), "app");
326
327        #[cfg(debug_assertions)]
328        dom.register_subsecond_handler();
329
330        dom
331    }
332
333    /// Get the state for any scope given its ID
334    ///
335    /// This is useful for inserting or removing contexts from a scope, or rendering out its root node
336    pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
337        self.scopes.get(id.0)
338    }
339
340    /// Get the single scope at the top of the VirtualDom tree that will always be around
341    ///
342    /// This scope has a ScopeId of 0 and is the root of the tree
343    pub fn base_scope(&self) -> &ScopeState {
344        self.get_scope(ScopeId::ROOT).unwrap()
345    }
346
347    /// Run a closure inside the dioxus runtime
348    #[instrument(skip(self, f), level = "trace", name = "VirtualDom::in_runtime")]
349    pub fn in_runtime<O>(&self, f: impl FnOnce() -> O) -> O {
350        let _runtime = RuntimeGuard::new(self.runtime.clone());
351        f()
352    }
353
354    /// Build the virtualdom with a global context inserted into the base scope
355    ///
356    /// This is useful for what is essentially dependency injection when building the app
357    pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
358        self.base_scope().state().provide_context(context);
359        self
360    }
361
362    /// Provide a context to the root scope
363    pub fn provide_root_context<T: Clone + 'static>(&self, context: T) {
364        self.base_scope().state().provide_context(context);
365    }
366
367    /// Build the virtualdom with a global context inserted into the base scope
368    ///
369    /// This method is useful for when you want to provide a context in your app without knowing its type
370    pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
371        self.base_scope().state().provide_any_context(context);
372    }
373
374    /// Mark all scopes as dirty. Each scope will be re-rendered.
375    pub fn mark_all_dirty(&mut self) {
376        let mut orders = vec![];
377
378        for (_idx, scope) in self.scopes.iter() {
379            orders.push(ScopeOrder::new(scope.state().height(), scope.id()));
380        }
381
382        for order in orders {
383            self.queue_scope(order);
384        }
385    }
386
387    /// Manually mark a scope as requiring a re-render
388    ///
389    /// Whenever the Runtime "works", it will re-render this scope
390    pub fn mark_dirty(&mut self, id: ScopeId) {
391        let Some(scope) = self.runtime.get_state(id) else {
392            return;
393        };
394
395        tracing::event!(tracing::Level::TRACE, "Marking scope {:?} as dirty", id);
396        let order = ScopeOrder::new(scope.height(), id);
397        drop(scope);
398        self.queue_scope(order);
399    }
400
401    /// Mark a task as dirty
402    fn mark_task_dirty(&mut self, task: Task) {
403        let Some(scope) = self.runtime.task_scope(task) else {
404            return;
405        };
406        let Some(scope) = self.runtime.get_state(scope) else {
407            return;
408        };
409
410        tracing::event!(
411            tracing::Level::TRACE,
412            "Marking task {:?} (spawned in {:?}) as dirty",
413            task,
414            scope.id,
415        );
416
417        let order = ScopeOrder::new(scope.height(), scope.id);
418        drop(scope);
419        self.queue_task(task, order);
420    }
421
422    /// Wait for the scheduler to have any work.
423    ///
424    /// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
425    /// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
426    /// will exit.
427    ///
428    /// This method is cancel-safe, so you're fine to discard the future in a select block.
429    ///
430    /// This lets us poll async tasks and suspended trees during idle periods without blocking the main thread.
431    ///
432    /// # Example
433    ///
434    /// ```rust, no_run
435    /// # use dioxus::prelude::*;
436    /// # fn app() -> Element { rsx! { div {} } }
437    /// let dom = VirtualDom::new(app);
438    /// ```
439    #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_work")]
440    pub async fn wait_for_work(&mut self) {
441        loop {
442            // Process all events - Scopes are marked dirty, etc
443            // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
444            self.process_events();
445
446            // 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
447            if self.has_dirty_scopes() {
448                return;
449            }
450
451            // Make sure we set the runtime since we're running user code
452            let _runtime = RuntimeGuard::new(self.runtime.clone());
453
454            // There isn't any more work we can do synchronously. Wait for any new work to be ready
455            self.wait_for_event().await;
456        }
457    }
458
459    /// Wait for the next event to trigger and add it to the queue
460    #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_event")]
461    async fn wait_for_event(&mut self) {
462        match self.rx.next().await.expect("channel should never close") {
463            SchedulerMsg::Immediate(id) => self.mark_dirty(id),
464            SchedulerMsg::TaskNotified(id) => {
465                // Instead of running the task immediately, we insert it into the runtime's task queue.
466                // The task may be marked dirty at the same time as the scope that owns the task is dropped.
467                self.mark_task_dirty(Task::from_id(id));
468            }
469            SchedulerMsg::EffectQueued => {}
470            SchedulerMsg::AllDirty => self.mark_all_dirty(),
471        };
472    }
473
474    /// Queue any pending events
475    fn queue_events(&mut self) {
476        // Prevent a task from deadlocking the runtime by repeatedly queueing itself
477        while let Ok(Some(msg)) = self.rx.try_next() {
478            match msg {
479                SchedulerMsg::Immediate(id) => self.mark_dirty(id),
480                SchedulerMsg::TaskNotified(task) => self.mark_task_dirty(Task::from_id(task)),
481                SchedulerMsg::EffectQueued => {}
482                SchedulerMsg::AllDirty => self.mark_all_dirty(),
483            }
484        }
485    }
486
487    /// Process all events in the queue until there are no more left
488    #[instrument(skip(self), level = "trace", name = "VirtualDom::process_events")]
489    pub fn process_events(&mut self) {
490        self.queue_events();
491
492        // 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
493        if self.has_dirty_scopes() {
494            return;
495        }
496
497        self.poll_tasks()
498    }
499
500    /// Poll any queued tasks
501    #[instrument(skip(self), level = "trace", name = "VirtualDom::poll_tasks")]
502    fn poll_tasks(&mut self) {
503        // Make sure we set the runtime since we're running user code
504        let _runtime = RuntimeGuard::new(self.runtime.clone());
505
506        // Keep polling tasks until there are no more effects or tasks to run
507        // Or until we have no more dirty scopes
508        while !self.runtime.dirty_tasks.borrow().is_empty()
509            || !self.runtime.pending_effects.borrow().is_empty()
510        {
511            // Next, run any queued tasks
512            // We choose not to poll the deadline since we complete pretty quickly anyways
513            while let Some(task) = self.pop_task() {
514                let _ = self.runtime.handle_task_wakeup(task);
515
516                // Running that task, may mark a scope higher up as dirty. If it does, return from the function early
517                self.queue_events();
518                if self.has_dirty_scopes() {
519                    return;
520                }
521            }
522
523            // 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
524            while let Some(effect) = self.pop_effect() {
525                effect.run();
526                // Check if any new scopes are queued for rerun
527                self.queue_events();
528                if self.has_dirty_scopes() {
529                    return;
530                }
531            }
532        }
533    }
534
535    /// Rebuild the virtualdom without handling any of the mutations
536    ///
537    /// This is useful for testing purposes and in cases where you render the output of the virtualdom without
538    /// handling any of its mutations.
539    pub fn rebuild_in_place(&mut self) {
540        self.rebuild(&mut NoOpMutations);
541    }
542
543    /// [`VirtualDom::rebuild`] to a vector of mutations for testing purposes
544    pub fn rebuild_to_vec(&mut self) -> Mutations {
545        let mut mutations = Mutations::default();
546        self.rebuild(&mut mutations);
547        mutations
548    }
549
550    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
551    ///
552    /// The mutations item expects the RealDom's stack to be the root of the application.
553    ///
554    /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
555    /// root component will be run once and then diffed. All updates will flow out as mutations.
556    ///
557    /// All state stored in components will be completely wiped away.
558    ///
559    /// Any templates previously registered will remain.
560    ///
561    /// # Example
562    /// ```rust, no_run
563    /// # use dioxus::prelude::*;
564    /// # use dioxus_core::*;
565    /// fn app() -> Element {
566    ///     rsx! { "hello world" }
567    /// }
568    ///
569    /// let mut dom = VirtualDom::new(app);
570    /// let mut mutations = Mutations::default();
571    /// dom.rebuild(&mut mutations);
572    /// ```
573    #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
574    pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
575        let _runtime = RuntimeGuard::new(self.runtime.clone());
576        let new_nodes = self
577            .runtime
578            .clone()
579            .while_rendering(|| self.run_scope(ScopeId::ROOT));
580
581        self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());
582
583        // Rebuilding implies we append the created elements to the root
584        let m = self.create_scope(Some(to), ScopeId::ROOT, new_nodes, None);
585
586        to.append_children(ElementId(0), m);
587    }
588
589    /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
590    /// suspended subtrees.
591    #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
592    pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
593        // Process any events that might be pending in the queue
594        // Signals marked with .write() need a chance to be handled by the effect driver
595        // This also processes futures which might progress into immediately rerunning a scope
596        self.process_events();
597
598        // Next, diff any dirty scopes
599        // We choose not to poll the deadline since we complete pretty quickly anyways
600        let _runtime = RuntimeGuard::new(self.runtime.clone());
601        while let Some(work) = self.pop_work() {
602            match work {
603                Work::PollTask(task) => {
604                    _ = self.runtime.handle_task_wakeup(task);
605                    // Make sure we process any new events
606                    self.queue_events();
607                }
608                Work::RerunScope(scope) => {
609                    // If the scope is dirty, run the scope and get the mutations
610                    self.runtime.clone().while_rendering(|| {
611                        self.run_and_diff_scope(Some(to), scope.id);
612                    });
613                }
614            }
615        }
616
617        self.runtime.finish_render();
618    }
619
620    /// [`Self::render_immediate`] to a vector of mutations for testing purposes
621    pub fn render_immediate_to_vec(&mut self) -> Mutations {
622        let mut mutations = Mutations::default();
623        self.render_immediate(&mut mutations);
624        mutations
625    }
626
627    /// Render the virtual dom, waiting for all suspense to be finished
628    ///
629    /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
630    ///
631    /// We don't call "flush_sync" here since there's no sync work to be done. Futures will be progressed like usual,
632    /// however any futures waiting on flush_sync will remain pending
633    #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_suspense")]
634    pub async fn wait_for_suspense(&mut self) {
635        loop {
636            if !self.suspended_tasks_remaining() {
637                break;
638            }
639
640            self.wait_for_suspense_work().await;
641
642            self.render_suspense_immediate().await;
643        }
644    }
645
646    /// Check if there are any suspended tasks remaining
647    pub fn suspended_tasks_remaining(&self) -> bool {
648        self.runtime.suspended_tasks.get() > 0
649    }
650
651    /// Wait for the scheduler to have any work that should be run during suspense.
652    pub async fn wait_for_suspense_work(&mut self) {
653        // Wait for a work to be ready (IE new suspense leaves to pop up)
654        loop {
655            // Process all events - Scopes are marked dirty, etc
656            // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
657            self.queue_events();
658
659            // 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
660            if self.has_dirty_scopes() {
661                break;
662            }
663
664            {
665                // Make sure we set the runtime since we're running user code
666                let _runtime = RuntimeGuard::new(self.runtime.clone());
667                // Next, run any queued tasks
668                // We choose not to poll the deadline since we complete pretty quickly anyways
669                let mut tasks_polled = 0;
670                while let Some(task) = self.pop_task() {
671                    if self.runtime.task_runs_during_suspense(task) {
672                        let _ = self.runtime.handle_task_wakeup(task);
673                        // Running that task, may mark a scope higher up as dirty. If it does, return from the function early
674                        self.queue_events();
675                        if self.has_dirty_scopes() {
676                            return;
677                        }
678                    }
679                    tasks_polled += 1;
680                    // Once we have polled a few tasks, we manually yield to the scheduler to give it a chance to run other pending work
681                    if tasks_polled > 32 {
682                        yield_now().await;
683                        tasks_polled = 0;
684                    }
685                }
686            }
687
688            self.wait_for_event().await;
689        }
690    }
691
692    /// Render any dirty scopes immediately, but don't poll any futures that are client only on that scope
693    /// Returns a list of suspense boundaries that were resolved
694    pub async fn render_suspense_immediate(&mut self) -> Vec<ScopeId> {
695        // Queue any new events before we start working
696        self.queue_events();
697
698        // Render whatever work needs to be rendered, unlocking new futures and suspense leaves
699        let _runtime = RuntimeGuard::new(self.runtime.clone());
700
701        let mut work_done = 0;
702        while let Some(work) = self.pop_work() {
703            match work {
704                Work::PollTask(task) => {
705                    // During suspense, we only want to run tasks that are suspended
706                    if self.runtime.task_runs_during_suspense(task) {
707                        let _ = self.runtime.handle_task_wakeup(task);
708                    }
709                }
710                Work::RerunScope(scope) => {
711                    let scope_id: ScopeId = scope.id;
712                    let run_scope = self
713                        .runtime
714                        .get_state(scope.id)
715                        .filter(|scope| scope.should_run_during_suspense())
716                        .is_some();
717                    if run_scope {
718                        // If the scope is dirty, run the scope and get the mutations
719                        self.runtime.clone().while_rendering(|| {
720                            self.run_and_diff_scope(None::<&mut NoOpMutations>, scope_id);
721                        });
722
723                        tracing::trace!("Ran scope {:?} during suspense", scope_id);
724                    } else {
725                        tracing::warn!(
726                            "Scope {:?} was marked as dirty, but will not rerun during suspense. Only nodes that are under a suspense boundary rerun during suspense",
727                            scope_id
728                        );
729                    }
730                }
731            }
732            // Queue any new events
733            self.queue_events();
734            work_done += 1;
735            // Once we have polled a few tasks, we manually yield to the scheduler to give it a chance to run other pending work
736            if work_done > 32 {
737                yield_now().await;
738                work_done = 0;
739            }
740        }
741
742        self.resolved_scopes
743            .sort_by_key(|&id| self.runtime.get_state(id).unwrap().height);
744        std::mem::take(&mut self.resolved_scopes)
745    }
746
747    /// Get the current runtime
748    pub fn runtime(&self) -> Rc<Runtime> {
749        self.runtime.clone()
750    }
751
752    /// 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.
753    #[deprecated = "Use [VirtualDom::runtime().handle_event] instead"]
754    pub fn handle_event(&self, name: &str, event: Rc<dyn Any>, element: ElementId, bubbling: bool) {
755        let event = crate::Event::new(event, bubbling);
756        self.runtime().handle_event(name, event, element);
757    }
758
759    #[cfg(debug_assertions)]
760    fn register_subsecond_handler(&self) {
761        let sender = self.runtime().sender.clone();
762        subsecond::register_handler(std::sync::Arc::new(move || {
763            _ = sender.unbounded_send(SchedulerMsg::AllDirty);
764        }));
765    }
766}
767
768impl Drop for VirtualDom {
769    fn drop(&mut self) {
770        // Drop all scopes in order of height
771        let mut scopes = self.scopes.drain().collect::<Vec<_>>();
772        scopes.sort_by_key(|scope| scope.state().height);
773        for scope in scopes.into_iter().rev() {
774            drop(scope);
775        }
776
777        // Drop the mounts, tasks, and effects, releasing any `Rc<Runtime>` references
778        self.runtime.pending_effects.borrow_mut().clear();
779        self.runtime.tasks.borrow_mut().clear();
780        self.runtime.mounts.borrow_mut().clear();
781    }
782}
783
784/// 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.
785async fn yield_now() {
786    let mut yielded = false;
787    std::future::poll_fn::<(), _>(move |cx| {
788        if !yielded {
789            cx.waker().wake_by_ref();
790            yielded = true;
791            std::task::Poll::Pending
792        } else {
793            std::task::Poll::Ready(())
794        }
795    })
796    .await;
797}