virtual_dom.rs 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917
  1. //! # Virtual DOM Implementation for Rust
  2. //!
  3. //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
  4. use crate::innerlude::Work;
  5. use crate::properties::RootProps;
  6. use crate::root_wrapper::RootScopeWrapper;
  7. use crate::{
  8. arena::ElementId,
  9. innerlude::{
  10. ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
  11. WriteMutations,
  12. },
  13. runtime::{Runtime, RuntimeGuard},
  14. scopes::ScopeId,
  15. AttributeValue, ComponentFunction, Element, Event, Mutations,
  16. };
  17. use crate::{Task, VComponent};
  18. use futures_util::StreamExt;
  19. use slab::Slab;
  20. use std::collections::BTreeSet;
  21. use std::{any::Any, rc::Rc};
  22. use tracing::instrument;
  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 [`VirtualDom::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 event = std::rc::Rc::new(0);
  106. /// vdom.handle_event("onclick", event, ElementId(0), true);
  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() => dom.handle_event("onclick", evt, ElementId(0), true),
  176. /// }
  177. ///
  178. /// dom.render_immediate(&mut real_dom.apply());
  179. /// }
  180. /// # });
  181. /// ```
  182. ///
  183. /// ## Waiting for suspense
  184. ///
  185. /// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other use cases
  186. /// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
  187. /// [`VirtualDom::wait_for_suspense`] method:
  188. ///
  189. /// ```rust, no_run
  190. /// # use dioxus::prelude::*;
  191. /// # use dioxus_core::*;
  192. /// # fn app() -> Element { rsx! { div {} } }
  193. /// tokio::runtime::Runtime::new().unwrap().block_on(async {
  194. /// let mut dom = VirtualDom::new(app);
  195. ///
  196. /// dom.rebuild_in_place();
  197. /// dom.wait_for_suspense().await;
  198. /// });
  199. ///
  200. /// // Render the virtual dom
  201. /// ```
  202. pub struct VirtualDom {
  203. pub(crate) scopes: Slab<ScopeState>,
  204. pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
  205. // The element ids that are used in the renderer
  206. // These mark a specific place in a whole rsx block
  207. pub(crate) elements: Slab<Option<ElementRef>>,
  208. // Once nodes are mounted, the information about where they are mounted is stored here
  209. // We need to store this information on the virtual dom so that we know what nodes are mounted where when we bubble events
  210. // Each mount is associated with a whole rsx block. [`VirtualDom::elements`] link to a specific node in the block
  211. pub(crate) mounts: Slab<VNodeMount>,
  212. pub(crate) runtime: Rc<Runtime>,
  213. // The scopes that have been resolved since the last render
  214. pub(crate) resolved_scopes: Vec<ScopeId>,
  215. rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
  216. }
  217. impl VirtualDom {
  218. /// Create a new VirtualDom with a component that does not have special props.
  219. ///
  220. /// # Description
  221. ///
  222. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  223. ///
  224. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  225. /// to toss out the entire tree.
  226. ///
  227. ///
  228. /// # Example
  229. /// ```rust, no_run
  230. /// # use dioxus::prelude::*;
  231. /// # use dioxus_core::*;
  232. /// fn Example() -> Element {
  233. /// rsx!( div { "hello world" } )
  234. /// }
  235. ///
  236. /// let dom = VirtualDom::new(Example);
  237. /// ```
  238. ///
  239. /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
  240. pub fn new(app: fn() -> Element) -> Self {
  241. Self::new_with_props(
  242. move || {
  243. use warnings::Warning;
  244. // The root props don't come from a vcomponent so we need to manually rerun them sometimes
  245. crate::properties::component_called_as_function::allow(app)
  246. },
  247. (),
  248. )
  249. }
  250. /// Create a new VirtualDom with the given properties for the root component.
  251. ///
  252. /// # Description
  253. ///
  254. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  255. ///
  256. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  257. /// to toss out the entire tree.
  258. ///
  259. ///
  260. /// # Example
  261. /// ```rust, no_run
  262. /// # use dioxus::prelude::*;
  263. /// # use dioxus_core::*;
  264. /// #[derive(PartialEq, Props, Clone)]
  265. /// struct SomeProps {
  266. /// name: &'static str
  267. /// }
  268. ///
  269. /// fn Example(cx: SomeProps) -> Element {
  270. /// rsx!{ div { "hello {cx.name}" } }
  271. /// }
  272. ///
  273. /// let dom = VirtualDom::new_with_props(Example, SomeProps { name: "world" });
  274. /// ```
  275. ///
  276. /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
  277. ///
  278. /// ```rust, no_run
  279. /// # use dioxus::prelude::*;
  280. /// # use dioxus_core::*;
  281. /// # #[derive(PartialEq, Props, Clone)]
  282. /// # struct SomeProps {
  283. /// # name: &'static str
  284. /// # }
  285. /// # fn Example(cx: SomeProps) -> Element {
  286. /// # rsx!{ div { "hello {cx.name}" } }
  287. /// # }
  288. /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
  289. /// dom.rebuild_in_place();
  290. /// ```
  291. pub fn new_with_props<P: Clone + 'static, M: 'static>(
  292. root: impl ComponentFunction<P, M>,
  293. root_props: P,
  294. ) -> Self {
  295. let render_fn = root.id();
  296. let props = VProps::new(root, |_, _| true, root_props, "Root");
  297. Self::new_with_component(VComponent {
  298. name: "root",
  299. render_fn,
  300. props: Box::new(props),
  301. })
  302. }
  303. /// Create a new virtualdom and build it immediately
  304. pub fn prebuilt(app: fn() -> Element) -> Self {
  305. let mut dom = Self::new(app);
  306. dom.rebuild_in_place();
  307. dom
  308. }
  309. /// Create a new VirtualDom from something that implements [`AnyProps`]
  310. #[instrument(skip(root), level = "trace", name = "VirtualDom::new")]
  311. pub(crate) fn new_with_component(root: VComponent) -> Self {
  312. let (tx, rx) = futures_channel::mpsc::unbounded();
  313. let mut dom = Self {
  314. rx,
  315. runtime: Runtime::new(tx),
  316. scopes: Default::default(),
  317. dirty_scopes: Default::default(),
  318. elements: Default::default(),
  319. mounts: Default::default(),
  320. resolved_scopes: Default::default(),
  321. };
  322. let root = VProps::new(
  323. RootScopeWrapper,
  324. |_, _| true,
  325. RootProps(root),
  326. "RootWrapper",
  327. );
  328. dom.new_scope(Box::new(root), "app");
  329. // the root element is always given element ID 0 since it's the container for the entire tree
  330. dom.elements.insert(None);
  331. dom
  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. /// Get the single scope at the top of the VirtualDom tree that will always be around
  340. ///
  341. /// This scope has a ScopeId of 0 and is the root of the tree
  342. pub fn base_scope(&self) -> &ScopeState {
  343. self.get_scope(ScopeId::ROOT).unwrap()
  344. }
  345. /// Run a closure inside the dioxus runtime
  346. #[instrument(skip(self, f), level = "trace", name = "VirtualDom::in_runtime")]
  347. pub fn in_runtime<O>(&self, f: impl FnOnce() -> O) -> O {
  348. let _runtime = RuntimeGuard::new(self.runtime.clone());
  349. f()
  350. }
  351. /// Build the virtualdom with a global context inserted into the base scope
  352. ///
  353. /// This is useful for what is essentially dependency injection when building the app
  354. pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
  355. self.base_scope().state().provide_context(context);
  356. self
  357. }
  358. /// Provide a context to the root scope
  359. pub fn provide_root_context<T: Clone + 'static>(&self, context: T) {
  360. self.base_scope().state().provide_context(context);
  361. }
  362. /// Build the virtualdom with a global context inserted into the base scope
  363. ///
  364. /// This method is useful for when you want to provide a context in your app without knowing its type
  365. pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
  366. self.base_scope().state().provide_any_context(context);
  367. }
  368. /// Manually mark a scope as requiring a re-render
  369. ///
  370. /// Whenever the Runtime "works", it will re-render this scope
  371. pub fn mark_dirty(&mut self, id: ScopeId) {
  372. let Some(scope) = self.runtime.get_state(id) else {
  373. return;
  374. };
  375. tracing::event!(tracing::Level::TRACE, "Marking scope {:?} as dirty", id);
  376. let order = ScopeOrder::new(scope.height(), id);
  377. drop(scope);
  378. self.queue_scope(order);
  379. }
  380. /// Mark a task as dirty
  381. fn mark_task_dirty(&mut self, task: Task) {
  382. let Some(scope) = self.runtime.task_scope(task) else {
  383. return;
  384. };
  385. let Some(scope) = self.runtime.get_state(scope) else {
  386. return;
  387. };
  388. tracing::event!(
  389. tracing::Level::TRACE,
  390. "Marking task {:?} (spawned in {:?}) as dirty",
  391. task,
  392. scope.id,
  393. );
  394. let order = ScopeOrder::new(scope.height(), scope.id);
  395. drop(scope);
  396. self.queue_task(task, order);
  397. }
  398. /// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
  399. ///
  400. /// This method will identify the appropriate element. The data must match up with the listener declared. Note that
  401. /// this method does not give any indication as to the success of the listener call. If the listener is not found,
  402. /// nothing will happen.
  403. ///
  404. /// It is up to the listeners themselves to mark nodes as dirty.
  405. ///
  406. /// If you have multiple events, you can call this method multiple times before calling "render_with_deadline"
  407. #[instrument(skip(self), level = "trace", name = "VirtualDom::handle_event")]
  408. pub fn handle_event(
  409. &mut self,
  410. name: &str,
  411. data: Rc<dyn Any>,
  412. element: ElementId,
  413. bubbles: bool,
  414. ) {
  415. let _runtime = RuntimeGuard::new(self.runtime.clone());
  416. if let Some(Some(parent_path)) = self.elements.get(element.0).copied() {
  417. if bubbles {
  418. self.handle_bubbling_event(parent_path, name, Event::new(data, bubbles));
  419. } else {
  420. self.handle_non_bubbling_event(parent_path, name, Event::new(data, bubbles));
  421. }
  422. }
  423. }
  424. /// Wait for the scheduler to have any work.
  425. ///
  426. /// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
  427. /// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
  428. /// will exit.
  429. ///
  430. /// This method is cancel-safe, so you're fine to discard the future in a select block.
  431. ///
  432. /// This lets us poll async tasks and suspended trees during idle periods without blocking the main thread.
  433. ///
  434. /// # Example
  435. ///
  436. /// ```rust, no_run
  437. /// # use dioxus::prelude::*;
  438. /// # fn app() -> Element { rsx! { div {} } }
  439. /// let dom = VirtualDom::new(app);
  440. /// ```
  441. #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_work")]
  442. pub async fn wait_for_work(&mut self) {
  443. loop {
  444. // Process all events - Scopes are marked dirty, etc
  445. // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
  446. self.process_events();
  447. // 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
  448. if self.has_dirty_scopes() {
  449. return;
  450. }
  451. // Make sure we set the runtime since we're running user code
  452. let _runtime = RuntimeGuard::new(self.runtime.clone());
  453. // There isn't any more work we can do synchronously. Wait for any new work to be ready
  454. self.wait_for_event().await;
  455. }
  456. }
  457. /// Wait for the next event to trigger and add it to the queue
  458. #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_event")]
  459. async fn wait_for_event(&mut self) {
  460. match self.rx.next().await.expect("channel should never close") {
  461. SchedulerMsg::Immediate(id) => self.mark_dirty(id),
  462. SchedulerMsg::TaskNotified(id) => {
  463. // Instead of running the task immediately, we insert it into the runtime's task queue.
  464. // The task may be marked dirty at the same time as the scope that owns the task is dropped.
  465. self.mark_task_dirty(Task::from_id(id));
  466. }
  467. SchedulerMsg::EffectQueued => {}
  468. };
  469. }
  470. /// Queue any pending events
  471. fn queue_events(&mut self) {
  472. // Prevent a task from deadlocking the runtime by repeatedly queueing itself
  473. while let Ok(Some(msg)) = self.rx.try_next() {
  474. match msg {
  475. SchedulerMsg::Immediate(id) => self.mark_dirty(id),
  476. SchedulerMsg::TaskNotified(task) => self.mark_task_dirty(Task::from_id(task)),
  477. SchedulerMsg::EffectQueued => {}
  478. }
  479. }
  480. }
  481. /// Process all events in the queue until there are no more left
  482. #[instrument(skip(self), level = "trace", name = "VirtualDom::process_events")]
  483. pub fn process_events(&mut self) {
  484. self.queue_events();
  485. // 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
  486. if self.has_dirty_scopes() {
  487. return;
  488. }
  489. self.poll_tasks()
  490. }
  491. /// Poll any queued tasks
  492. #[instrument(skip(self), level = "trace", name = "VirtualDom::poll_tasks")]
  493. fn poll_tasks(&mut self) {
  494. // Make sure we set the runtime since we're running user code
  495. let _runtime = RuntimeGuard::new(self.runtime.clone());
  496. // Keep polling tasks until there are no more effects or tasks to run
  497. // Or until we have no more dirty scopes
  498. while !self.runtime.dirty_tasks.borrow().is_empty()
  499. || !self.runtime.pending_effects.borrow().is_empty()
  500. {
  501. // Next, run any queued tasks
  502. // We choose not to poll the deadline since we complete pretty quickly anyways
  503. while let Some(task) = self.pop_task() {
  504. let _ = self.runtime.handle_task_wakeup(task);
  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. // 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
  512. while let Some(effect) = self.pop_effect() {
  513. effect.run(&self.runtime);
  514. // Check if any new scopes are queued for rerun
  515. self.queue_events();
  516. if self.has_dirty_scopes() {
  517. return;
  518. }
  519. }
  520. }
  521. }
  522. /// Rebuild the virtualdom without handling any of the mutations
  523. ///
  524. /// This is useful for testing purposes and in cases where you render the output of the virtualdom without
  525. /// handling any of its mutations.
  526. pub fn rebuild_in_place(&mut self) {
  527. self.rebuild(&mut NoOpMutations);
  528. }
  529. /// [`VirtualDom::rebuild`] to a vector of mutations for testing purposes
  530. pub fn rebuild_to_vec(&mut self) -> Mutations {
  531. let mut mutations = Mutations::default();
  532. self.rebuild(&mut mutations);
  533. mutations
  534. }
  535. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
  536. ///
  537. /// The mutations item expects the RealDom's stack to be the root of the application.
  538. ///
  539. /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
  540. /// root component will be run once and then diffed. All updates will flow out as mutations.
  541. ///
  542. /// All state stored in components will be completely wiped away.
  543. ///
  544. /// Any templates previously registered will remain.
  545. ///
  546. /// # Example
  547. /// ```rust, no_run
  548. /// # use dioxus::prelude::*;
  549. /// # use dioxus_core::*;
  550. /// fn app() -> Element {
  551. /// rsx! { "hello world" }
  552. /// }
  553. ///
  554. /// let mut dom = VirtualDom::new(app);
  555. /// let mut mutations = Mutations::default();
  556. /// dom.rebuild(&mut mutations);
  557. /// ```
  558. #[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
  559. pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
  560. let _runtime = RuntimeGuard::new(self.runtime.clone());
  561. let new_nodes = self.run_scope(ScopeId::ROOT);
  562. self.scopes[ScopeId::ROOT.0].last_rendered_node = Some(new_nodes.clone());
  563. // Rebuilding implies we append the created elements to the root
  564. let m = self.create_scope(Some(to), ScopeId::ROOT, new_nodes, None);
  565. to.append_children(ElementId(0), m);
  566. }
  567. /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
  568. /// suspended subtrees.
  569. #[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
  570. pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
  571. // Process any events that might be pending in the queue
  572. // Signals marked with .write() need a chance to be handled by the effect driver
  573. // This also processes futures which might progress into immediately rerunning a scope
  574. self.process_events();
  575. // Next, diff any dirty scopes
  576. // We choose not to poll the deadline since we complete pretty quickly anyways
  577. let _runtime = RuntimeGuard::new(self.runtime.clone());
  578. while let Some(work) = self.pop_work() {
  579. match work {
  580. Work::PollTask(task) => {
  581. _ = self.runtime.handle_task_wakeup(task);
  582. // Make sure we process any new events
  583. self.queue_events();
  584. }
  585. Work::RerunScope(scope) => {
  586. // If the scope is dirty, run the scope and get the mutations
  587. self.run_and_diff_scope(Some(to), scope.id);
  588. }
  589. }
  590. }
  591. self.runtime.finish_render();
  592. }
  593. /// [`Self::render_immediate`] to a vector of mutations for testing purposes
  594. pub fn render_immediate_to_vec(&mut self) -> Mutations {
  595. let mut mutations = Mutations::default();
  596. self.render_immediate(&mut mutations);
  597. mutations
  598. }
  599. /// Render the virtual dom, waiting for all suspense to be finished
  600. ///
  601. /// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
  602. ///
  603. /// We don't call "flush_sync" here since there's no sync work to be done. Futures will be progressed like usual,
  604. /// however any futures waiting on flush_sync will remain pending
  605. #[instrument(skip(self), level = "trace", name = "VirtualDom::wait_for_suspense")]
  606. pub async fn wait_for_suspense(&mut self) {
  607. loop {
  608. if !self.suspended_tasks_remaining() {
  609. break;
  610. }
  611. self.wait_for_suspense_work().await;
  612. self.render_suspense_immediate().await;
  613. }
  614. }
  615. /// Check if there are any suspended tasks remaining
  616. pub fn suspended_tasks_remaining(&self) -> bool {
  617. self.runtime.suspended_tasks.get() > 0
  618. }
  619. /// Wait for the scheduler to have any work that should be run during suspense.
  620. pub async fn wait_for_suspense_work(&mut self) {
  621. // Wait for a work to be ready (IE new suspense leaves to pop up)
  622. loop {
  623. // Process all events - Scopes are marked dirty, etc
  624. // Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
  625. self.queue_events();
  626. // 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
  627. if self.has_dirty_scopes() {
  628. break;
  629. }
  630. {
  631. // Make sure we set the runtime since we're running user code
  632. let _runtime = RuntimeGuard::new(self.runtime.clone());
  633. // Next, run any queued tasks
  634. // We choose not to poll the deadline since we complete pretty quickly anyways
  635. let mut tasks_polled = 0;
  636. while let Some(task) = self.pop_task() {
  637. if self.runtime.task_runs_during_suspense(task) {
  638. let _ = self.runtime.handle_task_wakeup(task);
  639. // Running that task, may mark a scope higher up as dirty. If it does, return from the function early
  640. self.queue_events();
  641. if self.has_dirty_scopes() {
  642. return;
  643. }
  644. }
  645. tasks_polled += 1;
  646. // Once we have polled a few tasks, we manually yield to the scheduler to give it a chance to run other pending work
  647. if tasks_polled > 32 {
  648. yield_now().await;
  649. tasks_polled = 0;
  650. }
  651. }
  652. }
  653. self.wait_for_event().await;
  654. }
  655. }
  656. /// Render any dirty scopes immediately, but don't poll any futures that are client only on that scope
  657. /// Returns a list of suspense boundaries that were resolved
  658. pub async fn render_suspense_immediate(&mut self) -> Vec<ScopeId> {
  659. // Queue any new events before we start working
  660. self.queue_events();
  661. // Render whatever work needs to be rendered, unlocking new futures and suspense leaves
  662. let _runtime = RuntimeGuard::new(self.runtime.clone());
  663. let mut work_done = 0;
  664. while let Some(work) = self.pop_work() {
  665. match work {
  666. Work::PollTask(task) => {
  667. // During suspense, we only want to run tasks that are suspended
  668. if self.runtime.task_runs_during_suspense(task) {
  669. let _ = self.runtime.handle_task_wakeup(task);
  670. }
  671. }
  672. Work::RerunScope(scope) => {
  673. let scope_id: ScopeId = scope.id;
  674. let run_scope = self
  675. .runtime
  676. .get_state(scope.id)
  677. .filter(|scope| scope.should_run_during_suspense())
  678. .is_some();
  679. if run_scope {
  680. // If the scope is dirty, run the scope and get the mutations
  681. self.run_and_diff_scope(None::<&mut NoOpMutations>, scope_id);
  682. tracing::trace!("Ran scope {:?} during suspense", scope_id);
  683. } else {
  684. tracing::warn!(
  685. "Scope {:?} was marked as dirty, but will not rerun during suspense. Only nodes that are under a suspense boundary rerun during suspense",
  686. scope_id
  687. );
  688. }
  689. }
  690. }
  691. // Queue any new events
  692. self.queue_events();
  693. work_done += 1;
  694. // Once we have polled a few tasks, we manually yield to the scheduler to give it a chance to run other pending work
  695. if work_done > 32 {
  696. yield_now().await;
  697. work_done = 0;
  698. }
  699. }
  700. self.resolved_scopes
  701. .sort_by_key(|&id| self.runtime.get_state(id).unwrap().height);
  702. std::mem::take(&mut self.resolved_scopes)
  703. }
  704. /// Get the current runtime
  705. pub fn runtime(&self) -> Rc<Runtime> {
  706. self.runtime.clone()
  707. }
  708. /*
  709. ------------------------
  710. The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
  711. we find the target path.
  712. With the target path, we try and move up to the parent until there is no parent.
  713. Due to how bubbling works, we call the listeners before walking to the parent.
  714. If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
  715. ----------------------
  716. For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
  717. right.
  718. | <-- yes (is ascendant)
  719. | | | <-- no (is not direct ascendant)
  720. | | <-- yes (is ascendant)
  721. | | | | | <--- target element, break early, don't check other listeners
  722. | | | <-- no, broke early
  723. | <-- no, broke early
  724. */
  725. #[instrument(
  726. skip(self, uievent),
  727. level = "trace",
  728. name = "VirtualDom::handle_bubbling_event"
  729. )]
  730. fn handle_bubbling_event(&mut self, parent: ElementRef, name: &str, uievent: Event<dyn Any>) {
  731. // If the event bubbles, we traverse through the tree until we find the target element.
  732. // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
  733. let mut parent = Some(parent);
  734. while let Some(path) = parent {
  735. let mut listeners = vec![];
  736. let Some(mount) = self.mounts.get(path.mount.0) else {
  737. // If the node is suspended and not mounted, we can just ignore the event
  738. return;
  739. };
  740. let el_ref = &mount.node;
  741. let node_template = el_ref.template;
  742. let target_path = path.path;
  743. // Accumulate listeners into the listener list bottom to top
  744. for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
  745. let attrs = &*el_ref.dynamic_attrs[idx];
  746. for attr in attrs.iter() {
  747. // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
  748. if attr.name.get(2..) == Some(name) && target_path.is_descendant(this_path) {
  749. listeners.push(&attr.value);
  750. // Break if this is the exact target element.
  751. // This means we won't call two listeners with the same name on the same element. This should be
  752. // documented, or be rejected from the rsx! macro outright
  753. if target_path == this_path {
  754. break;
  755. }
  756. }
  757. }
  758. }
  759. // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
  760. // We check the bubble state between each call to see if the event has been stopped from bubbling
  761. tracing::event!(
  762. tracing::Level::TRACE,
  763. "Calling {} listeners",
  764. listeners.len()
  765. );
  766. for listener in listeners.into_iter().rev() {
  767. if let AttributeValue::Listener(listener) = listener {
  768. self.runtime.rendering.set(false);
  769. listener.call(uievent.clone());
  770. self.runtime.rendering.set(true);
  771. if !uievent.propagates.get() {
  772. return;
  773. }
  774. }
  775. }
  776. let mount = el_ref.mount.get().as_usize();
  777. parent = mount.and_then(|id| self.mounts.get(id).and_then(|el| el.parent));
  778. }
  779. }
  780. /// Call an event listener in the simplest way possible without bubbling upwards
  781. #[instrument(
  782. skip(self, uievent),
  783. level = "trace",
  784. name = "VirtualDom::handle_non_bubbling_event"
  785. )]
  786. fn handle_non_bubbling_event(&mut self, node: ElementRef, name: &str, uievent: Event<dyn Any>) {
  787. let Some(mount) = self.mounts.get(node.mount.0) else {
  788. // If the node is suspended and not mounted, we can just ignore the event
  789. return;
  790. };
  791. let el_ref = &mount.node;
  792. let node_template = el_ref.template;
  793. let target_path = node.path;
  794. for (idx, this_path) in node_template.attr_paths.iter().enumerate() {
  795. let attrs = &*el_ref.dynamic_attrs[idx];
  796. for attr in attrs.iter() {
  797. // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
  798. // Only call the listener if this is the exact target element.
  799. if attr.name.get(2..) == Some(name) && target_path == this_path {
  800. if let AttributeValue::Listener(listener) = &attr.value {
  801. self.runtime.rendering.set(false);
  802. listener.call(uievent.clone());
  803. self.runtime.rendering.set(true);
  804. break;
  805. }
  806. }
  807. }
  808. }
  809. }
  810. }
  811. impl Drop for VirtualDom {
  812. fn drop(&mut self) {
  813. // Drop all scopes in order of height
  814. let mut scopes = self.scopes.drain().collect::<Vec<_>>();
  815. scopes.sort_by_key(|scope| scope.state().height);
  816. for scope in scopes.into_iter().rev() {
  817. drop(scope);
  818. }
  819. }
  820. }
  821. /// 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.
  822. async fn yield_now() {
  823. let mut yielded = false;
  824. std::future::poll_fn::<(), _>(move |cx| {
  825. if !yielded {
  826. cx.waker().wake_by_ref();
  827. yielded = true;
  828. std::task::Poll::Pending
  829. } else {
  830. std::task::Poll::Ready(())
  831. }
  832. })
  833. .await;
  834. }