virtual_dom.rs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. //! # VirtualDOM Implementation for Rust
  2. //!
  3. //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
  4. //!
  5. //! In this file, multiple items are defined. This file is big, but should be documented well to
  6. //! navigate the inner workings of the Dom. We try to keep these main mechanics in this file to limit
  7. //! the possible exposed API surface (keep fields private). This particular implementation of VDOM
  8. //! is extremely efficient, but relies on some unsafety under the hood to do things like manage
  9. //! micro-heaps for components. We are currently working on refactoring the safety out into safe(r)
  10. //! abstractions, but current tests (MIRI and otherwise) show no issues with the current implementation.
  11. //!
  12. //! Included is:
  13. //! - The [`VirtualDom`] itself
  14. //! - The [`Scope`] object for managing component lifecycle
  15. //! - The [`ActiveFrame`] object for managing the Scope`s microheap
  16. //! - The [`Context`] object for exposing VirtualDOM API to components
  17. //! - The [`NodeFactory`] object for lazily exposing the `Context` API to the nodebuilder API
  18. //!
  19. //! This module includes just the barebones for a complete VirtualDOM API.
  20. //! Additional functionality is defined in the respective files.
  21. use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
  22. use crate::innerlude::*;
  23. use std::{any::Any, rc::Rc};
  24. /// An integrated virtual node system that progresses events and diffs UI trees.
  25. ///
  26. /// Differences are converted into patches which a renderer can use to draw the UI.
  27. ///
  28. /// If you are building an App with Dioxus, you probably won't want to reach for this directly, instead opting to defer
  29. /// to a particular crate's wrapper over the [`VirtualDom`] API.
  30. ///
  31. /// Example
  32. /// ```rust
  33. /// static App: FC<()> = |(cx, props)|{
  34. /// cx.render(rsx!{
  35. /// div {
  36. /// "Hello World"
  37. /// }
  38. /// })
  39. /// }
  40. ///
  41. /// async fn main() {
  42. /// let mut dom = VirtualDom::new(App);
  43. /// let mut inital_edits = dom.rebuild();
  44. /// initialize_screen(inital_edits);
  45. ///
  46. /// loop {
  47. /// let next_frame = TimeoutFuture::new(Duration::from_millis(16));
  48. /// let edits = dom.run_with_deadline(next_frame).await;
  49. /// apply_edits(edits);
  50. /// render_frame();
  51. /// }
  52. /// }
  53. /// ```
  54. pub struct VirtualDom {
  55. scheduler: Scheduler,
  56. base_scope: ScopeId,
  57. root_fc: Box<dyn Any>,
  58. root_props: Rc<dyn Any + Send>,
  59. // we need to keep the allocation around, but we don't necessarily use it
  60. _root_caller: RootCaller,
  61. }
  62. impl VirtualDom {
  63. /// Create a new VirtualDOM with a component that does not have special props.
  64. ///
  65. /// # Description
  66. ///
  67. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  68. ///
  69. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  70. /// to toss out the entire tree.
  71. ///
  72. ///
  73. /// # Example
  74. /// ```
  75. /// fn Example(cx: Context<()>) -> DomTree {
  76. /// cx.render(rsx!( div { "hello world" } ))
  77. /// }
  78. ///
  79. /// let dom = VirtualDom::new(Example);
  80. /// ```
  81. ///
  82. /// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
  83. pub fn new(root: FC<()>) -> Self {
  84. Self::new_with_props(root, ())
  85. }
  86. /// Create a new VirtualDOM with the given properties for the root component.
  87. ///
  88. /// # Description
  89. ///
  90. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  91. ///
  92. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  93. /// to toss out the entire tree.
  94. ///
  95. ///
  96. /// # Example
  97. /// ```
  98. /// #[derive(PartialEq, Props)]
  99. /// struct SomeProps {
  100. /// name: &'static str
  101. /// }
  102. ///
  103. /// fn Example(cx: Context<SomeProps>) -> DomTree {
  104. /// cx.render(rsx!{ div{ "hello {cx.name}" } })
  105. /// }
  106. ///
  107. /// let dom = VirtualDom::new(Example);
  108. /// ```
  109. ///
  110. /// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
  111. ///
  112. /// ```rust
  113. /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
  114. /// let mutations = dom.rebuild();
  115. /// ```
  116. pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
  117. let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
  118. Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
  119. }
  120. /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
  121. ///
  122. /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
  123. /// VirtualDom to be created just to retrieve its channel receiver.
  124. pub fn new_with_props_and_scheduler<P: 'static + Send>(
  125. root: FC<P>,
  126. root_props: P,
  127. sender: UnboundedSender<SchedulerMsg>,
  128. receiver: UnboundedReceiver<SchedulerMsg>,
  129. ) -> Self {
  130. let root_fc = Box::new(root);
  131. let root_props: Rc<dyn Any + Send> = Rc::new(root_props);
  132. let _p = root_props.clone();
  133. // Safety: this callback is only valid for the lifetime of the root props
  134. let root_caller: Rc<dyn Fn(&ScopeInner) -> Element> =
  135. Rc::new(move |scope: &ScopeInner| unsafe {
  136. let props = _p.downcast_ref::<P>().unwrap();
  137. std::mem::transmute(root((Context { scope }, props)))
  138. });
  139. let scheduler = Scheduler::new(sender, receiver);
  140. let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
  141. ScopeInner::new(
  142. root_caller.as_ref(),
  143. myidx,
  144. None,
  145. 0,
  146. 0,
  147. ScopeChildren(&[]),
  148. scheduler.pool.channel.clone(),
  149. )
  150. });
  151. Self {
  152. _root_caller: RootCaller(root_caller),
  153. root_fc,
  154. base_scope,
  155. scheduler,
  156. root_props,
  157. }
  158. }
  159. /// Get the [`Scope`] for the root component.
  160. ///
  161. /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
  162. /// directly.
  163. pub fn base_scope(&self) -> &ScopeInner {
  164. self.scheduler.pool.get_scope(self.base_scope).unwrap()
  165. }
  166. /// Get the [`Scope`] for a component given its [`ScopeId`]
  167. pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
  168. self.scheduler.pool.get_scope(id)
  169. }
  170. /// Update the root props of this VirtualDOM.
  171. ///
  172. /// This method returns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
  173. /// so calling this method will block the main thread until computation is done.
  174. ///
  175. /// ## Example
  176. ///
  177. /// ```rust
  178. /// #[derive(Props, PartialEq)]
  179. /// struct AppProps {
  180. /// route: &'static str
  181. /// }
  182. /// static App: FC<AppProps> = |(cx, props)|cx.render(rsx!{ "route is {cx.route}" });
  183. ///
  184. /// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" });
  185. ///
  186. /// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap();
  187. /// ```
  188. pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
  189. where
  190. P: 'static + Send,
  191. {
  192. let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
  193. // Pre-emptively drop any downstream references of the old props
  194. root_scope.ensure_drop_safety(&self.scheduler.pool);
  195. let mut root_props: Rc<dyn Any + Send> = Rc::new(root_props);
  196. if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
  197. // Swap the old props and new props
  198. std::mem::swap(&mut self.root_props, &mut root_props);
  199. let root = *self.root_fc.downcast_ref::<FC<P>>().unwrap();
  200. let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
  201. Box::new(move |scope: &ScopeInner| unsafe {
  202. let props: &'_ P = &*(props_ptr as *const P);
  203. std::mem::transmute(root((Context { scope }, props)))
  204. });
  205. root_scope.update_scope_dependencies(&root_caller, ScopeChildren(&[]));
  206. drop(root_props);
  207. Some(self.rebuild())
  208. } else {
  209. None
  210. }
  211. }
  212. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
  213. ///
  214. /// The diff machine expects the RealDom's stack to be the root of the application.
  215. ///
  216. /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
  217. /// root component will be ran once and then diffed. All updates will flow out as mutations.
  218. ///
  219. /// All state stored in components will be completely wiped away.
  220. ///
  221. /// # Example
  222. /// ```
  223. /// static App: FC<()> = |(cx, props)|cx.render(rsx!{ "hello world" });
  224. /// let mut dom = VirtualDom::new();
  225. /// let edits = dom.rebuild();
  226. ///
  227. /// apply_edits(edits);
  228. /// ```
  229. pub fn rebuild(&mut self) -> Mutations {
  230. self.scheduler.rebuild(self.base_scope)
  231. }
  232. /// Compute a manual diff of the VirtualDOM between states.
  233. ///
  234. /// This can be useful when state inside the DOM is remotely changed from the outside, but not propagated as an event.
  235. ///
  236. /// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
  237. /// to force an update of the DOM when the state of the app is changed outside of the app.
  238. ///
  239. ///
  240. /// # Example
  241. /// ```rust
  242. /// #[derive(PartialEq, Props)]
  243. /// struct AppProps {
  244. /// value: Shared<&'static str>,
  245. /// }
  246. ///
  247. /// static App: FC<AppProps> = |(cx, props)|{
  248. /// let val = cx.value.borrow();
  249. /// cx.render(rsx! { div { "{val}" } })
  250. /// };
  251. ///
  252. /// let value = Rc::new(RefCell::new("Hello"));
  253. /// let mut dom = VirtualDom::new_with_props(
  254. /// App,
  255. /// AppProps {
  256. /// value: value.clone(),
  257. /// },
  258. /// );
  259. ///
  260. /// let _ = dom.rebuild();
  261. ///
  262. /// *value.borrow_mut() = "goodbye";
  263. ///
  264. /// let edits = dom.diff();
  265. /// ```
  266. pub fn diff(&mut self) -> Mutations {
  267. self.scheduler.hard_diff(self.base_scope)
  268. }
  269. /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
  270. ///
  271. /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
  272. /// return "None"
  273. pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
  274. if self.scheduler.has_any_work() {
  275. Some(self.scheduler.work_sync())
  276. } else {
  277. None
  278. }
  279. }
  280. /// Run the virtualdom with a deadline.
  281. ///
  282. /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
  283. /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
  284. /// exhaust the deadline working on them.
  285. ///
  286. /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
  287. /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
  288. ///
  289. /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
  290. /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
  291. /// deadline closure manually.
  292. ///
  293. /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
  294. /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
  295. /// the screen will "jank" up. In debug, this will trigger an alert.
  296. ///
  297. /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
  298. /// the provided deadline future resolves.
  299. ///
  300. /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
  301. /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
  302. /// entirely jank-free applications that perform a ton of work.
  303. ///
  304. /// # Example
  305. ///
  306. /// ```no_run
  307. /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
  308. /// let mut dom = VirtualDom::new(App);
  309. /// loop {
  310. /// let deadline = TimeoutFuture::from_ms(16);
  311. /// let mutations = dom.run_with_deadline(deadline).await;
  312. /// apply_mutations(mutations);
  313. /// }
  314. /// ```
  315. ///
  316. /// ## Mutations
  317. ///
  318. /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
  319. /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
  320. /// applied the edits.
  321. ///
  322. /// Mutations are the only link between the RealDOM and the VirtualDOM.
  323. pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
  324. self.scheduler.work_with_deadline(deadline)
  325. }
  326. pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
  327. self.scheduler.pool.channel.sender.clone()
  328. }
  329. /// Waits for the scheduler to have work
  330. /// This lets us poll async tasks during idle periods without blocking the main thread.
  331. pub async fn wait_for_work(&mut self) {
  332. // todo: poll the events once even if there is work to do to prevent starvation
  333. if self.scheduler.has_any_work() {
  334. return;
  335. }
  336. use futures_util::StreamExt;
  337. // Wait for any new events if we have nothing to do
  338. todo!("wait for work without select macro")
  339. // futures_util::select! {
  340. // _ = self.scheduler.async_tasks.next() => {}
  341. // msg = self.scheduler.receiver.next() => {
  342. // match msg.unwrap() {
  343. // SchedulerMsg::Task(t) => {
  344. // self.scheduler.handle_task(t);
  345. // },
  346. // SchedulerMsg::Immediate(im) => {
  347. // self.scheduler.dirty_scopes.insert(im);
  348. // }
  349. // SchedulerMsg::UiEvent(evt) => {
  350. // self.scheduler.ui_events.push_back(evt);
  351. // }
  352. // }
  353. // },
  354. // }
  355. }
  356. }
  357. impl std::fmt::Display for VirtualDom {
  358. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  359. let base = self.base_scope();
  360. let root = base.root_node();
  361. let renderer = ScopeRenderer {
  362. show_fragments: false,
  363. skip_components: false,
  364. _scope: base,
  365. _pre_render: false,
  366. _newline: true,
  367. _indent: true,
  368. _max_depth: usize::MAX,
  369. };
  370. renderer.render(self, root, f, 0)
  371. }
  372. }
  373. // we never actually use the contents of this root caller
  374. struct RootCaller(Rc<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'static>);