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}