virtual_dom.rs 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. //! # VirtualDom Implementation for Rust
  2. //!
  3. //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
  4. use crate::innerlude::*;
  5. use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
  6. use futures_util::{Future, StreamExt};
  7. use fxhash::FxHashSet;
  8. use indexmap::IndexSet;
  9. use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::Arc, task::Poll};
  10. /// A virtual node s ystem that progresses user events and diffs UI trees.
  11. ///
  12. ///
  13. /// ## Guide
  14. ///
  15. /// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
  16. ///
  17. /// ```rust, ignore
  18. /// #[derive(Props, PartialEq)]
  19. /// struct AppProps {
  20. /// title: String
  21. /// }
  22. ///
  23. /// fn App(cx: Scope<AppProps>) -> Element {
  24. /// cx.render(rsx!(
  25. /// div {"hello, {cx.props.title}"}
  26. /// ))
  27. /// }
  28. /// ```
  29. ///
  30. /// Components may be composed to make complex apps.
  31. ///
  32. /// ```rust, ignore
  33. /// fn App(cx: Scope<AppProps>) -> Element {
  34. /// cx.render(rsx!(
  35. /// NavBar { routes: ROUTES }
  36. /// Title { "{cx.props.title}" }
  37. /// Footer {}
  38. /// ))
  39. /// }
  40. /// ```
  41. ///
  42. /// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
  43. /// draw the UI.
  44. ///
  45. /// ```rust, ignore
  46. /// let mut vdom = VirtualDom::new(App);
  47. /// let edits = vdom.rebuild();
  48. /// ```
  49. ///
  50. /// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
  51. ///
  52. /// ```rust, ignore
  53. /// let channel = vdom.get_scheduler_channel();
  54. /// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
  55. /// // ...
  56. /// }))
  57. /// ```
  58. ///
  59. /// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
  60. ///
  61. /// ```rust, ignore
  62. /// vdom.wait_for_work().await;
  63. /// ```
  64. ///
  65. /// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and
  66. /// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
  67. /// handled by the renderer.
  68. ///
  69. /// ```rust, ignore
  70. /// let mutations = vdom.work_with_deadline(|| false);
  71. /// for edit in mutations {
  72. /// apply(edit);
  73. /// }
  74. /// ```
  75. ///
  76. /// ## Building an event loop around Dioxus:
  77. ///
  78. /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
  79. ///
  80. /// ```rust, ignore
  81. /// fn App(cx: Scope<()>) -> Element {
  82. /// cx.render(rsx!{
  83. /// div { "Hello World" }
  84. /// })
  85. /// }
  86. ///
  87. /// async fn main() {
  88. /// let mut dom = VirtualDom::new(App);
  89. ///
  90. /// let mut inital_edits = dom.rebuild();
  91. /// apply_edits(inital_edits);
  92. ///
  93. /// loop {
  94. /// dom.wait_for_work().await;
  95. /// let frame_timeout = TimeoutFuture::new(Duration::from_millis(16));
  96. /// let deadline = || (&mut frame_timeout).now_or_never();
  97. /// let edits = dom.run_with_deadline(deadline).await;
  98. /// apply_edits(edits);
  99. /// }
  100. /// }
  101. /// ```
  102. pub struct VirtualDom {
  103. scopes: ScopeArena,
  104. pending_messages: VecDeque<SchedulerMsg>,
  105. dirty_scopes: IndexSet<ScopeId>,
  106. channel: (
  107. UnboundedSender<SchedulerMsg>,
  108. UnboundedReceiver<SchedulerMsg>,
  109. ),
  110. }
  111. // Methods to create the VirtualDom
  112. impl VirtualDom {
  113. /// Create a new VirtualDom with a component that does not have special props.
  114. ///
  115. /// # Description
  116. ///
  117. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  118. ///
  119. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  120. /// to toss out the entire tree.
  121. ///
  122. ///
  123. /// # Example
  124. /// ```rust, ignore
  125. /// fn Example(cx: Scope<()>) -> Element {
  126. /// cx.render(rsx!( div { "hello world" } ))
  127. /// }
  128. ///
  129. /// let dom = VirtualDom::new(Example);
  130. /// ```
  131. ///
  132. /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
  133. pub fn new(root: Component<()>) -> Self {
  134. Self::new_with_props(root, ())
  135. }
  136. /// Create a new VirtualDom with the given properties for the root component.
  137. ///
  138. /// # Description
  139. ///
  140. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  141. ///
  142. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  143. /// to toss out the entire tree.
  144. ///
  145. ///
  146. /// # Example
  147. /// ```rust, ignore
  148. /// #[derive(PartialEq, Props)]
  149. /// struct SomeProps {
  150. /// name: &'static str
  151. /// }
  152. ///
  153. /// fn Example(cx: Scope<SomeProps>) -> Element {
  154. /// cx.render(rsx!{ div{ "hello {cx.props.name}" } })
  155. /// }
  156. ///
  157. /// let dom = VirtualDom::new(Example);
  158. /// ```
  159. ///
  160. /// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
  161. ///
  162. /// ```rust, ignore
  163. /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
  164. /// let mutations = dom.rebuild();
  165. /// ```
  166. pub fn new_with_props<P>(root: Component<P>, root_props: P) -> Self
  167. where
  168. P: 'static,
  169. {
  170. Self::new_with_props_and_scheduler(
  171. root,
  172. root_props,
  173. futures_channel::mpsc::unbounded::<SchedulerMsg>(),
  174. )
  175. }
  176. /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
  177. ///
  178. /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
  179. /// VirtualDom to be created just to retrieve its channel receiver.
  180. ///
  181. /// ```rust
  182. /// let channel = futures_channel::mpsc::unbounded();
  183. /// let dom = VirtualDom::new_with_scheduler(Example, (), channel);
  184. /// ```
  185. pub fn new_with_props_and_scheduler<P: 'static>(
  186. root: Component<P>,
  187. root_props: P,
  188. channel: (
  189. UnboundedSender<SchedulerMsg>,
  190. UnboundedReceiver<SchedulerMsg>,
  191. ),
  192. ) -> Self {
  193. let scopes = ScopeArena::new(channel.0.clone());
  194. scopes.new_with_key(
  195. root as *const _,
  196. Box::new(VComponentProps {
  197. props: root_props,
  198. memo: |_a, _b| unreachable!("memo on root will neve be run"),
  199. render_fn: root,
  200. }),
  201. None,
  202. ElementId(0),
  203. 0,
  204. );
  205. Self {
  206. scopes,
  207. channel,
  208. dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
  209. pending_messages: VecDeque::new(),
  210. }
  211. }
  212. /// Get the [`Scope`] for the root component.
  213. ///
  214. /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
  215. /// directly.
  216. ///
  217. /// This method is equivalent to calling `get_scope(ScopeId(0))`
  218. ///
  219. /// # Example
  220. ///
  221. /// ```rust
  222. /// let mut dom = VirtualDom::new(example);
  223. /// dom.rebuild();
  224. ///
  225. ///
  226. /// ```
  227. pub fn base_scope(&self) -> &ScopeState {
  228. self.get_scope(ScopeId(0)).unwrap()
  229. }
  230. /// Get the [`ScopeState`] for a component given its [`ScopeId`]
  231. ///
  232. /// # Example
  233. ///
  234. ///
  235. ///
  236. pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
  237. self.scopes.get_scope(id)
  238. }
  239. /// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
  240. ///
  241. /// # Example
  242. ///
  243. /// ```rust, ignore
  244. /// let dom = VirtualDom::new(App);
  245. /// let sender = dom.get_scheduler_channel();
  246. /// ```
  247. pub fn get_scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
  248. self.channel.0.clone()
  249. }
  250. /// Add a new message to the scheduler queue directly.
  251. ///
  252. ///
  253. /// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to
  254. /// call `get_schedule_channel` and then `send`.
  255. ///
  256. /// # Example
  257. /// ```rust, ignore
  258. /// let dom = VirtualDom::new(App);
  259. /// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
  260. /// ```
  261. pub fn handle_message(&mut self, msg: SchedulerMsg) {
  262. if self.channel.0.unbounded_send(msg).is_ok() {
  263. self.process_all_messages();
  264. }
  265. }
  266. /// Check if the [`VirtualDom`] has any pending updates or work to be done.
  267. ///
  268. /// # Example
  269. ///
  270. /// ```rust, ignore
  271. /// let dom = VirtualDom::new(App);
  272. ///
  273. /// // the dom is "dirty" when it is started and must be rebuilt to get the first render
  274. /// assert!(dom.has_any_work());
  275. /// ```
  276. pub fn has_work(&self) -> bool {
  277. !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
  278. }
  279. /// Wait for the scheduler to have any work.
  280. ///
  281. /// This method polls the internal future queue *and* the scheduler channel.
  282. /// To add work to the VirtualDom, insert a message via the scheduler channel.
  283. ///
  284. /// This lets us poll async tasks during idle periods without blocking the main thread.
  285. ///
  286. /// # Example
  287. ///
  288. /// ```rust, ignore
  289. /// let dom = VirtualDom::new(App);
  290. /// let sender = dom.get_scheduler_channel();
  291. /// ```
  292. pub async fn wait_for_work(&mut self) {
  293. loop {
  294. if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
  295. break;
  296. }
  297. if self.pending_messages.is_empty() {
  298. if self.scopes.tasks.has_tasks() {
  299. use futures_util::future::{select, Either};
  300. match select(PollTasks(&mut self.scopes), self.channel.1.next()).await {
  301. Either::Left((_, _)) => {}
  302. Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
  303. }
  304. } else {
  305. self.pending_messages
  306. .push_front(self.channel.1.next().await.unwrap());
  307. }
  308. }
  309. // Move all the messages into the queue
  310. self.process_all_messages();
  311. }
  312. }
  313. /// Manually kick the VirtualDom to process any
  314. pub fn process_all_messages(&mut self) {
  315. // clear out the scheduler queue
  316. while let Ok(Some(msg)) = self.channel.1.try_next() {
  317. self.pending_messages.push_front(msg);
  318. }
  319. // process all the messages pulled from the queue
  320. while let Some(msg) = self.pending_messages.pop_back() {
  321. self.process_message(msg);
  322. }
  323. }
  324. pub fn process_message(&mut self, msg: SchedulerMsg) {
  325. match msg {
  326. SchedulerMsg::NewTask(_id) => {
  327. // uh, not sure? I think end up re-polling it anyways
  328. }
  329. SchedulerMsg::Event(event) => {
  330. if let Some(element) = event.element {
  331. self.scopes.call_listener_with_bubbling(event, element);
  332. }
  333. }
  334. SchedulerMsg::Immediate(s) => {
  335. self.dirty_scopes.insert(s);
  336. }
  337. }
  338. }
  339. /// Run the virtualdom with a deadline.
  340. ///
  341. /// This method will perform any outstanding diffing work and try to return as many mutations as possible before the
  342. /// deadline is reached. This method accepts a closure that returns `true` if the deadline has been reached. To wrap
  343. /// your future into a deadline, consider the `now_or_never` method from `future_utils`.
  344. ///
  345. /// ```rust, ignore
  346. /// let mut vdom = VirtualDom::new(App);
  347. ///
  348. /// let timeout = TimeoutFuture::from_ms(16);
  349. /// let deadline = || (&mut timeout).now_or_never();
  350. ///
  351. /// let mutations = vdom.work_with_deadline(deadline);
  352. /// ```
  353. ///
  354. /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
  355. /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
  356. ///
  357. /// If the work is not finished by the deadline, Dioxus will store it for later and return when work_with_deadline
  358. /// is called again. This means you can ensure some level of free time on the VirtualDom's thread during the work phase.
  359. ///
  360. /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
  361. /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
  362. /// entirely jank-free applications that perform a ton of work.
  363. ///
  364. /// In general use, Dioxus is plenty fast enough to not need to worry about this.
  365. ///
  366. /// # Example
  367. ///
  368. /// ```rust, ignore
  369. /// fn App(cx: Scope<()>) -> Element {
  370. /// cx.render(rsx!( div {"hello"} ))
  371. /// }
  372. ///
  373. /// let mut dom = VirtualDom::new(App);
  374. ///
  375. /// loop {
  376. /// let mut timeout = TimeoutFuture::from_ms(16);
  377. /// let deadline = move || (&mut timeout).now_or_never();
  378. ///
  379. /// let mutations = dom.run_with_deadline(deadline).await;
  380. ///
  381. /// apply_mutations(mutations);
  382. /// }
  383. /// ```
  384. pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
  385. let mut committed_mutations = vec![];
  386. while !self.dirty_scopes.is_empty() {
  387. let scopes = &self.scopes;
  388. let mut diff_state = DiffState::new(scopes);
  389. let mut ran_scopes = FxHashSet::default();
  390. // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
  391. self.dirty_scopes
  392. .retain(|id| scopes.get_scope(*id).is_some());
  393. self.dirty_scopes.sort_by(|a, b| {
  394. let h1 = scopes.get_scope(*a).unwrap().height;
  395. let h2 = scopes.get_scope(*b).unwrap().height;
  396. h1.cmp(&h2).reverse()
  397. });
  398. if let Some(scopeid) = self.dirty_scopes.pop() {
  399. if !ran_scopes.contains(&scopeid) {
  400. ran_scopes.insert(scopeid);
  401. self.scopes.run_scope(scopeid);
  402. let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
  403. diff_state.stack.push(DiffInstruction::Diff { new, old });
  404. diff_state.stack.scope_stack.push(scopeid);
  405. let scope = scopes.get_scope(scopeid).unwrap();
  406. diff_state.stack.element_stack.push(scope.container);
  407. }
  408. }
  409. if diff_state.work(&mut deadline) {
  410. let DiffState { mutations, .. } = diff_state;
  411. for scope in &mutations.dirty_scopes {
  412. self.dirty_scopes.remove(scope);
  413. }
  414. committed_mutations.push(mutations);
  415. } else {
  416. // leave the work in an incomplete state
  417. //
  418. // todo: we should store the edits and re-apply them later
  419. // for now, we just dump the work completely (threadsafe)
  420. return committed_mutations;
  421. }
  422. }
  423. committed_mutations
  424. }
  425. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
  426. ///
  427. /// The diff machine expects the RealDom's stack to be the root of the application.
  428. ///
  429. /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
  430. /// root component will be ran once and then diffed. All updates will flow out as mutations.
  431. ///
  432. /// All state stored in components will be completely wiped away.
  433. ///
  434. /// # Example
  435. /// ```rust, ignore
  436. /// static App: Component<()> = |cx, props| cx.render(rsx!{ "hello world" });
  437. /// let mut dom = VirtualDom::new();
  438. /// let edits = dom.rebuild();
  439. ///
  440. /// apply_edits(edits);
  441. /// ```
  442. pub fn rebuild(&mut self) -> Mutations {
  443. let scope_id = ScopeId(0);
  444. let mut diff_state = DiffState::new(&self.scopes);
  445. self.scopes.run_scope(scope_id);
  446. diff_state
  447. .stack
  448. .create_node(self.scopes.fin_head(scope_id), MountType::Append);
  449. diff_state.stack.element_stack.push(ElementId(0));
  450. diff_state.stack.scope_stack.push(scope_id);
  451. diff_state.work(|| false);
  452. diff_state.mutations
  453. }
  454. /// Compute a manual diff of the VirtualDom between states.
  455. ///
  456. /// This can be useful when state inside the DOM is remotely changed from the outside, but not propagated as an event.
  457. ///
  458. /// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
  459. /// to force an update of the DOM when the state of the app is changed outside of the app.
  460. ///
  461. /// To force a reflow of the entire VirtualDom, use `ScopeId(0)` as the scope_id.
  462. ///
  463. /// # Example
  464. /// ```rust, ignore
  465. /// #[derive(PartialEq, Props)]
  466. /// struct AppProps {
  467. /// value: Shared<&'static str>,
  468. /// }
  469. ///
  470. /// static App: Component<AppProps> = |cx, props|{
  471. /// let val = cx.value.borrow();
  472. /// cx.render(rsx! { div { "{val}" } })
  473. /// };
  474. ///
  475. /// let value = Rc::new(RefCell::new("Hello"));
  476. /// let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone(), });
  477. ///
  478. /// let _ = dom.rebuild();
  479. ///
  480. /// *value.borrow_mut() = "goodbye";
  481. ///
  482. /// let edits = dom.diff();
  483. /// ```
  484. pub fn hard_diff(&mut self, scope_id: ScopeId) -> Mutations {
  485. let mut diff_machine = DiffState::new(&self.scopes);
  486. self.scopes.run_scope(scope_id);
  487. let (old, new) = (
  488. diff_machine.scopes.wip_head(scope_id),
  489. diff_machine.scopes.fin_head(scope_id),
  490. );
  491. diff_machine.force_diff = true;
  492. diff_machine.stack.push(DiffInstruction::Diff { old, new });
  493. diff_machine.stack.scope_stack.push(scope_id);
  494. let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
  495. diff_machine.stack.element_stack.push(scope.container);
  496. diff_machine.work(|| false);
  497. diff_machine.mutations
  498. }
  499. /// Renders an `rsx` call into the Base Scope's allocator.
  500. ///
  501. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
  502. ///
  503. /// ```rust
  504. /// fn Base(cx: Scope<()>) -> Element {
  505. /// rsx!(cx, div {})
  506. /// }
  507. ///
  508. /// let dom = VirtualDom::new(Base);
  509. /// let nodes = dom.render_nodes(rsx!("div"));
  510. /// ```
  511. pub fn render_vnodes<'a>(&'a self, lazy_nodes: LazyNodes<'a, '_>) -> &'a VNode<'a> {
  512. let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
  513. let frame = scope.wip_frame();
  514. let factory = NodeFactory { bump: &frame.bump };
  515. let node = lazy_nodes.call(factory);
  516. frame.bump.alloc(node)
  517. }
  518. /// Renders an `rsx` call into the Base Scope's allocator.
  519. ///
  520. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
  521. ///
  522. /// ```rust
  523. /// fn Base(cx: Scope<()>) -> Element {
  524. /// rsx!(cx, div {})
  525. /// }
  526. ///
  527. /// let dom = VirtualDom::new(Base);
  528. /// let nodes = dom.render_nodes(rsx!("div"));
  529. /// ```
  530. pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
  531. let mut machine = DiffState::new(&self.scopes);
  532. machine.stack.push(DiffInstruction::Diff { new, old });
  533. machine.stack.element_stack.push(ElementId(0));
  534. machine.stack.scope_stack.push(ScopeId(0));
  535. machine.work(|| false);
  536. machine.mutations
  537. }
  538. /// Renders an `rsx` call into the Base Scope's allocator.
  539. ///
  540. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
  541. ///
  542. ///
  543. /// ```rust
  544. /// fn Base(cx: Scope<()>) -> Element {
  545. /// rsx!(cx, div {})
  546. /// }
  547. ///
  548. /// let dom = VirtualDom::new(Base);
  549. /// let nodes = dom.render_nodes(rsx!("div"));
  550. /// ```
  551. pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
  552. let mut machine = DiffState::new(&self.scopes);
  553. machine.stack.element_stack.push(ElementId(0));
  554. machine
  555. .stack
  556. .create_node(self.render_vnodes(nodes), MountType::Append);
  557. machine.work(|| false);
  558. machine.mutations
  559. }
  560. /// Renders an `rsx` call into the Base Scopes's arena.
  561. ///
  562. /// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test.
  563. ///
  564. ///
  565. /// ```rust
  566. /// fn Base(cx: Scope<()>) -> Element {
  567. /// rsx!(cx, div {})
  568. /// }
  569. ///
  570. /// let dom = VirtualDom::new(Base);
  571. /// let nodes = dom.render_nodes(rsx!("div"));
  572. /// ```
  573. pub fn diff_lazynodes<'a>(
  574. &'a self,
  575. left: LazyNodes<'a, '_>,
  576. right: LazyNodes<'a, '_>,
  577. ) -> (Mutations<'a>, Mutations<'a>) {
  578. let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
  579. let mut create = DiffState::new(&self.scopes);
  580. create.stack.scope_stack.push(ScopeId(0));
  581. create.stack.element_stack.push(ElementId(0));
  582. create.stack.create_node(old, MountType::Append);
  583. create.work(|| false);
  584. let mut edit = DiffState::new(&self.scopes);
  585. edit.stack.scope_stack.push(ScopeId(0));
  586. edit.stack.element_stack.push(ElementId(0));
  587. edit.stack.push(DiffInstruction::Diff { old, new });
  588. edit.work(|| false);
  589. (create.mutations, edit.mutations)
  590. }
  591. }
  592. /*
  593. Scopes and ScopeArenas are never dropped internally.
  594. An app will always occupy as much memory as its biggest form.
  595. This means we need to handle all specifics of drop *here*. It's easier
  596. to reason about centralizing all the drop logic in one spot rather than scattered in each module.
  597. Broadly speaking, we want to use the remove_nodes method to clean up *everything*
  598. This will drop listeners, borrowed props, and hooks for all components.
  599. We need to do this in the correct order - nodes at the very bottom must be dropped first to release
  600. the borrow chain.
  601. Once the contents of the tree have been cleaned up, we can finally clean up the
  602. memory used by ScopeState itself.
  603. questions:
  604. should we build a vcomponent for the root?
  605. - probably - yes?
  606. - store the vcomponent in the root dom
  607. - 1: Use remove_nodes to use the ensure_drop_safety pathway to safely drop the tree
  608. - 2: Drop the ScopeState itself
  609. */
  610. impl Drop for VirtualDom {
  611. fn drop(&mut self) {
  612. // the best way to drop the dom is to replace the root scope with a dud
  613. // the diff infrastructure will then finish the rest
  614. let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
  615. // todo: move the remove nodes method onto scopearena
  616. // this will clear *all* scopes *except* the root scope
  617. let mut machine = DiffState::new(&self.scopes);
  618. machine.remove_nodes([scope.root_node()], false);
  619. // Now, clean up the root scope
  620. // safety: there are no more references to the root scope
  621. let scope = unsafe { &mut *self.scopes.get_scope_raw(ScopeId(0)).unwrap() };
  622. scope.reset();
  623. // make sure there are no "live" components
  624. for (_, scopeptr) in self.scopes.scopes.get_mut().drain() {
  625. // safety: all scopes were made in the bump's allocator
  626. // They are never dropped until now. The only way to drop is through Box.
  627. let scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
  628. drop(scope);
  629. }
  630. for scopeptr in self.scopes.free_scopes.get_mut().drain(..) {
  631. // safety: all scopes were made in the bump's allocator
  632. // They are never dropped until now. The only way to drop is through Box.
  633. let mut scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
  634. scope.reset();
  635. drop(scope);
  636. }
  637. }
  638. }
  639. struct PollTasks<'a>(&'a mut ScopeArena);
  640. impl<'a> Future for PollTasks<'a> {
  641. type Output = ();
  642. fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
  643. let mut any_pending = false;
  644. let mut tasks = self.0.tasks.tasks.borrow_mut();
  645. let mut to_remove = vec![];
  646. // this would be better served by retain
  647. for (id, task) in tasks.iter_mut() {
  648. if task.as_mut().poll(cx).is_ready() {
  649. to_remove.push(*id);
  650. } else {
  651. any_pending = true;
  652. }
  653. }
  654. for id in to_remove {
  655. tasks.remove(&id);
  656. }
  657. // Resolve the future if any singular task is ready
  658. match any_pending {
  659. true => Poll::Pending,
  660. false => Poll::Ready(()),
  661. }
  662. }
  663. }
  664. #[derive(Debug)]
  665. pub enum SchedulerMsg {
  666. // events from the host
  667. Event(UserEvent),
  668. // setstate
  669. Immediate(ScopeId),
  670. // an async task pushed from an event handler (or just spawned)
  671. NewTask(ScopeId),
  672. }
  673. /// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
  674. ///
  675. /// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
  676. /// where each listener is checked and fired if the event name matches.
  677. ///
  678. /// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
  679. /// attempting to downcast the event data.
  680. ///
  681. /// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
  682. /// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
  683. ///
  684. /// # Example
  685. /// ```rust
  686. /// fn App(cx: Scope<()>) -> Element {
  687. /// rsx!(cx, div {
  688. /// onclick: move |_| println!("Clicked!")
  689. /// })
  690. /// }
  691. ///
  692. /// let mut dom = VirtualDom::new(App);
  693. /// let mut scheduler = dom.get_scheduler_channel();
  694. /// scheduler.unbounded_send(SchedulerMsg::UiEvent(
  695. /// UserEvent {
  696. /// scope_id: None,
  697. /// priority: EventPriority::Medium,
  698. /// name: "click",
  699. /// element: Some(ElementId(0)),
  700. /// data: Arc::new(ClickEvent { .. })
  701. /// }
  702. /// )).unwrap();
  703. /// ```
  704. #[derive(Debug)]
  705. pub struct UserEvent {
  706. /// The originator of the event trigger if available
  707. pub scope_id: Option<ScopeId>,
  708. /// The priority of the event to be scheduled around ongoing work
  709. pub priority: EventPriority,
  710. /// The optional real node associated with the trigger
  711. pub element: Option<ElementId>,
  712. /// The event type IE "onclick" or "onmouseover"
  713. pub name: &'static str,
  714. /// The event data to be passed onto the event handler
  715. pub data: Arc<dyn Any + Send + Sync>,
  716. }
  717. /// Priority of Event Triggers.
  718. ///
  719. /// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
  720. /// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
  721. /// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
  722. ///
  723. /// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
  724. ///
  725. /// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
  726. /// we keep it simple, and just use a 3-tier priority system.
  727. ///
  728. /// - NoPriority = 0
  729. /// - LowPriority = 1
  730. /// - NormalPriority = 2
  731. /// - UserBlocking = 3
  732. /// - HighPriority = 4
  733. /// - ImmediatePriority = 5
  734. ///
  735. /// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
  736. /// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
  737. /// flushed before proceeding. Multiple discrete events is highly unlikely, though.
  738. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
  739. pub enum EventPriority {
  740. /// Work that must be completed during the EventHandler phase.
  741. ///
  742. /// Currently this is reserved for controlled inputs.
  743. Immediate = 3,
  744. /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
  745. ///
  746. /// This is typically reserved for things like user interaction.
  747. ///
  748. /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
  749. High = 2,
  750. /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
  751. /// than "High Priority" events and will take precedence over low priority events.
  752. ///
  753. /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
  754. ///
  755. /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
  756. Medium = 1,
  757. /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
  758. /// advanced to the front of the work queue until completed.
  759. ///
  760. /// The primary user of Low Priority work is the asynchronous work system (Suspense).
  761. ///
  762. /// This is considered "idle" work or "background" work.
  763. Low = 0,
  764. }
  765. pub struct FragmentProps<'a>(Element<'a>);
  766. pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
  767. impl<'a> FragmentBuilder<'a, false> {
  768. pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
  769. FragmentBuilder(children)
  770. }
  771. }
  772. impl<'a, const A: bool> FragmentBuilder<'a, A> {
  773. pub fn build(self) -> FragmentProps<'a> {
  774. FragmentProps(self.0)
  775. }
  776. }
  777. /// Access the children elements passed into the component
  778. ///
  779. /// This enables patterns where a component is passed children from its parent.
  780. ///
  781. /// ## Details
  782. ///
  783. /// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
  784. /// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
  785. /// on the props that takes Context.
  786. ///
  787. /// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
  788. /// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
  789. /// props are valid for the static lifetime.
  790. ///
  791. /// ## Example
  792. ///
  793. /// ```rust, ignore
  794. /// fn App(cx: Scope<()>) -> Element {
  795. /// cx.render(rsx!{
  796. /// CustomCard {
  797. /// h1 {}2
  798. /// p {}
  799. /// }
  800. /// })
  801. /// }
  802. ///
  803. /// #[derive(PartialEq, Props)]
  804. /// struct CardProps {
  805. /// children: Element
  806. /// }
  807. ///
  808. /// fn CustomCard(cx: Scope<CardProps>) -> Element {
  809. /// cx.render(rsx!{
  810. /// div {
  811. /// h1 {"Title card"}
  812. /// {cx.props.children}
  813. /// }
  814. /// })
  815. /// }
  816. /// ```
  817. impl<'a> Properties for FragmentProps<'a> {
  818. type Builder = FragmentBuilder<'a, false>;
  819. const IS_STATIC: bool = false;
  820. fn builder() -> Self::Builder {
  821. FragmentBuilder(None)
  822. }
  823. unsafe fn memoize(&self, _other: &Self) -> bool {
  824. false
  825. }
  826. }
  827. /// Create inline fragments using Component syntax.
  828. ///
  829. /// ## Details
  830. ///
  831. /// Fragments capture a series of children without rendering extra nodes.
  832. ///
  833. /// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
  834. /// a key is needed to identify each item.
  835. ///
  836. /// ## Example
  837. ///
  838. /// ```rust, ignore
  839. /// rsx!{
  840. /// Fragment { key: "abc" }
  841. /// }
  842. /// ```
  843. ///
  844. /// ## Usage
  845. ///
  846. /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
  847. /// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
  848. ///
  849. /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
  850. ///
  851. /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
  852. #[allow(non_upper_case_globals, non_snake_case)]
  853. pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
  854. let i = cx.props.0.as_ref().map(|f| f.decouple());
  855. cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
  856. }
  857. /// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
  858. /// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
  859. /// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
  860. ///
  861. /// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
  862. /// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
  863. /// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
  864. ///
  865. /// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
  866. /// this is only safe if the props are 'static - otherwise you might borrow references after-free.
  867. ///
  868. /// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
  869. /// we advise the use of smart pointers in cases where memoization is important.
  870. ///
  871. /// ## Example
  872. ///
  873. /// For props that are 'static:
  874. /// ```rust, ignore ignore
  875. /// #[derive(Props, PartialEq)]
  876. /// struct MyProps {
  877. /// data: String
  878. /// }
  879. /// ```
  880. ///
  881. /// For props that borrow:
  882. ///
  883. /// ```rust, ignore ignore
  884. /// #[derive(Props)]
  885. /// struct MyProps<'a >{
  886. /// data: &'a str
  887. /// }
  888. /// ```
  889. pub trait Properties: Sized {
  890. type Builder;
  891. const IS_STATIC: bool;
  892. fn builder() -> Self::Builder;
  893. /// Memoization can only happen if the props are valid for the 'static lifetime
  894. ///
  895. /// # Safety
  896. /// The user must know if their props are static, but if they make a mistake, UB happens
  897. /// Therefore it's unsafe to memoize.
  898. unsafe fn memoize(&self, other: &Self) -> bool;
  899. }
  900. impl Properties for () {
  901. type Builder = EmptyBuilder;
  902. const IS_STATIC: bool = true;
  903. fn builder() -> Self::Builder {
  904. EmptyBuilder {}
  905. }
  906. unsafe fn memoize(&self, _other: &Self) -> bool {
  907. true
  908. }
  909. }
  910. // We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
  911. // that the macros use to anonymously complete prop construction.
  912. pub struct EmptyBuilder;
  913. impl EmptyBuilder {
  914. pub fn build(self) {}
  915. }
  916. /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
  917. /// to initialize a component's props.
  918. pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
  919. T::builder()
  920. }