scheduler.rs 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  1. /*
  2. Welcome to Dioxus's cooperative, priority-based scheduler.
  3. I hope you enjoy your stay.
  4. Some essential reading:
  5. - https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
  6. - https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
  7. - https://github.com/WICG/is-input-pending
  8. - https://web.dev/rail/
  9. - https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
  10. # What's going on?
  11. Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
  12. snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
  13. point if being "fast" if you can't also be "responsive."
  14. As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
  15. priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
  16. The controller responsible for this priority management is called the "scheduler" and is responsible for juggle many
  17. different types of work simultaneously.
  18. # How does it work?
  19. Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
  20. React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
  21. React, and the UI still stays snappy!
  22. While it's straightforward to run code ASAP and be as "fast as possible", what's not _not_ straightforward is how to do
  23. this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
  24. has time to paint and run animations. When the browser is finished, we can step in and continue our work.
  25. React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
  26. necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
  27. "commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
  28. our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
  29. so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
  30. React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
  31. now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
  32. before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
  33. are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
  34. future is paused and saved (self-referentially).
  35. # Priorty System
  36. So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
  37. 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
  38. priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
  39. a "lower" priority. That way, we can interrupt long-running low-prioty work with short-running high-priority work.
  40. React's priority system is quite complex.
  41. There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
  42. uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
  43. For Dioxus, we're going with a 4 tier priorty system:
  44. - Sync: Things that need to be done by the next frame, like TextInput on controlled elements
  45. - High: for events that block all others - clicks, keyboard, and hovers
  46. - Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
  47. - Low: set_state called asynchronously, and anything generated by suspense
  48. In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
  49. work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
  50. as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
  51. do anything too arduous from onInput.
  52. For the rest, we defer to the rIC period and work down each queue from high to low.
  53. */
  54. use std::cell::{Cell, RefCell, RefMut};
  55. use std::fmt::Display;
  56. use std::intrinsics::transmute;
  57. use std::{cell::UnsafeCell, rc::Rc};
  58. use crate::heuristics::*;
  59. use crate::innerlude::*;
  60. use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
  61. use futures_util::stream::FuturesUnordered;
  62. use fxhash::{FxHashMap, FxHashSet};
  63. use indexmap::IndexSet;
  64. use slab::Slab;
  65. use smallvec::SmallVec;
  66. use std::any::Any;
  67. use std::any::TypeId;
  68. use std::cell::Ref;
  69. use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
  70. use std::pin::Pin;
  71. use futures_util::future::FusedFuture;
  72. use futures_util::pin_mut;
  73. use futures_util::Future;
  74. use futures_util::FutureExt;
  75. use futures_util::StreamExt;
  76. #[derive(Clone)]
  77. pub struct EventChannel {
  78. pub task_counter: Rc<Cell<u64>>,
  79. pub sender: UnboundedSender<SchedulerMsg>,
  80. pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
  81. pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
  82. pub get_shared_context: Rc<dyn Fn(ScopeId, TypeId) -> Option<Rc<dyn Any>>>,
  83. }
  84. pub enum SchedulerMsg {
  85. Immediate(ScopeId),
  86. UiEvent(EventTrigger),
  87. SubmitTask(FiberTask, u64),
  88. ToggleTask(u64),
  89. PauseTask(u64),
  90. ResumeTask(u64),
  91. DropTask(u64),
  92. }
  93. /// The scheduler holds basically everything around "working"
  94. ///
  95. /// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
  96. ///
  97. /// In Dioxus, the scheduler provides 3 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
  98. /// out.
  99. ///
  100. /// 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
  101. /// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
  102. /// task shift to a higher priority task that needs mutable access to the same scopes.
  103. ///
  104. /// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
  105. ///
  106. ///
  107. pub struct Scheduler {
  108. /*
  109. This *has* to be an UnsafeCell.
  110. Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to
  111. its bumpframe conents immutably.
  112. However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly
  113. simple to audit.
  114. Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!)
  115. */
  116. pub components: Rc<UnsafeCell<Slab<Scope>>>,
  117. /*
  118. Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy
  119. that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client.
  120. This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse.
  121. In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes...
  122. */
  123. pub raw_elements: Slab<()>,
  124. pub heuristics: HeuristicsEngine,
  125. pub channel: EventChannel,
  126. pub receiver: UnboundedReceiver<SchedulerMsg>,
  127. // Garbage stored
  128. pub pending_garbage: FxHashSet<ScopeId>,
  129. // In-flight futures
  130. pub async_tasks: FuturesUnordered<FiberTask>,
  131. // scheduler stuff
  132. pub current_priority: EventPriority,
  133. pub ui_events: VecDeque<EventTrigger>,
  134. pub pending_immediates: VecDeque<ScopeId>,
  135. pub pending_tasks: VecDeque<EventTrigger>,
  136. pub garbage_scopes: HashSet<ScopeId>,
  137. pub lanes: [PriortySystem; 4],
  138. }
  139. impl Scheduler {
  140. pub fn new() -> Self {
  141. /*
  142. Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
  143. Perhaps this should be configurable?
  144. */
  145. let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
  146. let raw_elements = Slab::with_capacity(2000);
  147. let heuristics = HeuristicsEngine::new();
  148. let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
  149. let task_counter = Rc::new(Cell::new(0));
  150. let channel = EventChannel {
  151. task_counter: task_counter.clone(),
  152. sender: sender.clone(),
  153. schedule_any_immediate: {
  154. let sender = sender.clone();
  155. Rc::new(move |id| sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap())
  156. },
  157. submit_task: {
  158. let sender = sender.clone();
  159. Rc::new(move |fiber_task| {
  160. let task_id = task_counter.get();
  161. task_counter.set(task_id + 1);
  162. sender
  163. .unbounded_send(SchedulerMsg::SubmitTask(fiber_task, task_id))
  164. .unwrap();
  165. TaskHandle {
  166. our_id: task_id,
  167. sender: sender.clone(),
  168. }
  169. })
  170. },
  171. get_shared_context: {
  172. let components = components.clone();
  173. Rc::new(move |id, ty| {
  174. let components = unsafe { &*components.get() };
  175. let mut search: Option<&Scope> = components.get(id.0);
  176. while let Some(inner) = search.take() {
  177. if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
  178. return Some(shared.clone());
  179. } else {
  180. search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
  181. }
  182. }
  183. None
  184. })
  185. },
  186. };
  187. Self {
  188. channel,
  189. receiver,
  190. components,
  191. async_tasks: FuturesUnordered::new(),
  192. pending_garbage: FxHashSet::default(),
  193. heuristics,
  194. raw_elements,
  195. // a storage for our receiver to dump into
  196. ui_events: VecDeque::new(),
  197. pending_immediates: VecDeque::new(),
  198. pending_tasks: VecDeque::new(),
  199. garbage_scopes: HashSet::new(),
  200. current_priority: EventPriority::Low,
  201. // a dedicated fiber for each priority
  202. lanes: [
  203. PriortySystem::new(),
  204. PriortySystem::new(),
  205. PriortySystem::new(),
  206. PriortySystem::new(),
  207. ],
  208. }
  209. }
  210. /// this is unsafe because the caller needs to track which other scopes it's already using
  211. pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
  212. let inner = unsafe { &*self.components.get() };
  213. inner.get(idx.0)
  214. }
  215. /// this is unsafe because the caller needs to track which other scopes it's already using
  216. pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
  217. let inner = unsafe { &mut *self.components.get() };
  218. inner.get_mut(idx.0)
  219. }
  220. pub fn with_scope<'b, O: 'static>(
  221. &'b self,
  222. _id: ScopeId,
  223. _f: impl FnOnce(&'b mut Scope) -> O,
  224. ) -> Result<O> {
  225. todo!()
  226. }
  227. // return a bumpframe with a lifetime attached to the arena borrow
  228. // this is useful for merging lifetimes
  229. pub fn with_scope_vnode<'b>(
  230. &self,
  231. _id: ScopeId,
  232. _f: impl FnOnce(&mut Scope) -> &VNode<'b>,
  233. ) -> Result<&VNode<'b>> {
  234. todo!()
  235. }
  236. pub fn try_remove(&self, id: ScopeId) -> Result<Scope> {
  237. let inner = unsafe { &mut *self.components.get() };
  238. Ok(inner.remove(id.0))
  239. // .try_remove(id.0)
  240. // .ok_or_else(|| Error::FatalInternal("Scope not found"))
  241. }
  242. pub fn reserve_node(&self) -> ElementId {
  243. todo!("reserving wip until it's fast enough again")
  244. // ElementId(self.raw_elements.insert(()))
  245. }
  246. /// return the id, freeing the space of the original node
  247. pub fn collect_garbage(&self, id: ElementId) {
  248. todo!("garabge collection currently WIP")
  249. // self.raw_elements.remove(id.0);
  250. }
  251. pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
  252. let g = unsafe { &mut *self.components.get() };
  253. let entry = g.vacant_entry();
  254. let id = ScopeId(entry.key());
  255. entry.insert(f(id));
  256. id
  257. }
  258. pub fn clean_up_garbage(&mut self) {
  259. // let mut scopes_to_kill = Vec::new();
  260. // let mut garbage_list = Vec::new();
  261. todo!("garbage collection is currently immediate")
  262. // for scope in self.garbage_scopes.drain() {
  263. // let scope = self.get_scope_mut(scope).unwrap();
  264. // for node in scope.consume_garbage() {
  265. // garbage_list.push(node);
  266. // }
  267. // while let Some(node) = garbage_list.pop() {
  268. // match &node {
  269. // VNode::Text(_) => {
  270. // self.collect_garbage(node.direct_id());
  271. // }
  272. // VNode::Anchor(_) => {
  273. // self.collect_garbage(node.direct_id());
  274. // }
  275. // VNode::Suspended(_) => {
  276. // self.collect_garbage(node.direct_id());
  277. // }
  278. // VNode::Element(el) => {
  279. // self.collect_garbage(node.direct_id());
  280. // for child in el.children {
  281. // garbage_list.push(child);
  282. // }
  283. // }
  284. // VNode::Fragment(frag) => {
  285. // for child in frag.children {
  286. // garbage_list.push(child);
  287. // }
  288. // }
  289. // VNode::Component(comp) => {
  290. // // TODO: run the hook destructors and then even delete the scope
  291. // let scope_id = comp.ass_scope.get().unwrap();
  292. // let scope = self.get_scope(scope_id).unwrap();
  293. // let root = scope.root();
  294. // garbage_list.push(root);
  295. // scopes_to_kill.push(scope_id);
  296. // }
  297. // }
  298. // }
  299. // }
  300. // for scope in scopes_to_kill.drain(..) {
  301. // //
  302. // // kill em
  303. // }
  304. }
  305. pub fn manually_poll_events(&mut self) {
  306. while let Ok(Some(msg)) = self.receiver.try_next() {
  307. self.handle_channel_msg(msg);
  308. }
  309. }
  310. // Converts UI events into dirty scopes with various priorities
  311. pub fn consume_pending_events(&mut self) -> Result<()> {
  312. // while let Some(trigger) = self.ui_events.pop_back() {
  313. // match &trigger.event {
  314. // SyntheticEvent::ClipboardEvent(_)
  315. // | SyntheticEvent::CompositionEvent(_)
  316. // | SyntheticEvent::KeyboardEvent(_)
  317. // | SyntheticEvent::FocusEvent(_)
  318. // | SyntheticEvent::FormEvent(_)
  319. // | SyntheticEvent::SelectionEvent(_)
  320. // | SyntheticEvent::TouchEvent(_)
  321. // | SyntheticEvent::UIEvent(_)
  322. // | SyntheticEvent::WheelEvent(_)
  323. // | SyntheticEvent::MediaEvent(_)
  324. // | SyntheticEvent::AnimationEvent(_)
  325. // | SyntheticEvent::TransitionEvent(_)
  326. // | SyntheticEvent::ToggleEvent(_)
  327. // | SyntheticEvent::MouseEvent(_)
  328. // | SyntheticEvent::PointerEvent(_) => {
  329. // if let Some(scope) = self.get_scope_mut(trigger.scope) {
  330. // if let Some(element) = trigger.mounted_dom_id {
  331. // scope.call_listener(trigger.event, element)?;
  332. // // let receiver = self.immediate_receiver.clone();
  333. // // let mut receiver = receiver.borrow_mut();
  334. // // // Drain the immediates into the dirty scopes, setting the appropiate priorities
  335. // // while let Ok(Some(dirty_scope)) = receiver.try_next() {
  336. // // self.add_dirty_scope(dirty_scope, trigger.priority)
  337. // // }
  338. // }
  339. // }
  340. // }
  341. // }
  342. // }
  343. Ok(())
  344. }
  345. // nothing to do, no events on channels, no work
  346. pub fn has_any_work(&self) -> bool {
  347. self.has_work() || self.has_pending_events() || self.has_pending_garbage()
  348. }
  349. pub fn has_pending_events(&self) -> bool {
  350. self.ui_events.len() > 0
  351. }
  352. pub fn has_work(&self) -> bool {
  353. todo!()
  354. // self.high_priorty.has_work()
  355. // || self.medium_priority.has_work()
  356. // || self.low_priority.has_work()
  357. }
  358. pub fn has_pending_garbage(&self) -> bool {
  359. !self.garbage_scopes.is_empty()
  360. }
  361. fn get_current_fiber<'a>(&'a mut self) -> &mut DiffMachine<'a> {
  362. todo!()
  363. // let fib = match self.current_priority {
  364. // EventPriority::High => &mut self.high_priorty,
  365. // EventPriority::Medium => &mut self.medium_priority,
  366. // EventPriority::Low => &mut self.low_priority,
  367. // };
  368. // unsafe { std::mem::transmute(fib) }
  369. }
  370. /// The primary workhorse of the VirtualDOM.
  371. ///
  372. /// Uses some fairly complex logic to schedule what work should be produced.
  373. ///
  374. /// Returns a list of successful mutations.
  375. ///
  376. ///
  377. pub async fn work_with_deadline<'a>(
  378. &'a mut self,
  379. mut deadline: Pin<Box<impl FusedFuture<Output = ()>>>,
  380. ) -> Vec<Mutations<'a>> {
  381. /*
  382. Strategy:
  383. - When called, check for any UI events that might've been received since the last frame.
  384. - Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
  385. - If there are any pending discrete events, then elevate our priorty level. If our priority level is already "high,"
  386. then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
  387. will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
  388. to bump the other work out of the way, or choose to process it so we don't have any conflicts.
  389. 'static components have a leg up here since their work can be re-used among multiple scopes.
  390. "High priority" is only for blocking! Should only be used on "clicks"
  391. - If there are no pending discrete events, then check for continuous events. These can be completely batched
  392. Open questions:
  393. - what if we get two clicks from the component during the same slice?
  394. - should we batch?
  395. - react says no - they are continuous
  396. - but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
  397. */
  398. let mut committed_mutations = Vec::<Mutations<'static>>::new();
  399. // TODO:
  400. // the scheduler uses a bunch of different receivers to mimic a "topic" queue system. The futures-channel implementation
  401. // doesn't really have a concept of a "topic" queue, so there's a lot of noise in the hand-rolled scheduler. We should
  402. // explore abstracting the scheduler into a topic-queue channel system - similar to Kafka or something similar.
  403. loop {
  404. // Internalize any pending work since the last time we ran
  405. self.manually_poll_events();
  406. // Wait for any new events if we have nothing to do
  407. if !self.has_any_work() {
  408. self.clean_up_garbage();
  409. let deadline_expired = self.wait_for_any_trigger(&mut deadline).await;
  410. if deadline_expired {
  411. return committed_mutations;
  412. }
  413. }
  414. // Create work from the pending event queue
  415. self.consume_pending_events().unwrap();
  416. // Work through the current subtree, and commit the results when it finishes
  417. // When the deadline expires, give back the work
  418. self.current_priority = match (
  419. self.lanes[0].has_work(),
  420. self.lanes[1].has_work(),
  421. self.lanes[2].has_work(),
  422. self.lanes[3].has_work(),
  423. ) {
  424. (true, _, _, _) => EventPriority::Immediate,
  425. (false, true, _, _) => EventPriority::High,
  426. (false, false, true, _) => EventPriority::Medium,
  427. (false, false, false, _) => EventPriority::Low,
  428. };
  429. let current_lane = match self.current_priority {
  430. EventPriority::Immediate => &mut self.lanes[0],
  431. EventPriority::High => &mut self.lanes[1],
  432. EventPriority::Medium => &mut self.lanes[2],
  433. EventPriority::Low => &mut self.lanes[3],
  434. };
  435. if self.current_priority == EventPriority::Immediate {
  436. // IDGAF - get this out the door right now. loop poll if we need to
  437. }
  438. use futures_util::future::{select, Either};
  439. // if let Some(state) = current_lane.saved_state.take() {
  440. // let mut machine = unsafe { state.promote(&self) };
  441. // machine.work().await;
  442. // } else {
  443. if let Some(scope) = current_lane.dirty_scopes.pop() {
  444. let (mut saved_work, work_complete): (SavedDiffWork<'static>, bool) = {
  445. let shared = SharedVdom {
  446. channel: self.channel.clone(),
  447. components: unsafe { &mut *self.components.get() },
  448. elements: &mut self.raw_elements,
  449. };
  450. let mut machine = DiffMachine::new(Mutations::new(), shared);
  451. let work_complete = {
  452. let fut = machine.diff_scope(scope);
  453. pin_mut!(fut);
  454. match select(fut, &mut deadline).await {
  455. Either::Left((work, _other)) => {
  456. //
  457. true
  458. }
  459. Either::Right((deadline, _other)) => {
  460. //
  461. false
  462. }
  463. }
  464. };
  465. let saved = machine.save();
  466. let extended: SavedDiffWork<'static> = unsafe { transmute(saved) };
  467. (extended, work_complete)
  468. };
  469. // release the stack borrow of ourself
  470. if work_complete {
  471. for scope in saved_work.seen_scopes.drain() {
  472. current_lane.dirty_scopes.remove(&scope);
  473. }
  474. } else {
  475. }
  476. // }
  477. };
  478. // let mut new_mutations = Mutations::new();
  479. // match self.work_with_deadline(&mut deadline).await {
  480. // Some(mutations) => {
  481. // // safety: the scheduler will never let us mutate
  482. // let extended: Mutations<'static> = unsafe { std::mem::transmute(mutations) };
  483. // committed_mutations.push(extended)
  484. // }
  485. // None => return committed_mutations,
  486. // }
  487. }
  488. // // check if we need to elevate priority
  489. // // let mut machine = DiffMachine::new(mutations, ScopeId(0), &self);
  490. // let dirty_root = {
  491. // let dirty_roots = match self.current_priority {
  492. // EventPriority::High => &self.high_priorty.dirty_scopes,
  493. // EventPriority::Medium => &self.medium_priority.dirty_scopes,
  494. // EventPriority::Low => &self.low_priority.dirty_scopes,
  495. // };
  496. // let mut height = 0;
  497. // let mut dirty_root = {
  498. // let root = dirty_roots.iter().next();
  499. // if root.is_none() {
  500. // return true;
  501. // }
  502. // root.unwrap()
  503. // };
  504. // for root in dirty_roots {
  505. // if let Some(scope) = self.get_scope(*root) {
  506. // if scope.height < height {
  507. // height = scope.height;
  508. // dirty_root = root;
  509. // }
  510. // }
  511. // }
  512. // dirty_root
  513. // };
  514. // let fut = machine.diff_scope(*dirty_root).fuse();
  515. // pin_mut!(fut);
  516. // match futures_util::future::select(deadline, fut).await {
  517. // futures_util::future::Either::Left((deadline, work_fut)) => true,
  518. // futures_util::future::Either::Right((_, deadline_fut)) => false,
  519. // }
  520. }
  521. // waits for a trigger, canceling early if the deadline is reached
  522. // returns true if the deadline was reached
  523. // does not return the trigger, but caches it in the scheduler
  524. pub async fn wait_for_any_trigger(
  525. &mut self,
  526. deadline: &mut Pin<Box<impl FusedFuture<Output = ()>>>,
  527. ) -> bool {
  528. use futures_util::future::{select, Either};
  529. let event_fut = async {
  530. match select(self.receiver.next(), self.async_tasks.next()).await {
  531. Either::Left((msg, _other)) => {
  532. self.handle_channel_msg(msg.unwrap());
  533. }
  534. Either::Right((task, _other)) => {
  535. // do nothing, async task will likely generate a set of scheduler messages
  536. }
  537. }
  538. };
  539. pin_mut!(event_fut);
  540. match select(event_fut, deadline).await {
  541. Either::Left((msg, _other)) => false,
  542. Either::Right((deadline, _)) => true,
  543. }
  544. }
  545. pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
  546. match msg {
  547. SchedulerMsg::Immediate(_) => todo!(),
  548. SchedulerMsg::UiEvent(_) => todo!(),
  549. //
  550. SchedulerMsg::SubmitTask(_, _) => todo!(),
  551. SchedulerMsg::ToggleTask(_) => todo!(),
  552. SchedulerMsg::PauseTask(_) => todo!(),
  553. SchedulerMsg::ResumeTask(_) => todo!(),
  554. SchedulerMsg::DropTask(_) => todo!(),
  555. }
  556. }
  557. pub fn add_dirty_scope(&mut self, scope: ScopeId, priority: EventPriority) {
  558. todo!()
  559. // match priority {
  560. // EventPriority::High => self.high_priorty.dirty_scopes.insert(scope),
  561. // EventPriority::Medium => self.medium_priority.dirty_scopes.insert(scope),
  562. // EventPriority::Low => self.low_priority.dirty_scopes.insert(scope),
  563. // };
  564. }
  565. }
  566. pub struct PriortySystem {
  567. pub dirty_scopes: IndexSet<ScopeId>,
  568. pub saved_state: Option<SavedDiffWork<'static>>,
  569. }
  570. impl PriortySystem {
  571. pub fn new() -> Self {
  572. Self {
  573. saved_state: None,
  574. dirty_scopes: Default::default(),
  575. }
  576. }
  577. fn has_work(&self) -> bool {
  578. todo!()
  579. }
  580. fn work(&mut self) {
  581. let scope = self.dirty_scopes.pop();
  582. }
  583. }
  584. pub struct TaskHandle {
  585. pub sender: UnboundedSender<SchedulerMsg>,
  586. pub our_id: u64,
  587. }
  588. impl TaskHandle {
  589. /// Toggles this coroutine off/on.
  590. ///
  591. /// This method is not synchronous - your task will not stop immediately.
  592. pub fn toggle(&self) {}
  593. /// This method is not synchronous - your task will not stop immediately.
  594. pub fn start(&self) {}
  595. /// This method is not synchronous - your task will not stop immediately.
  596. pub fn stop(&self) {}
  597. /// This method is not synchronous - your task will not stop immediately.
  598. pub fn restart(&self) {}
  599. }
  600. #[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)]
  601. pub struct ScopeId(pub usize);
  602. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
  603. pub struct ElementId(pub usize);
  604. impl Display for ElementId {
  605. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  606. write!(f, "{}", self.0)
  607. }
  608. }
  609. impl ElementId {
  610. pub fn as_u64(self) -> u64 {
  611. self.0 as u64
  612. }
  613. }
  614. /// Priority of Event Triggers.
  615. ///
  616. /// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
  617. /// won't be afraid to pause work or flush changes to the RealDOM. This is called "cooperative scheduling". Some Renderers
  618. /// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
  619. ///
  620. /// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
  621. ///
  622. /// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
  623. /// we keep it simple, and just use a 3-tier priority system.
  624. ///
  625. /// - NoPriority = 0
  626. /// - LowPriority = 1
  627. /// - NormalPriority = 2
  628. /// - UserBlocking = 3
  629. /// - HighPriority = 4
  630. /// - ImmediatePriority = 5
  631. ///
  632. /// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
  633. /// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
  634. /// flushed before proceeding. Multiple discrete events is highly unlikely, though.
  635. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
  636. pub enum EventPriority {
  637. /// Work that must be completed during the EventHandler phase
  638. ///
  639. ///
  640. Immediate = 3,
  641. /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
  642. ///
  643. /// This is typically reserved for things like user interaction.
  644. ///
  645. /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
  646. High = 2,
  647. /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
  648. /// than "High Priority" events and will take presedence over low priority events.
  649. ///
  650. /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
  651. ///
  652. /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
  653. Medium = 1,
  654. /// "Low Priority" work will always be pre-empted unless the work is significantly delayed, in which case it will be
  655. /// advanced to the front of the work queue until completed.
  656. ///
  657. /// The primary user of Low Priority work is the asynchronous work system (suspense).
  658. ///
  659. /// This is considered "idle" work or "background" work.
  660. Low = 0,
  661. }