virtual_dom.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. use crate::{
  2. any_props::VComponentProps,
  3. arena::ElementId,
  4. arena::ElementPath,
  5. diff::DirtyScope,
  6. factory::RenderReturn,
  7. innerlude::{Mutations, Scheduler, SchedulerMsg},
  8. mutations::Mutation,
  9. nodes::{Template, TemplateId},
  10. scheduler::{SuspenseBoundary, SuspenseId},
  11. scopes::{ScopeId, ScopeState},
  12. Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext, UiEvent,
  13. };
  14. use futures_util::{pin_mut, FutureExt, StreamExt};
  15. use slab::Slab;
  16. use std::future::Future;
  17. use std::rc::Rc;
  18. use std::{
  19. any::Any,
  20. collections::{BTreeSet, HashMap},
  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 [`Scope`] and return an [`Element`].
  27. ///
  28. /// ```rust, ignore
  29. /// #[derive(Props, PartialEq)]
  30. /// struct AppProps {
  31. /// title: String
  32. /// }
  33. ///
  34. /// fn App(cx: Scope<AppProps>) -> Element {
  35. /// cx.render(rsx!(
  36. /// div {"hello, {cx.props.title}"}
  37. /// ))
  38. /// }
  39. /// ```
  40. ///
  41. /// Components may be composed to make complex apps.
  42. ///
  43. /// ```rust, ignore
  44. /// fn App(cx: Scope<AppProps>) -> Element {
  45. /// cx.render(rsx!(
  46. /// NavBar { routes: ROUTES }
  47. /// Title { "{cx.props.title}" }
  48. /// Footer {}
  49. /// ))
  50. /// }
  51. /// ```
  52. ///
  53. /// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
  54. /// draw the UI.
  55. ///
  56. /// ```rust, ignore
  57. /// let mut vdom = VirtualDom::new(App);
  58. /// let edits = vdom.rebuild();
  59. /// ```
  60. ///
  61. /// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
  62. ///
  63. /// ```rust, ignore
  64. /// vdom.handle_event(event);
  65. /// ```
  66. ///
  67. /// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
  68. ///
  69. /// ```rust, ignore
  70. /// vdom.wait_for_work().await;
  71. /// ```
  72. ///
  73. /// Once work is ready, call [`VirtualDom::render_with_deadline`] to compute the differences between the previous and
  74. /// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
  75. /// handled by the renderer.
  76. ///
  77. /// ```rust, ignore
  78. /// let mutations = vdom.work_with_deadline(tokio::time::sleep(Duration::from_millis(100)));
  79. ///
  80. /// for edit in mutations.edits {
  81. /// real_dom.apply(edit);
  82. /// }
  83. /// ```
  84. ///
  85. /// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`] or pass an immediately
  86. /// ready future to [`VirtualDom::render_with_deadline`].
  87. ///
  88. ///
  89. /// ## Building an event loop around Dioxus:
  90. ///
  91. /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
  92. /// ```rust
  93. /// fn app(cx: Scope) -> Element {
  94. /// cx.render(rsx!{
  95. /// div { "Hello World" }
  96. /// })
  97. /// }
  98. ///
  99. /// let dom = VirtualDom::new(app);
  100. ///
  101. /// real_dom.apply(dom.rebuild());
  102. ///
  103. /// loop {
  104. /// select! {
  105. /// _ = dom.wait_for_work() => {}
  106. /// evt = real_dom.wait_for_event() => dom.handle_event(evt),
  107. /// }
  108. ///
  109. /// real_dom.apply(dom.render_immediate());
  110. /// }
  111. /// ```
  112. ///
  113. /// ## Waiting for suspense
  114. ///
  115. /// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other usecases
  116. /// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
  117. /// [`VirtualDom::render_with_deadline`] method:
  118. ///
  119. /// ```rust
  120. /// let dom = VirtualDom::new(app);
  121. ///
  122. /// let deadline = tokio::time::sleep(Duration::from_millis(100));
  123. /// let edits = dom.render_with_deadline(deadline).await;
  124. /// ```
  125. ///
  126. /// ## Use with streaming
  127. ///
  128. /// If not all rendering is done by the deadline, it might be worthwhile to stream the rest later. To do this, we
  129. /// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until
  130. /// no suspended work is left.
  131. ///
  132. /// ```
  133. /// let dom = VirtualDom::new(app);
  134. ///
  135. /// let deadline = tokio::time::sleep(Duration::from_millis(20));
  136. /// let edits = dom.render_with_deadline(deadline).await;
  137. ///
  138. /// real_dom.apply(edits);
  139. ///
  140. /// while dom.has_suspended_work() {
  141. /// dom.wait_for_work().await;
  142. /// real_dom.apply(dom.render_immediate());
  143. /// }
  144. /// ```
  145. pub struct VirtualDom {
  146. pub(crate) templates: HashMap<TemplateId, Template<'static>>,
  147. pub(crate) elements: Slab<ElementPath>,
  148. pub(crate) scopes: Slab<ScopeState>,
  149. pub(crate) element_stack: Vec<ElementId>,
  150. pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
  151. pub(crate) scheduler: Rc<Scheduler>,
  152. // While diffing we need some sort of way of breaking off a stream of suspended mutations.
  153. pub(crate) scope_stack: Vec<ScopeId>,
  154. pub(crate) collected_leaves: Vec<SuspenseId>,
  155. // Whenever a suspense tree is finished, we push its boundary onto this stack.
  156. // When "render_with_deadline" is called, we pop the stack and return the mutations
  157. pub(crate) finished_fibers: Vec<ScopeId>,
  158. pub(crate) rx: futures_channel::mpsc::UnboundedReceiver<SchedulerMsg>,
  159. }
  160. impl VirtualDom {
  161. /// Create a new VirtualDom with a component that does not have special props.
  162. ///
  163. /// # Description
  164. ///
  165. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  166. ///
  167. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  168. /// to toss out the entire tree.
  169. ///
  170. ///
  171. /// # Example
  172. /// ```rust, ignore
  173. /// fn Example(cx: Scope) -> Element {
  174. /// cx.render(rsx!( div { "hello world" } ))
  175. /// }
  176. ///
  177. /// let dom = VirtualDom::new(Example);
  178. /// ```
  179. ///
  180. /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
  181. pub fn new(app: fn(Scope) -> Element) -> Self {
  182. Self::new_with_props(app, ())
  183. }
  184. /// Create a new VirtualDom with the given properties for the root component.
  185. ///
  186. /// # Description
  187. ///
  188. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  189. ///
  190. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  191. /// to toss out the entire tree.
  192. ///
  193. ///
  194. /// # Example
  195. /// ```rust, ignore
  196. /// #[derive(PartialEq, Props)]
  197. /// struct SomeProps {
  198. /// name: &'static str
  199. /// }
  200. ///
  201. /// fn Example(cx: Scope<SomeProps>) -> Element {
  202. /// cx.render(rsx!{ div{ "hello {cx.props.name}" } })
  203. /// }
  204. ///
  205. /// let dom = VirtualDom::new(Example);
  206. /// ```
  207. ///
  208. /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
  209. ///
  210. /// ```rust, ignore
  211. /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
  212. /// let mutations = dom.rebuild();
  213. /// ```
  214. pub fn new_with_props<P>(root: fn(Scope<P>) -> Element, root_props: P) -> Self
  215. where
  216. P: 'static,
  217. {
  218. let (tx, rx) = futures_channel::mpsc::unbounded();
  219. let mut dom = Self {
  220. rx,
  221. scheduler: Scheduler::new(tx),
  222. templates: Default::default(),
  223. scopes: Slab::default(),
  224. elements: Default::default(),
  225. scope_stack: Vec::new(),
  226. element_stack: vec![ElementId(0)],
  227. dirty_scopes: BTreeSet::new(),
  228. collected_leaves: Vec::new(),
  229. finished_fibers: Vec::new(),
  230. };
  231. let root = dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
  232. root,
  233. |_, _| unreachable!(),
  234. root_props,
  235. ))));
  236. // The root component is always a suspense boundary for any async children
  237. // This could be unexpected, so we might rethink this behavior
  238. root.provide_context(SuspenseBoundary::new(ScopeId(0)));
  239. // the root element is always given element 0
  240. dom.elements.insert(ElementPath::null());
  241. dom
  242. }
  243. pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
  244. self.scopes.get(id.0)
  245. }
  246. pub fn base_scope(&self) -> &ScopeState {
  247. self.scopes.get(0).unwrap()
  248. }
  249. fn mark_dirty_scope(&mut self, id: ScopeId) {
  250. let height = self.scopes[id.0].height;
  251. self.dirty_scopes.insert(DirtyScope { height, id });
  252. }
  253. fn is_scope_suspended(&self, id: ScopeId) -> bool {
  254. !self.scopes[id.0]
  255. .consume_context::<SuspenseContext>()
  256. .unwrap()
  257. .waiting_on
  258. .borrow()
  259. .is_empty()
  260. }
  261. /// Returns true if there is any suspended work left to be done.
  262. pub fn has_suspended_work(&self) -> bool {
  263. !self.scheduler.leaves.borrow().is_empty()
  264. }
  265. /// Call a listener inside the VirtualDom with data from outside the VirtualDom.
  266. ///
  267. /// This method will identify the appropriate element
  268. ///
  269. ///
  270. ///
  271. ///
  272. ///
  273. ///
  274. ///
  275. pub fn handle_event<T: 'static>(
  276. &mut self,
  277. name: &str,
  278. data: Rc<T>,
  279. element: ElementId,
  280. bubbles: bool,
  281. priority: EventPriority,
  282. ) -> Option<()> {
  283. /*
  284. - click registers
  285. - walk upwards until first element with onclick listener
  286. - get template from ElementID
  287. - break out of wait loop
  288. - send event to virtualdom
  289. */
  290. let event = UiEvent {
  291. bubble_state: std::cell::Cell::new(true),
  292. data,
  293. };
  294. let path = &self.elements[element.0];
  295. let template = unsafe { &*path.template };
  296. let dynamic = &template.dynamic_nodes[path.element];
  297. let location = template
  298. .dynamic_attrs
  299. .iter()
  300. .position(|attr| attr.mounted_element.get() == element)?;
  301. // let mut index = Some((path.template, location));
  302. let mut listeners = Vec::<&Attribute>::new();
  303. // while let Some((raw_parent, dyn_index)) = index {
  304. // let parent = unsafe { &mut *raw_parent };
  305. // let path = parent.template.node_paths[dyn_index];
  306. // listeners.extend(
  307. // parent
  308. // .dynamic_attrs
  309. // .iter()
  310. // .enumerate()
  311. // .filter_map(|(idx, attr)| {
  312. // match is_path_ascendant(parent.template.node_paths[idx], path) {
  313. // true if attr.name == event.name => Some(attr),
  314. // _ => None,
  315. // }
  316. // }),
  317. // );
  318. // index = parent.parent;
  319. // }
  320. for listener in listeners {
  321. if let AttributeValue::Listener(listener) = &listener.value {
  322. (listener.borrow_mut())(&event.clone())
  323. }
  324. }
  325. Some(())
  326. }
  327. /// Wait for the scheduler to have any work.
  328. ///
  329. /// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
  330. /// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
  331. /// will exit.
  332. ///
  333. /// This method is cancel-safe, so you're fine to discard the future in a select block.
  334. ///
  335. /// This lets us poll async tasks during idle periods without blocking the main thread.
  336. ///
  337. /// # Example
  338. ///
  339. /// ```rust, ignore
  340. /// let dom = VirtualDom::new(App);
  341. /// let sender = dom.get_scheduler_channel();
  342. /// ```
  343. pub async fn wait_for_work(&mut self) {
  344. let mut some_msg = None;
  345. loop {
  346. match some_msg.take() {
  347. // If a bunch of messages are ready in a sequence, try to pop them off synchronously
  348. Some(msg) => match msg {
  349. SchedulerMsg::Immediate(id) => self.mark_dirty_scope(id),
  350. SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
  351. SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
  352. },
  353. // If they're not ready, then we should wait for them to be ready
  354. None => {
  355. match self.rx.try_next() {
  356. Ok(Some(val)) => some_msg = Some(val),
  357. Ok(None) => return,
  358. Err(_) => {
  359. // If we have any dirty scopes, or finished fiber trees then we should exit
  360. if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
  361. return;
  362. }
  363. some_msg = self.rx.next().await
  364. }
  365. }
  366. }
  367. }
  368. }
  369. }
  370. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
  371. ///
  372. /// The diff machine expects the RealDom's stack to be the root of the application.
  373. ///
  374. /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
  375. /// root component will be ran once and then diffed. All updates will flow out as mutations.
  376. ///
  377. /// All state stored in components will be completely wiped away.
  378. ///
  379. /// Any templates previously registered will remain.
  380. ///
  381. /// # Example
  382. /// ```rust, ignore
  383. /// static App: Component = |cx| cx.render(rsx!{ "hello world" });
  384. ///
  385. /// let mut dom = VirtualDom::new();
  386. /// let edits = dom.rebuild();
  387. ///
  388. /// apply_edits(edits);
  389. /// ```
  390. pub fn rebuild<'a>(&'a mut self) -> Mutations<'a> {
  391. let mut mutations = Mutations::new(0);
  392. let root_node = unsafe { self.run_scope_extend(ScopeId(0)) };
  393. match root_node {
  394. RenderReturn::Sync(Some(node)) => {
  395. let m = self.create_scope(ScopeId(0), &mut mutations, node);
  396. mutations.push(Mutation::AppendChildren { m });
  397. }
  398. RenderReturn::Sync(None) => {}
  399. RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
  400. }
  401. mutations
  402. }
  403. /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
  404. /// suspended subtrees.
  405. pub fn render_immediate(&mut self) -> Mutations {
  406. // Build a waker that won't wake up since our deadline is already expired when it's polled
  407. let waker = futures_util::task::noop_waker();
  408. let mut cx = std::task::Context::from_waker(&waker);
  409. // Now run render with deadline but dont even try to poll any async tasks
  410. let fut = self.render_with_deadline(std::future::ready(()));
  411. pin_mut!(fut);
  412. match fut.poll_unpin(&mut cx) {
  413. std::task::Poll::Ready(mutations) => mutations,
  414. std::task::Poll::Pending => panic!("render_immediate should never return pending"),
  415. }
  416. }
  417. /// Render what you can given the timeline and then move on
  418. ///
  419. /// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
  420. ///
  421. /// If no suspense trees are present
  422. pub async fn render_with_deadline<'a>(
  423. &'a mut self,
  424. deadline: impl Future<Output = ()>,
  425. ) -> Mutations<'a> {
  426. use futures_util::future::{select, Either};
  427. let mut mutations = Mutations::new(0);
  428. pin_mut!(deadline);
  429. loop {
  430. // first, unload any complete suspense trees
  431. for finished_fiber in self.finished_fibers.drain(..) {
  432. let scope = &mut self.scopes[finished_fiber.0];
  433. let context = scope.has_context::<SuspenseContext>().unwrap();
  434. println!("unloading suspense tree {:?}", context.mutations);
  435. mutations.extend(context.mutations.borrow_mut().template_mutations.drain(..));
  436. mutations.extend(context.mutations.borrow_mut().drain(..));
  437. mutations.push(Mutation::ReplaceWith {
  438. id: context.placeholder.get().unwrap(),
  439. m: 1,
  440. })
  441. }
  442. // Next, diff any dirty scopes
  443. // We choose not to poll the deadline since we complete pretty quickly anyways
  444. if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
  445. self.dirty_scopes.remove(&dirty);
  446. // if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update
  447. if !self.is_scope_suspended(dirty.id) {
  448. self.run_scope(dirty.id);
  449. self.diff_scope(&mut mutations, dirty.id);
  450. }
  451. }
  452. // Wait for suspense, or a deadline
  453. if self.dirty_scopes.is_empty() {
  454. if self.scheduler.leaves.borrow().is_empty() {
  455. return mutations;
  456. }
  457. let (work, deadline) = (self.wait_for_work(), &mut deadline);
  458. pin_mut!(work);
  459. if let Either::Left((_, _)) = select(deadline, work).await {
  460. return mutations;
  461. }
  462. }
  463. }
  464. }
  465. fn mark_dirty_scope(&mut self, scope_id: ScopeId) {
  466. let scopes = &self.scopes;
  467. if let Some(scope) = scopes.get_scope(scope_id) {
  468. let height = scope.height;
  469. let id = scope_id.0;
  470. if let Err(index) = self.dirty_scopes.binary_search_by(|new| {
  471. let scope = scopes.get_scope(*new).unwrap();
  472. let new_height = scope.height;
  473. let new_id = &scope.scope_id();
  474. height.cmp(&new_height).then(new_id.0.cmp(&id))
  475. }) {
  476. self.dirty_scopes.insert(index, scope_id);
  477. log::info!("mark_dirty_scope: {:?}", self.dirty_scopes);
  478. }
  479. }
  480. }
  481. }
  482. impl Drop for VirtualDom {
  483. fn drop(&mut self) {
  484. // self.drop_scope(ScopeId(0));
  485. }
  486. }