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}