virtual_dom.rs 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964
  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 crate::innerlude::*;
  22. use bumpalo::Bump;
  23. use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
  24. use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
  25. use fxhash::FxHashMap;
  26. use fxhash::FxHashSet;
  27. use indexmap::IndexSet;
  28. use slab::Slab;
  29. use std::pin::Pin;
  30. use std::task::Poll;
  31. use std::{
  32. any::{Any, TypeId},
  33. cell::{Cell, UnsafeCell},
  34. collections::{HashSet, VecDeque},
  35. rc::Rc,
  36. };
  37. use crate::innerlude::*;
  38. /// An integrated virtual node system that progresses events and diffs UI trees.
  39. ///
  40. /// Differences are converted into patches which a renderer can use to draw the UI.
  41. ///
  42. /// If you are building an App with Dioxus, you probably won't want to reach for this directly, instead opting to defer
  43. /// to a particular crate's wrapper over the [`VirtualDom`] API.
  44. ///
  45. /// Example
  46. /// ```rust
  47. /// static App: FC<()> = |(cx, props)|{
  48. /// cx.render(rsx!{
  49. /// div {
  50. /// "Hello World"
  51. /// }
  52. /// })
  53. /// }
  54. ///
  55. /// async fn main() {
  56. /// let mut dom = VirtualDom::new(App);
  57. /// let mut inital_edits = dom.rebuild();
  58. /// initialize_screen(inital_edits);
  59. ///
  60. /// loop {
  61. /// let next_frame = TimeoutFuture::new(Duration::from_millis(16));
  62. /// let edits = dom.run_with_deadline(next_frame).await;
  63. /// apply_edits(edits);
  64. /// render_frame();
  65. /// }
  66. /// }
  67. /// ```
  68. pub struct VirtualDom {
  69. base_scope: ScopeId,
  70. root_fc: Box<dyn Any>,
  71. root_props: Rc<dyn Any>,
  72. // we need to keep the allocation around, but we don't necessarily use it
  73. _root_caller: Box<dyn Any>,
  74. pub(crate) scopes: ScopeArena,
  75. pub receiver: UnboundedReceiver<SchedulerMsg>,
  76. pub sender: UnboundedSender<SchedulerMsg>,
  77. // Every component that has futures that need to be polled
  78. pub pending_futures: FxHashSet<ScopeId>,
  79. pub ui_events: VecDeque<UserEvent>,
  80. pub pending_immediates: VecDeque<ScopeId>,
  81. pub dirty_scopes: IndexSet<ScopeId>,
  82. pub(crate) saved_state: Option<SavedDiffWork<'static>>,
  83. pub in_progress: bool,
  84. }
  85. pub enum SchedulerMsg {
  86. // events from the host
  87. UiEvent(UserEvent),
  88. // setstate
  89. Immediate(ScopeId),
  90. }
  91. impl VirtualDom {
  92. /// Create a new VirtualDOM with a component that does not have special props.
  93. ///
  94. /// # Description
  95. ///
  96. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  97. ///
  98. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  99. /// to toss out the entire tree.
  100. ///
  101. ///
  102. /// # Example
  103. /// ```
  104. /// fn Example(cx: Context<()>) -> DomTree {
  105. /// cx.render(rsx!( div { "hello world" } ))
  106. /// }
  107. ///
  108. /// let dom = VirtualDom::new(Example);
  109. /// ```
  110. ///
  111. /// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
  112. pub fn new(root: FC<()>) -> Self {
  113. Self::new_with_props(root, ())
  114. }
  115. /// Create a new VirtualDOM with the given properties for the root component.
  116. ///
  117. /// # Description
  118. ///
  119. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
  120. ///
  121. /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
  122. /// to toss out the entire tree.
  123. ///
  124. ///
  125. /// # Example
  126. /// ```
  127. /// #[derive(PartialEq, Props)]
  128. /// struct SomeProps {
  129. /// name: &'static str
  130. /// }
  131. ///
  132. /// fn Example(cx: Context<SomeProps>) -> DomTree {
  133. /// cx.render(rsx!{ div{ "hello {cx.name}" } })
  134. /// }
  135. ///
  136. /// let dom = VirtualDom::new(Example);
  137. /// ```
  138. ///
  139. /// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
  140. ///
  141. /// ```rust
  142. /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
  143. /// let mutations = dom.rebuild();
  144. /// ```
  145. pub fn new_with_props<P: 'static + Send>(root: FC<P>, root_props: P) -> Self {
  146. let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
  147. Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
  148. }
  149. /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
  150. ///
  151. /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
  152. /// VirtualDom to be created just to retrieve its channel receiver.
  153. pub fn new_with_props_and_scheduler<P: 'static>(
  154. root: FC<P>,
  155. root_props: P,
  156. sender: UnboundedSender<SchedulerMsg>,
  157. receiver: UnboundedReceiver<SchedulerMsg>,
  158. ) -> Self {
  159. let mut scopes = ScopeArena::new();
  160. let base_scope = scopes.new_with_key(
  161. //
  162. root as _,
  163. todo!(),
  164. // boxed_comp.as_ref(),
  165. None,
  166. 0,
  167. 0,
  168. sender.clone(),
  169. );
  170. Self {
  171. scopes,
  172. base_scope,
  173. receiver,
  174. sender,
  175. root_fc: todo!(),
  176. root_props: todo!(),
  177. _root_caller: todo!(),
  178. ui_events: todo!(),
  179. pending_immediates: todo!(),
  180. pending_futures: Default::default(),
  181. dirty_scopes: Default::default(),
  182. saved_state: Some(SavedDiffWork {
  183. mutations: Mutations::new(),
  184. stack: DiffStack::new(),
  185. seen_scopes: Default::default(),
  186. }),
  187. in_progress: false,
  188. }
  189. }
  190. /// Get the [`Scope`] for the root component.
  191. ///
  192. /// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
  193. /// directly.
  194. pub fn base_scope(&self) -> &ScopeInner {
  195. todo!()
  196. // self.get_scope(&self.base_scope).unwrap()
  197. }
  198. /// Get the [`Scope`] for a component given its [`ScopeId`]
  199. pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeInner> {
  200. todo!()
  201. // self.get_scope(&id)
  202. }
  203. /// Update the root props of this VirtualDOM.
  204. ///
  205. /// This method returns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
  206. /// so calling this method will block the main thread until computation is done.
  207. ///
  208. /// ## Example
  209. ///
  210. /// ```rust
  211. /// #[derive(Props, PartialEq)]
  212. /// struct AppProps {
  213. /// route: &'static str
  214. /// }
  215. /// static App: FC<AppProps> = |(cx, props)|cx.render(rsx!{ "route is {cx.route}" });
  216. ///
  217. /// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" });
  218. ///
  219. /// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap();
  220. /// ```
  221. pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
  222. where
  223. P: 'static,
  224. {
  225. let base = self.base_scope;
  226. let root_scope = self.get_scope_mut(&base).unwrap();
  227. // Pre-emptively drop any downstream references of the old props
  228. root_scope.ensure_drop_safety();
  229. let mut root_props: Rc<dyn Any> = Rc::new(root_props);
  230. if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
  231. // Swap the old props and new props
  232. std::mem::swap(&mut self.root_props, &mut root_props);
  233. let root = *self.root_fc.downcast_ref::<FC<P>>().unwrap();
  234. let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
  235. Box::new(move |scope: &ScopeInner| unsafe {
  236. let props: &'_ P = &*(props_ptr as *const P);
  237. Some(root((scope, props)))
  238. // std::mem::transmute()
  239. });
  240. drop(root_props);
  241. Some(self.rebuild())
  242. } else {
  243. None
  244. }
  245. }
  246. /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch
  247. ///
  248. /// The diff machine expects the RealDom's stack to be the root of the application.
  249. ///
  250. /// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
  251. /// root component will be ran once and then diffed. All updates will flow out as mutations.
  252. ///
  253. /// All state stored in components will be completely wiped away.
  254. ///
  255. /// # Example
  256. /// ```
  257. /// static App: FC<()> = |(cx, props)|cx.render(rsx!{ "hello world" });
  258. /// let mut dom = VirtualDom::new();
  259. /// let edits = dom.rebuild();
  260. ///
  261. /// apply_edits(edits);
  262. /// ```
  263. pub fn rebuild(&mut self) -> Mutations {
  264. todo!()
  265. // self.rebuild(self.base_scope)
  266. }
  267. /// Compute a manual diff of the VirtualDOM between states.
  268. ///
  269. /// This can be useful when state inside the DOM is remotely changed from the outside, but not propagated as an event.
  270. ///
  271. /// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
  272. /// to force an update of the DOM when the state of the app is changed outside of the app.
  273. ///
  274. ///
  275. /// # Example
  276. /// ```rust
  277. /// #[derive(PartialEq, Props)]
  278. /// struct AppProps {
  279. /// value: Shared<&'static str>,
  280. /// }
  281. ///
  282. /// static App: FC<AppProps> = |(cx, props)|{
  283. /// let val = cx.value.borrow();
  284. /// cx.render(rsx! { div { "{val}" } })
  285. /// };
  286. ///
  287. /// let value = Rc::new(RefCell::new("Hello"));
  288. /// let mut dom = VirtualDom::new_with_props(
  289. /// App,
  290. /// AppProps {
  291. /// value: value.clone(),
  292. /// },
  293. /// );
  294. ///
  295. /// let _ = dom.rebuild();
  296. ///
  297. /// *value.borrow_mut() = "goodbye";
  298. ///
  299. /// let edits = dom.diff();
  300. /// ```
  301. pub fn diff(&mut self) -> Mutations {
  302. self.hard_diff(self.base_scope)
  303. }
  304. /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
  305. ///
  306. /// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
  307. /// return "None"
  308. pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
  309. if self.has_any_work() {
  310. Some(self.work_sync())
  311. } else {
  312. None
  313. }
  314. }
  315. /// Run the virtualdom with a deadline.
  316. ///
  317. /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
  318. /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
  319. /// exhaust the deadline working on them.
  320. ///
  321. /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
  322. /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
  323. ///
  324. /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
  325. /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
  326. /// deadline closure manually.
  327. ///
  328. /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
  329. /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
  330. /// the screen will "jank" up. In debug, this will trigger an alert.
  331. ///
  332. /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
  333. /// the provided deadline future resolves.
  334. ///
  335. /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
  336. /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
  337. /// entirely jank-free applications that perform a ton of work.
  338. ///
  339. /// # Example
  340. ///
  341. /// ```no_run
  342. /// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
  343. /// let mut dom = VirtualDom::new(App);
  344. /// loop {
  345. /// let deadline = TimeoutFuture::from_ms(16);
  346. /// let mutations = dom.run_with_deadline(deadline).await;
  347. /// apply_mutations(mutations);
  348. /// }
  349. /// ```
  350. ///
  351. /// ## Mutations
  352. ///
  353. /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
  354. /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
  355. /// applied the edits.
  356. ///
  357. /// Mutations are the only link between the RealDOM and the VirtualDOM.
  358. pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
  359. self.work_with_deadline(deadline)
  360. }
  361. pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
  362. self.sender.clone()
  363. }
  364. /// Waits for the scheduler to have work
  365. /// This lets us poll async tasks during idle periods without blocking the main thread.
  366. pub async fn wait_for_work(&mut self) {
  367. // todo: poll the events once even if there is work to do to prevent starvation
  368. if self.has_any_work() {
  369. return;
  370. }
  371. use futures_util::StreamExt;
  372. // Wait for any new events if we have nothing to do
  373. // let tasks_fut = self.async_tasks.next();
  374. // let scheduler_fut = self.receiver.next();
  375. // use futures_util::future::{select, Either};
  376. // match select(tasks_fut, scheduler_fut).await {
  377. // // poll the internal futures
  378. // Either::Left((_id, _)) => {
  379. // //
  380. // }
  381. // // wait for an external event
  382. // Either::Right((msg, _)) => match msg.unwrap() {
  383. // SchedulerMsg::Task(t) => {
  384. // self.handle_task(t);
  385. // }
  386. // SchedulerMsg::Immediate(im) => {
  387. // self.dirty_scopes.insert(im);
  388. // }
  389. // SchedulerMsg::UiEvent(evt) => {
  390. // self.ui_events.push_back(evt);
  391. // }
  392. // },
  393. // }
  394. }
  395. }
  396. /*
  397. Welcome to Dioxus's cooperative, priority-based scheduler.
  398. I hope you enjoy your stay.
  399. Some essential reading:
  400. - https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
  401. - https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
  402. - https://github.com/WICG/is-input-pending
  403. - https://web.dev/rail/
  404. - https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
  405. # What's going on?
  406. Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
  407. snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
  408. point in being "fast" if you can't also be "responsive."
  409. As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
  410. priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
  411. The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
  412. different types of work simultaneously.
  413. # How does it work?
  414. Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
  415. React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
  416. React, and the UI still stays snappy!
  417. While it's straightforward to run code ASAP and be as "fast as possible", what's not _not_ straightforward is how to do
  418. this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
  419. has time to paint and run animations. When the browser is finished, we can step in and continue our work.
  420. React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
  421. necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
  422. "commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
  423. our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
  424. so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
  425. React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
  426. now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
  427. before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
  428. are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
  429. future is paused and saved (self-referentially).
  430. # Priority System
  431. So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
  432. doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
  433. priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
  434. a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
  435. React's priority system is quite complex.
  436. There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
  437. uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
  438. For Dioxus, we're going with a 4 tier priority system:
  439. - Sync: Things that need to be done by the next frame, like TextInput on controlled elements
  440. - High: for events that block all others - clicks, keyboard, and hovers
  441. - Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
  442. - Low: set_state called asynchronously, and anything generated by suspense
  443. In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
  444. work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
  445. as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
  446. do anything too arduous from onInput.
  447. For the rest, we defer to the rIC period and work down each queue from high to low.
  448. */
  449. /// The scheduler holds basically everything around "working"
  450. ///
  451. /// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
  452. ///
  453. /// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
  454. /// out.
  455. ///
  456. /// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
  457. /// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
  458. /// task shift to a higher priority task that needs mutable access to the same scopes.
  459. ///
  460. /// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
  461. ///
  462. /// There's a lot of raw pointers here...
  463. ///
  464. /// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
  465. /// The best way to do that is a bump allocator.
  466. ///
  467. ///
  468. ///
  469. impl VirtualDom {
  470. // returns true if the event is discrete
  471. pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
  472. // let (discrete, priority) = event_meta(&event);
  473. if let Some(scope) = self.get_scope_mut(&event.scope) {
  474. if let Some(element) = event.mounted_dom_id {
  475. // TODO: bubble properly here
  476. scope.call_listener(event, element);
  477. while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
  478. //
  479. // self.add_dirty_scope(dirty_scope, trigger.priority)
  480. }
  481. }
  482. }
  483. // use EventPriority::*;
  484. // match priority {
  485. // Immediate => todo!(),
  486. // High => todo!(),
  487. // Medium => todo!(),
  488. // Low => todo!(),
  489. // }
  490. todo!()
  491. // discrete
  492. }
  493. fn prepare_work(&mut self) {
  494. // while let Some(trigger) = self.ui_events.pop_back() {
  495. // if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
  496. // }
  497. }
  498. // nothing to do, no events on channels, no work
  499. pub fn has_any_work(&self) -> bool {
  500. !(self.dirty_scopes.is_empty() && self.ui_events.is_empty())
  501. }
  502. /// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
  503. fn balance_lanes(&mut self) {}
  504. fn save_work(&mut self, lane: SavedDiffWork) {
  505. let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
  506. self.saved_state = Some(saved);
  507. }
  508. unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
  509. self.saved_state.take().unwrap().extend()
  510. }
  511. pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
  512. match msg {
  513. SchedulerMsg::Immediate(_) => todo!(),
  514. SchedulerMsg::UiEvent(event) => {
  515. //
  516. // let (discrete, priority) = event_meta(&event);
  517. if let Some(scope) = self.get_scope_mut(&event.scope) {
  518. if let Some(element) = event.mounted_dom_id {
  519. // TODO: bubble properly here
  520. scope.call_listener(event, element);
  521. while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
  522. //
  523. // self.add_dirty_scope(dirty_scope, trigger.priority)
  524. }
  525. }
  526. }
  527. // discrete;
  528. }
  529. }
  530. }
  531. /// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
  532. ///
  533. /// Returns true if the lane is finished before the deadline could be met.
  534. pub fn work_on_current_lane(
  535. &mut self,
  536. deadline_reached: impl FnMut() -> bool,
  537. mutations: &mut Vec<Mutations>,
  538. ) -> bool {
  539. // Work through the current subtree, and commit the results when it finishes
  540. // When the deadline expires, give back the work
  541. let saved_state = unsafe { self.load_work() };
  542. // We have to split away some parts of ourself - current lane is borrowed mutably
  543. let mut machine = unsafe { saved_state.promote() };
  544. let mut ran_scopes = FxHashSet::default();
  545. if machine.stack.is_empty() {
  546. todo!("order scopes");
  547. // self.dirty_scopes.retain(|id| self.get_scope(id).is_some());
  548. // self.dirty_scopes.sort_by(|a, b| {
  549. // let h1 = self.get_scope(a).unwrap().height;
  550. // let h2 = self.get_scope(b).unwrap().height;
  551. // h1.cmp(&h2).reverse()
  552. // });
  553. if let Some(scopeid) = self.dirty_scopes.pop() {
  554. log::info!("handling dirty scope {:?}", scopeid);
  555. if !ran_scopes.contains(&scopeid) {
  556. ran_scopes.insert(scopeid);
  557. log::debug!("about to run scope {:?}", scopeid);
  558. // if let Some(component) = self.get_scope_mut(&scopeid) {
  559. if self.run_scope(&scopeid) {
  560. todo!("diff the scope")
  561. // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
  562. // // let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
  563. // machine.stack.scope_stack.push(scopeid);
  564. // machine.stack.push(DiffInstruction::Diff { new, old });
  565. }
  566. // }
  567. }
  568. }
  569. }
  570. let work_completed: bool = todo!();
  571. // let work_completed = machine.work(deadline_reached);
  572. // log::debug!("raw edits {:?}", machine.mutations.edits);
  573. let mut machine: DiffState<'static> = unsafe { std::mem::transmute(machine) };
  574. // let mut saved = machine.save();
  575. if work_completed {
  576. for node in machine.seen_scopes.drain() {
  577. // self.dirty_scopes.clear();
  578. // self.ui_events.clear();
  579. self.dirty_scopes.remove(&node);
  580. // self.dirty_scopes.remove(&node);
  581. }
  582. let mut new_mutations = Mutations::new();
  583. for edit in machine.mutations.edits.drain(..) {
  584. new_mutations.edits.push(edit);
  585. }
  586. // for edit in saved.edits.drain(..) {
  587. // new_mutations.edits.push(edit);
  588. // }
  589. // std::mem::swap(&mut new_mutations, &mut saved.mutations);
  590. mutations.push(new_mutations);
  591. // log::debug!("saved edits {:?}", mutations);
  592. todo!();
  593. // let mut saved = machine.save();
  594. // self.save_work(saved);
  595. true
  596. // self.save_work(saved);
  597. // false
  598. } else {
  599. false
  600. }
  601. }
  602. /// The primary workhorse of the VirtualDOM.
  603. ///
  604. /// Uses some fairly complex logic to schedule what work should be produced.
  605. ///
  606. /// Returns a list of successful mutations.
  607. pub fn work_with_deadline<'a>(
  608. &'a mut self,
  609. mut deadline: impl FnMut() -> bool,
  610. ) -> Vec<Mutations<'a>> {
  611. /*
  612. Strategy:
  613. - When called, check for any UI events that might've been received since the last frame.
  614. - Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
  615. - If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
  616. then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
  617. will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
  618. to bump the other work out of the way, or choose to process it so we don't have any conflicts.
  619. 'static components have a leg up here since their work can be re-used among multiple scopes.
  620. "High priority" is only for blocking! Should only be used on "clicks"
  621. - If there are no pending discrete events, then check for continuous events. These can be completely batched
  622. - we batch completely until we run into a discrete event
  623. - all continuous events are batched together
  624. - so D C C C C C would be two separate events - D and C. IE onclick and onscroll
  625. - D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
  626. - !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
  627. - we need to keep track of element parents to be able to traverse properly
  628. Open questions:
  629. - what if we get two clicks from the component during the same slice?
  630. - should we batch?
  631. - react says no - they are continuous
  632. - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
  633. */
  634. let mut committed_mutations = Vec::<Mutations<'static>>::new();
  635. while self.has_any_work() {
  636. while let Ok(Some(msg)) = self.receiver.try_next() {
  637. match msg {
  638. SchedulerMsg::Immediate(im) => {
  639. self.dirty_scopes.insert(im);
  640. }
  641. SchedulerMsg::UiEvent(evt) => {
  642. self.ui_events.push_back(evt);
  643. }
  644. }
  645. }
  646. // switch our priority, pop off any work
  647. while let Some(event) = self.ui_events.pop_front() {
  648. if let Some(scope) = self.get_scope_mut(&event.scope) {
  649. if let Some(element) = event.mounted_dom_id {
  650. log::info!("Calling listener {:?}, {:?}", event.scope, element);
  651. // TODO: bubble properly here
  652. scope.call_listener(event, element);
  653. while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
  654. match dirty_scope {
  655. SchedulerMsg::Immediate(im) => {
  656. self.dirty_scopes.insert(im);
  657. }
  658. SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
  659. }
  660. }
  661. }
  662. }
  663. }
  664. let work_complete = self.work_on_current_lane(&mut deadline, &mut committed_mutations);
  665. if !work_complete {
  666. return committed_mutations;
  667. }
  668. }
  669. committed_mutations
  670. }
  671. /// Work the scheduler down, not polling any ongoing tasks.
  672. ///
  673. /// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
  674. pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
  675. let mut committed_mutations = Vec::new();
  676. while let Ok(Some(msg)) = self.receiver.try_next() {
  677. self.handle_channel_msg(msg);
  678. }
  679. if !self.has_any_work() {
  680. return committed_mutations;
  681. }
  682. while self.has_any_work() {
  683. self.prepare_work();
  684. self.work_on_current_lane(|| false, &mut committed_mutations);
  685. }
  686. committed_mutations
  687. }
  688. /// Restart the entire VirtualDOM from scratch, wiping away any old state and components.
  689. ///
  690. /// Typically used to kickstart the VirtualDOM after initialization.
  691. pub fn rebuild_inner(&mut self, base_scope: ScopeId) -> Mutations {
  692. // TODO: drain any in-flight work
  693. // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
  694. if self.run_scope(&base_scope) {
  695. let cur_component = self
  696. .get_scope_mut(&base_scope)
  697. .expect("The base scope should never be moved");
  698. log::debug!("rebuild {:?}", base_scope);
  699. let mut diff_machine = DiffState::new(Mutations::new());
  700. diff_machine
  701. .stack
  702. .create_node(cur_component.frames.fin_head(), MountType::Append);
  703. diff_machine.stack.scope_stack.push(base_scope);
  704. todo!()
  705. // self.work(&mut diff_machine, || false);
  706. // diff_machine.work(|| false);
  707. } else {
  708. // todo: should this be a hard error?
  709. log::warn!(
  710. "Component failed to run successfully during rebuild.
  711. This does not result in a failed rebuild, but indicates a logic failure within your app."
  712. );
  713. }
  714. todo!()
  715. // unsafe { std::mem::transmute(diff_machine.mutations) }
  716. }
  717. pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
  718. let cur_component = self
  719. .get_scope_mut(&base_scope)
  720. .expect("The base scope should never be moved");
  721. log::debug!("hard diff {:?}", base_scope);
  722. if self.run_scope(&base_scope) {
  723. let mut diff_machine = DiffState::new(Mutations::new());
  724. diff_machine.cfg.force_diff = true;
  725. self.diff_scope(&mut diff_machine, base_scope);
  726. // diff_machine.diff_scope(base_scope);
  727. diff_machine.mutations
  728. } else {
  729. Mutations::new()
  730. }
  731. }
  732. pub fn get_scope_mut<'a>(&'a self, id: &ScopeId) -> Option<&'a ScopeInner> {
  733. self.scopes.get_mut(id)
  734. }
  735. pub fn run_scope(&mut self, id: &ScopeId) -> bool {
  736. let scope = self
  737. .get_scope_mut(id)
  738. .expect("The base scope should never be moved");
  739. // Cycle to the next frame and then reset it
  740. // This breaks any latent references, invalidating every pointer referencing into it.
  741. // Remove all the outdated listeners
  742. scope.ensure_drop_safety();
  743. // Safety:
  744. // - We dropped the listeners, so no more &mut T can be used while these are held
  745. // - All children nodes that rely on &mut T are replaced with a new reference
  746. unsafe { scope.hooks.reset() };
  747. // Safety:
  748. // - We've dropped all references to the wip bump frame
  749. todo!("reset wip frame");
  750. // unsafe { scope.frames.reset_wip_frame() };
  751. let items = scope.items.get_mut();
  752. // just forget about our suspended nodes while we're at it
  753. items.suspended_nodes.clear();
  754. // guarantee that we haven't screwed up - there should be no latent references anywhere
  755. debug_assert!(items.listeners.is_empty());
  756. debug_assert!(items.suspended_nodes.is_empty());
  757. debug_assert!(items.borrowed_props.is_empty());
  758. log::debug!("Borrowed stuff is successfully cleared");
  759. // temporarily cast the vcomponent to the right lifetime
  760. let vcomp = scope.load_vcomp();
  761. let render: &dyn Fn(&ScopeInner) -> Element = todo!();
  762. // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
  763. if let Some(builder) = render(scope) {
  764. todo!("attach the niode");
  765. // let new_head = builder.into_vnode(NodeFactory {
  766. // bump: &scope.frames.wip_frame().bump,
  767. // });
  768. log::debug!("Render is successful");
  769. // the user's component succeeded. We can safely cycle to the next frame
  770. // scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
  771. // scope.frames.cycle_frame();
  772. true
  773. } else {
  774. false
  775. }
  776. }
  777. pub fn reserve_node(&self, node: &VNode) -> ElementId {
  778. todo!()
  779. // self.node_reservations.insert(id);
  780. }
  781. pub fn collect_garbage(&self, id: ElementId) {
  782. todo!()
  783. }
  784. pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeInner> {
  785. todo!()
  786. }
  787. }
  788. // impl<'a> Future for PollAllTasks<'a> {
  789. // type Output = ();
  790. // fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
  791. // let mut all_pending = true;
  792. // for fut in self.pending_futures.iter() {
  793. // let scope = self
  794. // .pool
  795. // .get_scope_mut(&fut)
  796. // .expect("Scope should never be moved");
  797. // let items = scope.items.get_mut();
  798. // for task in items.tasks.iter_mut() {
  799. // let t = task.as_mut();
  800. // let g = unsafe { Pin::new_unchecked(t) };
  801. // match g.poll(cx) {
  802. // Poll::Ready(r) => {
  803. // all_pending = false;
  804. // }
  805. // Poll::Pending => {}
  806. // }
  807. // }
  808. // }
  809. // match all_pending {
  810. // true => Poll::Pending,
  811. // false => Poll::Ready(()),
  812. // }
  813. // }
  814. // }