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