virtual_dom.rs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. // use crate::{changelist::EditList, nodes::VNode};
  2. use crate::innerlude::*;
  3. use bumpalo::Bump;
  4. use generational_arena::Arena;
  5. use std::{
  6. any::TypeId,
  7. borrow::BorrowMut,
  8. cell::{RefCell, UnsafeCell},
  9. collections::{vec_deque, VecDeque},
  10. future::Future,
  11. marker::PhantomData,
  12. rc::Rc,
  13. sync::atomic::AtomicUsize,
  14. };
  15. /// An integrated virtual node system that progresses events and diffs UI trees.
  16. /// Differences are converted into patches which a renderer can use to draw the UI.
  17. pub struct VirtualDom {
  18. /// All mounted components are arena allocated to make additions, removals, and references easy to work with
  19. /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
  20. pub(crate) components: Arena<Scope>,
  21. /// The index of the root component.
  22. /// Will not be ready if the dom is fresh
  23. base_scope: ScopeIdx,
  24. event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
  25. // todo: encapsulate more state into this so we can better reuse it
  26. diff_bump: Bump,
  27. #[doc(hidden)]
  28. _root_prop_type: std::any::TypeId,
  29. }
  30. impl VirtualDom {
  31. /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
  32. ///
  33. /// This means that the root component must either consumes its own context, or statics are used to generate the page.
  34. /// The root component can access things like routing in its context.
  35. pub fn new(root: FC<()>) -> Self {
  36. Self::new_with_props(root, ())
  37. }
  38. /// Start a new VirtualDom instance with a dependent props.
  39. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  40. ///
  41. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  42. /// to toss out the entire tree.
  43. pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
  44. let mut components = Arena::new();
  45. let event_queue = Rc::new(RefCell::new(VecDeque::new()));
  46. // Create a reference to the component in the arena
  47. // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
  48. // This puts the dom in a usable state on creation, rather than being potentially invalid
  49. let base_scope =
  50. components.insert_with(|id| Scope::new::<_, P>(root, root_props, id, None));
  51. // evaluate the component, pushing any updates its generates into the lifecycle queue
  52. // todo!
  53. let _root_prop_type = TypeId::of::<P>();
  54. let diff_bump = Bump::new();
  55. Self {
  56. components,
  57. base_scope,
  58. event_queue,
  59. diff_bump,
  60. _root_prop_type,
  61. }
  62. }
  63. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
  64. ///
  65. ///
  66. pub fn rebuild(&mut self) -> Result<EditList<'_>> {
  67. // Reset and then build a new diff machine
  68. // The previous edit list cannot be around while &mut is held
  69. // Make sure variance doesnt break this
  70. self.diff_bump.reset();
  71. let mut diff_machine = DiffMachine::new(&self.diff_bump);
  72. // this is still a WIP
  73. // we'll need to re-fecth all the scopes that were changed and build the diff machine
  74. // fetch the component again
  75. let component = self
  76. .components
  77. .get_mut(self.base_scope)
  78. .expect("Root should always exist");
  79. component.run::<()>();
  80. diff_machine.diff_node(component.old_frame(), component.new_frame());
  81. Ok(diff_machine.consume())
  82. }
  83. /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
  84. ///
  85. /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
  86. /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
  87. /// change list.
  88. ///
  89. /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
  90. /// listeners.
  91. ///
  92. /// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
  93. /// executor and handlers for suspense as show in the example.
  94. ///
  95. /// ```ignore
  96. /// let (sender, receiver) = channel::new();
  97. /// sender.send(EventTrigger::start());
  98. ///
  99. /// let mut dom = VirtualDom::new();
  100. /// dom.suspense_handler(|event| sender.send(event));
  101. ///
  102. /// while let Ok(diffs) = dom.progress_with_event(receiver.recv().await) {
  103. /// render(diffs);
  104. /// }
  105. ///
  106. /// ```
  107. pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
  108. let EventTrigger {
  109. component_id,
  110. listener_id,
  111. event: source,
  112. } = event;
  113. let component = self
  114. .components
  115. .get_mut(component_id)
  116. .expect("Component should exist if an event was triggered");
  117. log::debug!("list: {}", component.listeners.len());
  118. let listener = unsafe {
  119. component
  120. .listeners
  121. .get(listener_id as usize)
  122. .expect("Listener should exist if it was triggered")
  123. .as_ref()
  124. }
  125. .unwrap();
  126. // Run the callback with the user event
  127. listener(source);
  128. // Reset and then build a new diff machine
  129. // The previous edit list cannot be around while &mut is held
  130. // Make sure variance doesnt break this
  131. self.diff_bump.reset();
  132. let mut diff_machine = DiffMachine::new(&self.diff_bump);
  133. // this is still a WIP
  134. // we'll need to re-fecth all the scopes that were changed and build the diff machine
  135. // fetch the component again
  136. // let component = self
  137. // .components
  138. // .get_mut(self.base_scope)
  139. // .expect("Root should always exist");
  140. component.run::<()>();
  141. diff_machine.diff_node(component.old_frame(), component.new_frame());
  142. // diff_machine.diff_node(
  143. // component.old_frame(),
  144. // component.new_frame(),
  145. // Some(self.base_scope),
  146. // );
  147. Ok(diff_machine.consume())
  148. // Err(crate::error::Error::NoEvent)
  149. // Mark dirty components. Descend from the highest node until all dirty nodes are updated.
  150. // let mut affected_components = Vec::new();
  151. // while let Some(event) = self.pop_event() {
  152. // if let Some(component_idx) = event.index() {
  153. // affected_components.push(component_idx);
  154. // }
  155. // self.process_lifecycle(event)?;
  156. // }
  157. // todo!()
  158. }
  159. /// Using mutable access to the Virtual Dom, progress a given lifecycle event
  160. fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> {
  161. match event_type {
  162. // Component needs to be mounted to the virtual dom
  163. LifecycleType::Mount {
  164. to: _,
  165. under: _,
  166. props: _,
  167. } => {}
  168. // The parent for this component generated new props and the component needs update
  169. LifecycleType::PropsChanged {
  170. props: _,
  171. component: _,
  172. } => {}
  173. // Component was messaged via the internal subscription service
  174. LifecycleType::Callback { component: _ } => {}
  175. }
  176. Ok(())
  177. }
  178. /// Pop the top event of the internal lifecycle event queu
  179. pub fn pop_event(&self) -> Option<LifecycleEvent> {
  180. self.event_queue.as_ref().borrow_mut().pop_front()
  181. }
  182. /// With access to the virtual dom, schedule an update to the Root component's props.
  183. /// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event
  184. /// back into the event system to get an edit list.
  185. pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
  186. // Ensure the props match
  187. if TypeId::of::<P>() != self._root_prop_type {
  188. return Err(Error::WrongProps);
  189. }
  190. Ok(LifecycleEvent {
  191. event_type: LifecycleType::PropsChanged {
  192. props: Box::new(new_props),
  193. component: self.base_scope,
  194. },
  195. })
  196. }
  197. }
  198. pub struct LifecycleEvent {
  199. pub event_type: LifecycleType,
  200. }
  201. pub enum LifecycleType {
  202. // Component needs to be mounted, but its scope doesn't exist yet
  203. Mount {
  204. to: ScopeIdx,
  205. under: usize,
  206. props: Box<dyn std::any::Any>,
  207. },
  208. // Parent was evalauted causing new props to generate
  209. PropsChanged {
  210. props: Box<dyn std::any::Any>,
  211. component: ScopeIdx,
  212. },
  213. // Hook for the subscription API
  214. Callback {
  215. component: ScopeIdx,
  216. },
  217. }
  218. impl LifecycleEvent {
  219. fn index(&self) -> Option<ScopeIdx> {
  220. match &self.event_type {
  221. LifecycleType::Mount {
  222. to: _,
  223. under: _,
  224. props: _,
  225. } => None,
  226. LifecycleType::PropsChanged { component, .. }
  227. | LifecycleType::Callback { component } => Some(component.clone()),
  228. }
  229. }
  230. }
  231. mod tests {
  232. use super::*;
  233. #[test]
  234. fn start_dom() {
  235. let mut dom = VirtualDom::new(|ctx, props| {
  236. todo!()
  237. // ctx.render(|ctx| {
  238. // use crate::builder::*;
  239. // let bump = ctx.bump();
  240. // div(bump).child(text("hello, world")).finish()
  241. // })
  242. });
  243. let edits = dom.rebuild().unwrap();
  244. println!("{:#?}", edits);
  245. }
  246. }