scheduler.rs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. //! # Dioxus uses a scheduler to run queued work in the correct order.
  2. //!
  3. //! ## Goals
  4. //! We try to prevent three different situations:
  5. //! 1. Running queued work after it could be dropped. Related issues (<https://github.com/DioxusLabs/dioxus/pull/1993>)
  6. //!
  7. //! User code often assumes that this property is true. For example, if this code reruns the child component after signal is changed to None, it will panic
  8. //! ```rust, ignore
  9. //! fn ParentComponent() -> Element {
  10. //! let signal: Signal<Option<i32>> = use_signal(None);
  11. //!
  12. //! rsx! {
  13. //! if signal.read().is_some() {
  14. //! ChildComponent { signal }
  15. //! }
  16. //! }
  17. //! }
  18. //!
  19. //! #[component]
  20. //! fn ChildComponent(signal: Signal<Option<i32>>) -> Element {
  21. //! // It feels safe to assume that signal is some because the parent component checked that it was some
  22. //! rsx! { "{signal.read().unwrap()}" }
  23. //! }
  24. //! ```
  25. //!
  26. //! 2. Running effects before the dom is updated. Related issues (<https://github.com/DioxusLabs/dioxus/issues/2307>)
  27. //!
  28. //! Effects can be used to run code that accesses the DOM directly. They should only run when the DOM is in an updated state. If they are run with an out of date version of the DOM, unexpected behavior can occur.
  29. //! ```rust, ignore
  30. //! fn EffectComponent() -> Element {
  31. //! let id = use_signal(0);
  32. //! use_effect(move || {
  33. //! let id = id.read();
  34. //! // This will panic if the id is not written to the DOM before the effect is run
  35. //! eval(format!(r#"document.getElementById("{id}").innerHTML = "Hello World";"#));
  36. //! });
  37. //!
  38. //! rsx! {
  39. //! div { id: "{id}" }
  40. //! }
  41. //! }
  42. //! ```
  43. //!
  44. //! 3. Observing out of date state. Related issues (<https://github.com/DioxusLabs/dioxus/issues/1935>)
  45. //!
  46. //! Where ever possible, updates should happen in an order that makes it impossible to observe an out of date state.
  47. //! ```rust, ignore
  48. //! fn OutOfDateComponent() -> Element {
  49. //! let id = use_signal(0);
  50. //! // When you read memo, it should **always** be two times the value of id
  51. //! let memo = use_memo(move || id() * 2);
  52. //! assert_eq!(memo(), id() * 2);
  53. //!
  54. //! // This should be true even if you update the value of id in the middle of the component
  55. //! id += 1;
  56. //! assert_eq!(memo(), id() * 2);
  57. //!
  58. //! rsx! {
  59. //! div { id: "{id}" }
  60. //! }
  61. //! }
  62. //! ```
  63. //!
  64. //! ## Implementation
  65. //!
  66. //! There are three different types of queued work that can be run by the virtualdom:
  67. //! 1. Dirty Scopes:
  68. //! Description: When a scope is marked dirty, a rerun of the scope will be scheduled. This will cause the scope to rerun and update the DOM if any changes are detected during the diffing phase.
  69. //! Priority: These are the highest priority tasks. Dirty scopes will be rerun in order from the scope closest to the root to the scope furthest from the root. We follow this order to ensure that if a higher component reruns and drops a lower component, the lower component will not be run after it should be dropped.
  70. //!
  71. //! 2. Tasks:
  72. //! Description: Futures spawned in the dioxus runtime each have an unique task id. When the waker for that future is called, the task is rerun.
  73. //! Priority: These are the second highest priority tasks. They are run after all other dirty scopes have been resolved because those dirty scopes may cause children (and the tasks those children own) to drop which should cancel the futures.
  74. //!
  75. //! 3. Effects:
  76. //! Description: Effects should always run after all changes to the DOM have been applied.
  77. //! Priority: These are the lowest priority tasks in the scheduler. They are run after all other dirty scopes and futures have been resolved. Other tasks may cause components to rerun, which would update the DOM. These effects should only run after the DOM has been updated.
  78. use crate::innerlude::Effect;
  79. use crate::ScopeId;
  80. use crate::Task;
  81. use crate::VirtualDom;
  82. use std::borrow::Borrow;
  83. use std::cell::RefCell;
  84. use std::collections::VecDeque;
  85. use std::hash::Hash;
  86. #[derive(Debug, Clone, Copy, Eq)]
  87. pub struct ScopeOrder {
  88. pub(crate) height: u32,
  89. pub(crate) id: ScopeId,
  90. }
  91. impl ScopeOrder {
  92. pub fn new(height: u32, id: ScopeId) -> Self {
  93. Self { height, id }
  94. }
  95. }
  96. impl PartialEq for ScopeOrder {
  97. fn eq(&self, other: &Self) -> bool {
  98. self.id == other.id
  99. }
  100. }
  101. impl PartialOrd for ScopeOrder {
  102. fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
  103. Some(self.cmp(other))
  104. }
  105. }
  106. impl Ord for ScopeOrder {
  107. fn cmp(&self, other: &Self) -> std::cmp::Ordering {
  108. self.height.cmp(&other.height).then(self.id.cmp(&other.id))
  109. }
  110. }
  111. impl Hash for ScopeOrder {
  112. fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
  113. self.id.hash(state);
  114. }
  115. }
  116. impl VirtualDom {
  117. /// Queue a task to be polled
  118. pub(crate) fn queue_task(&mut self, task: Task, order: ScopeOrder) {
  119. let mut dirty_tasks = self.runtime.dirty_tasks.borrow_mut();
  120. match dirty_tasks.get(&order) {
  121. Some(scope) => scope.queue_task(task),
  122. None => {
  123. let scope = DirtyTasks::from(order);
  124. scope.queue_task(task);
  125. dirty_tasks.insert(scope);
  126. }
  127. }
  128. }
  129. /// Queue a scope to be rerendered
  130. pub(crate) fn queue_scope(&mut self, order: ScopeOrder) {
  131. self.dirty_scopes.insert(order);
  132. }
  133. /// Check if there are any dirty scopes
  134. pub(crate) fn has_dirty_scopes(&self) -> bool {
  135. !self.dirty_scopes.is_empty()
  136. }
  137. /// Take the top task from the highest scope
  138. pub(crate) fn pop_task(&mut self) -> Option<Task> {
  139. let mut dirty_tasks = self.runtime.dirty_tasks.borrow_mut();
  140. let mut tasks = dirty_tasks.first()?;
  141. // If the scope doesn't exist for whatever reason, then we should skip it
  142. while !self.scopes.contains(tasks.order.id.0) {
  143. dirty_tasks.pop_first();
  144. tasks = dirty_tasks.first()?;
  145. }
  146. let mut tasks = tasks.tasks_queued.borrow_mut();
  147. let task = tasks.pop_front()?;
  148. if tasks.is_empty() {
  149. drop(tasks);
  150. dirty_tasks.pop_first();
  151. }
  152. Some(task)
  153. }
  154. /// Take any effects from the highest scope. This should only be called if there is no pending scope reruns or tasks
  155. pub(crate) fn pop_effect(&mut self) -> Option<Effect> {
  156. let mut pending_effects = self.runtime.pending_effects.borrow_mut();
  157. let mut effect = pending_effects.pop_first()?;
  158. // If the scope doesn't exist for whatever reason, then we should skip it
  159. while !self.scopes.contains(effect.order.id.0) {
  160. effect = pending_effects.pop_first()?;
  161. }
  162. Some(effect)
  163. }
  164. /// Take any work from the highest scope. This may include rerunning the scope and/or running tasks
  165. pub(crate) fn pop_work(&mut self) -> Option<Work> {
  166. let mut dirty_scope = self.dirty_scopes.first();
  167. // Pop any invalid scopes off of each dirty task;
  168. while let Some(scope) = dirty_scope {
  169. if !self.scopes.contains(scope.id.0) {
  170. self.dirty_scopes.pop_first();
  171. dirty_scope = self.dirty_scopes.first();
  172. } else {
  173. break;
  174. }
  175. }
  176. // Find the height of the highest dirty scope
  177. let dirty_task = {
  178. let mut dirty_tasks = self.runtime.dirty_tasks.borrow_mut();
  179. let mut dirty_task = dirty_tasks.first();
  180. // Pop any invalid tasks off of each dirty scope;
  181. while let Some(task) = dirty_task {
  182. if task.tasks_queued.borrow().is_empty() || !self.scopes.contains(task.order.id.0) {
  183. dirty_tasks.pop_first();
  184. dirty_task = dirty_tasks.first()
  185. } else {
  186. break;
  187. }
  188. }
  189. dirty_task.map(|task| task.order)
  190. };
  191. match (dirty_scope, dirty_task) {
  192. (Some(scope), Some(task)) => {
  193. let tasks_order = task.borrow();
  194. match scope.cmp(tasks_order) {
  195. std::cmp::Ordering::Less => {
  196. let scope = self.dirty_scopes.pop_first().unwrap();
  197. Some(Work::RerunScope(scope))
  198. }
  199. std::cmp::Ordering::Equal | std::cmp::Ordering::Greater => {
  200. Some(Work::PollTask(self.pop_task().unwrap()))
  201. }
  202. }
  203. }
  204. (Some(_), None) => {
  205. let scope = self.dirty_scopes.pop_first().unwrap();
  206. Some(Work::RerunScope(scope))
  207. }
  208. (None, Some(_)) => Some(Work::PollTask(self.pop_task().unwrap())),
  209. (None, None) => None,
  210. }
  211. }
  212. }
  213. #[derive(Debug)]
  214. pub enum Work {
  215. RerunScope(ScopeOrder),
  216. PollTask(Task),
  217. }
  218. #[derive(Debug, Clone, Eq)]
  219. pub(crate) struct DirtyTasks {
  220. pub order: ScopeOrder,
  221. pub tasks_queued: RefCell<VecDeque<Task>>,
  222. }
  223. impl From<ScopeOrder> for DirtyTasks {
  224. fn from(order: ScopeOrder) -> Self {
  225. Self {
  226. order,
  227. tasks_queued: VecDeque::new().into(),
  228. }
  229. }
  230. }
  231. impl DirtyTasks {
  232. pub fn queue_task(&self, task: Task) {
  233. let mut borrow_mut = self.tasks_queued.borrow_mut();
  234. // If the task is already queued, we don't need to do anything
  235. if borrow_mut.contains(&task) {
  236. return;
  237. }
  238. borrow_mut.push_back(task);
  239. }
  240. pub(crate) fn remove(&self, id: Task) {
  241. self.tasks_queued.borrow_mut().retain(|task| *task != id);
  242. }
  243. }
  244. impl Borrow<ScopeOrder> for DirtyTasks {
  245. fn borrow(&self) -> &ScopeOrder {
  246. &self.order
  247. }
  248. }
  249. impl PartialOrd for DirtyTasks {
  250. fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
  251. Some(self.order.cmp(&other.order))
  252. }
  253. }
  254. impl Ord for DirtyTasks {
  255. fn cmp(&self, other: &Self) -> std::cmp::Ordering {
  256. self.order.cmp(&other.order)
  257. }
  258. }
  259. impl PartialEq for DirtyTasks {
  260. fn eq(&self, other: &Self) -> bool {
  261. self.order == other.order
  262. }
  263. }
  264. impl Hash for DirtyTasks {
  265. fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
  266. self.order.hash(state);
  267. }
  268. }