Skip to main content

freya_core/
runner.rs

1use std::{
2    any::{
3        Any,
4        TypeId,
5    },
6    cell::RefCell,
7    cmp::Ordering,
8    collections::VecDeque,
9    fmt::Debug,
10    rc::Rc,
11    sync::atomic::AtomicU64,
12};
13
14use futures_lite::{
15    FutureExt,
16    StreamExt,
17};
18use itertools::Itertools;
19use pathgraph::PathGraph;
20use rustc_hash::{
21    FxHashMap,
22    FxHashSet,
23};
24use tiny_diff::DiffNode;
25
26use crate::{
27    current_context::CurrentContext,
28    diff_key::DiffKey,
29    element::{
30        Element,
31        ElementExt,
32        EventHandlerType,
33    },
34    events::{
35        data::{
36            Event,
37            EventType,
38        },
39        name::EventName,
40    },
41    node_id::NodeId,
42    path_element::PathElement,
43    prelude::{
44        Task,
45        TaskId,
46    },
47    reactive_context::ReactiveContext,
48    scope::{
49        PathNode,
50        Scope,
51        ScopeStorage,
52    },
53    scope_id::ScopeId,
54    tree::DiffModifies,
55};
56
57#[derive(Debug, PartialEq, Eq)]
58pub enum MutationRemove {
59    /// Because elements always have a different parent we can easily get their position relatively to their parent
60    Element { id: NodeId, index: u32 },
61    /// In the other hand, roots of Scopes are manually connected to their parent scopes, so getting their index is not worth the effort.
62    Scope { id: NodeId },
63}
64
65impl PartialOrd for MutationRemove {
66    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
67        Some(self.cmp(other))
68    }
69}
70
71impl Ord for MutationRemove {
72    fn cmp(&self, other: &Self) -> Ordering {
73        use MutationRemove::*;
74        match (self, other) {
75            // Order Element removals by index descending (so larger indices come first)
76            (Element { index: a, .. }, Element { index: b, .. }) => b.cmp(a),
77            // Elements come before Scopes
78            (Element { .. }, Scope { .. }) => Ordering::Less,
79            (Scope { .. }, Element { .. }) => Ordering::Greater,
80            // Order Scopes by id descending as well
81            (Scope { id: a }, Scope { id: b }) => b.cmp(a),
82        }
83    }
84}
85
86impl MutationRemove {
87    pub fn node_id(&self) -> NodeId {
88        match self {
89            Self::Element { id, .. } => *id,
90            Self::Scope { id } => *id,
91        }
92    }
93}
94
95pub struct MutationAdd {
96    pub node_id: NodeId,
97    pub parent_id: NodeId,
98    pub index: u32,
99    pub element: Rc<dyn ElementExt>,
100}
101
102pub struct MutationModified {
103    pub node_id: NodeId,
104    pub element: Rc<dyn ElementExt>,
105    pub flags: DiffModifies,
106}
107
108#[derive(Default)]
109pub struct Mutations {
110    /// New elements and their final sibling indices, inserted after removals.
111    pub added: Vec<MutationAdd>,
112    /// Updated elements and the flags identifying which states need refreshing.
113    pub modified: Vec<MutationModified>,
114    /// Elements and scope roots to remove before additions and reordering.
115    pub removed: Vec<MutationRemove>,
116    /// Final child order for reordered parents, applied after removals and additions.
117    pub moved: FxHashMap<NodeId, Vec<NodeId>>,
118}
119
120impl Mutations {
121    pub fn is_empty(&self) -> bool {
122        self.added.is_empty()
123            && self.modified.is_empty()
124            && self.removed.is_empty()
125            && self.moved.is_empty()
126    }
127}
128
129impl Debug for Mutations {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.write_fmt(format_args!(
132            "Added: {:?} Modified: {:?} Removed: {:?} Moved: {:?}",
133            self.added
134                .iter()
135                .map(|m| (m.node_id, m.parent_id, m.index))
136                .collect::<Vec<_>>(),
137            self.modified.iter().map(|m| m.node_id).collect::<Vec<_>>(),
138            self.removed,
139            self.moved
140        ))
141    }
142}
143
144pub enum Message {
145    MarkScopeAsDirty(ScopeId),
146    PollTask(TaskId),
147}
148
149/// Reported around every batch of dirty tasks polled by the [Runner].
150#[derive(Clone, Copy, PartialEq, Eq, Debug)]
151pub enum TasksPollStage {
152    Started,
153    Finished,
154}
155
156/// Parents receiving additions, keyed by new tree path, with their lazily found current path.
157type AdditionParents = FxHashMap<Box<[u32]>, (NodeId, Option<Vec<u32>>)>;
158
159type ReorderedParents = FxHashMap<NodeId, Box<[u32]>>;
160
161pub struct Runner {
162    pub scopes: FxHashMap<ScopeId, Rc<RefCell<Scope>>>,
163    pub scopes_storages: Rc<RefCell<FxHashMap<ScopeId, ScopeStorage>>>,
164
165    pub(crate) dirty_scopes: FxHashSet<ScopeId>,
166    pub(crate) dirty_tasks: VecDeque<TaskId>,
167
168    pub node_to_scope: FxHashMap<NodeId, ScopeId>,
169
170    pub(crate) node_id_counter: NodeId,
171    pub(crate) scope_id_counter: ScopeId,
172    pub(crate) task_id_counter: Rc<AtomicU64>,
173
174    pub(crate) tasks: Rc<RefCell<FxHashMap<TaskId, Rc<RefCell<Task>>>>>,
175
176    pub(crate) sender: futures_channel::mpsc::UnboundedSender<Message>,
177    pub(crate) receiver: futures_channel::mpsc::UnboundedReceiver<Message>,
178}
179
180impl Debug for Runner {
181    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
182        f.debug_struct("Runner")
183            .field("dirty_scopes", &self.dirty_scopes.len())
184            .field("dirty_tasks", &self.dirty_tasks.len())
185            .field("node_to_scope", &self.node_to_scope.len())
186            .field("scopes", &self.scopes.len())
187            .field("scopes_storages", &self.scopes_storages.borrow().len())
188            .field("tasks", &self.tasks.borrow().len())
189            .finish()
190    }
191}
192
193impl Drop for Runner {
194    fn drop(&mut self) {
195        // Drop scopes from deepest to shallowest.
196        for (scope_id, _scope) in self
197            .scopes
198            .drain()
199            .sorted_by_key(|s| s.1.borrow().height)
200            .rev()
201        {
202            CurrentContext::run_with_reactive(self.create_context(scope_id), || {
203                let mut removed_tasks = Vec::new();
204
205                self.tasks.borrow_mut().retain(|task_id, task| {
206                    if task.borrow().scope_id == scope_id {
207                        removed_tasks.push((*task_id, task.clone()));
208                        false
209                    } else {
210                        true
211                    }
212                });
213                drop(removed_tasks);
214                let _scope = self.scopes_storages.borrow_mut().remove(&scope_id);
215            });
216        }
217    }
218}
219
220impl Runner {
221    pub fn new(root: impl Fn() -> Element + 'static) -> Self {
222        let (sender, receiver) = futures_channel::mpsc::unbounded::<Message>();
223        Self {
224            scopes: FxHashMap::from_iter([(
225                ScopeId::ROOT,
226                Rc::new(RefCell::new(Scope {
227                    parent_node_id_in_parent: NodeId::ROOT,
228                    path_in_parent: Box::from([]),
229                    height: 0,
230                    parent_id: None,
231                    id: ScopeId::ROOT,
232                    key: DiffKey::Root,
233                    comp: Rc::new(move |_| root()),
234                    props: Rc::new(()),
235                    element: None,
236                    nodes: {
237                        let mut map = PathGraph::new();
238                        map.insert(
239                            &[],
240                            PathNode {
241                                node_id: NodeId::ROOT,
242                                scope_id: None,
243                            },
244                        );
245                        map
246                    },
247                })),
248            )]),
249            scopes_storages: Rc::new(RefCell::new(FxHashMap::from_iter([(
250                ScopeId::ROOT,
251                ScopeStorage::new(None, |writer| {
252                    ReactiveContext::new_for_scope(sender.clone(), ScopeId::ROOT, writer)
253                }),
254            )]))),
255
256            node_to_scope: FxHashMap::from_iter([(NodeId::ROOT, ScopeId::ROOT)]),
257
258            node_id_counter: NodeId::ROOT,
259            scope_id_counter: ScopeId::ROOT,
260            task_id_counter: Rc::default(),
261
262            dirty_tasks: VecDeque::default(),
263            dirty_scopes: FxHashSet::from_iter([ScopeId::ROOT]),
264
265            tasks: Rc::default(),
266
267            sender,
268            receiver,
269        }
270    }
271
272    #[cfg(all(debug_assertions, feature = "debug-integrity"))]
273    #[cfg_attr(feature = "hotpath", hotpath::measure)]
274    pub fn verify_scopes_integrity(&self) {
275        let mut visited = FxHashSet::default();
276        let size = self.scopes.len();
277        let mut buffer = vec![ScopeId::ROOT];
278        while let Some(scope_id) = buffer.pop() {
279            if visited.contains(&scope_id) {
280                continue;
281            }
282            visited.insert(scope_id);
283            let scope = self.scopes.get(&scope_id).unwrap();
284            let scope = scope.borrow();
285            if let Some(parent) = scope.parent_id {
286                buffer.push(parent);
287            }
288            scope.nodes.traverse(&[], |_, &PathNode { scope_id, .. }| {
289                if let Some(scope_id) = scope_id {
290                    buffer.push(scope_id);
291                }
292            });
293        }
294        assert_eq!(size, visited.len())
295    }
296
297    fn create_context(&self, scope_id: ScopeId) -> CurrentContext {
298        CurrentContext {
299            scope_id,
300            scopes_storages: self.scopes_storages.clone(),
301            tasks: self.tasks.clone(),
302            task_id_counter: self.task_id_counter.clone(),
303            sender: self.sender.clone(),
304        }
305    }
306
307    pub fn with_root_context<T>(&self, run: impl FnOnce() -> T) -> T {
308        self.run_in(run)
309    }
310
311    pub fn provide_root_context<T: 'static + Clone>(&mut self, context: impl FnOnce() -> T) -> T {
312        self.run_in(|| {
313            let context = context();
314            let mut scopes_storages = self.scopes_storages.borrow_mut();
315            let root_scope_storage = scopes_storages.get_mut(&ScopeId::ROOT).unwrap();
316            root_scope_storage
317                .contexts
318                .insert(TypeId::of::<T>(), Rc::new(context.clone()));
319
320            context
321        })
322    }
323
324    /// Dispatches an event, returning whether its default action remains enabled.
325    /// Missing nodes are ignored without cancelling other queued events.
326    pub fn handle_event(
327        &mut self,
328        node_id: impl Into<NodeId>,
329        event_name: EventName,
330        event_type: EventType,
331        bubbles: bool,
332    ) -> bool {
333        let node_id = node_id.into();
334        #[cfg(debug_assertions)]
335        tracing::info!("Handling event {event_name:?} for {node_id:?}");
336        let propagate = Rc::new(RefCell::new(bubbles));
337        let default = Rc::new(RefCell::new(true));
338
339        let Some(scope_id) = self.node_to_scope.get(&node_id) else {
340            return true;
341        };
342        let Some(path) = self
343            .scopes
344            .get(scope_id)
345            .unwrap()
346            .borrow()
347            .nodes
348            .find_path(|value| {
349                value
350                    == Some(&PathNode {
351                        node_id,
352                        scope_id: None,
353                    })
354            })
355        else {
356            return true;
357        };
358
359        let mut current_target = Some((path, *scope_id));
360        while let Some((path, scope_id)) = current_target.take() {
361            let scope = self.scopes.get(&scope_id).cloned().unwrap();
362            scope.borrow().with_element(&path, |element| {
363                match element {
364                    PathElement::Component { .. } => {
365                        unreachable!()
366                    }
367                    PathElement::Element { element, .. } => {
368                        CurrentContext::run(self.create_context(scope_id), || {
369                            match &event_type {
370                                EventType::AccessibilityAction(data) => {
371                                    if let Some(event_handlers) = element.events_handlers()
372                                        && let Some(EventHandlerType::AccessibilityAction(handler)) =
373                                            event_handlers.get(&event_name)
374                                    {
375                                        handler.call(Event {
376                                            data: data.clone(),
377                                            propagate: propagate.clone(),
378                                            default: default.clone(),
379                                        });
380                                    }
381                                }
382                                EventType::Mouse(data) => {
383                                    let event_handlers = element.events_handlers();
384                                    if let Some(event_handlers) = event_handlers {
385                                        match event_handlers.get(&event_name) {
386                                            Some(EventHandlerType::Mouse(handler)) => {
387                                                handler.call(Event {
388                                                    data: data.clone(),
389                                                    propagate: propagate.clone(),
390                                                    default: default.clone(),
391                                                });
392                                            }
393                                            Some(_) => unreachable!(),
394                                            _ => {}
395                                        }
396                                    }
397                                }
398                                EventType::Keyboard(data) => {
399                                    let event_handlers = element.events_handlers();
400                                    if let Some(event_handlers) = event_handlers {
401                                        match event_handlers.get(&event_name) {
402                                            Some(EventHandlerType::Keyboard(handler)) => {
403                                                handler.call(Event {
404                                                    data: data.clone(),
405                                                    propagate: propagate.clone(),
406                                                    default: default.clone(),
407                                                });
408                                            }
409                                            Some(_) => unreachable!(),
410                                            _ => {}
411                                        }
412                                    }
413                                }
414                                EventType::Sized(data) => {
415                                    let event_handlers = element.events_handlers();
416                                    if let Some(event_handlers) = event_handlers {
417                                        match event_handlers.get(&event_name) {
418                                            Some(EventHandlerType::Sized(handler)) => {
419                                                handler.call(Event {
420                                                    data: data.clone(),
421                                                    propagate: propagate.clone(),
422                                                    default: default.clone(),
423                                                });
424                                            }
425                                            Some(_) => unreachable!(),
426                                            _ => {}
427                                        }
428                                    }
429                                }
430                                EventType::Visible(data) => {
431                                    let event_handlers = element.events_handlers();
432                                    if let Some(event_handlers) = event_handlers {
433                                        match event_handlers.get(&event_name) {
434                                            Some(EventHandlerType::Visible(handler)) => {
435                                                handler.call(Event {
436                                                    data: data.clone(),
437                                                    propagate: propagate.clone(),
438                                                    default: default.clone(),
439                                                });
440                                            }
441                                            Some(_) => unreachable!(),
442                                            _ => {}
443                                        }
444                                    }
445                                }
446                                EventType::Styled(data) => {
447                                    let event_handlers = element.events_handlers();
448                                    if let Some(event_handlers) = event_handlers {
449                                        match event_handlers.get(&event_name) {
450                                            Some(EventHandlerType::Styled(handler)) => {
451                                                handler.call(Event {
452                                                    data: data.clone(),
453                                                    propagate: propagate.clone(),
454                                                    default: default.clone(),
455                                                });
456                                            }
457                                            Some(_) => unreachable!(),
458                                            _ => {}
459                                        }
460                                    }
461                                }
462                                EventType::Wheel(data) => {
463                                    let event_handlers = element.events_handlers();
464                                    if let Some(event_handlers) = event_handlers {
465                                        match event_handlers.get(&event_name) {
466                                            Some(EventHandlerType::Wheel(handler)) => {
467                                                handler.call(Event {
468                                                    data: data.clone(),
469                                                    propagate: propagate.clone(),
470                                                    default: default.clone(),
471                                                });
472                                            }
473                                            Some(_) => unreachable!(),
474                                            _ => {}
475                                        }
476                                    }
477                                }
478                                EventType::Touch(data) => {
479                                    let event_handlers = element.events_handlers();
480                                    if let Some(event_handlers) = event_handlers {
481                                        match event_handlers.get(&event_name) {
482                                            Some(EventHandlerType::Touch(handler)) => {
483                                                handler.call(Event {
484                                                    data: data.clone(),
485                                                    propagate: propagate.clone(),
486                                                    default: default.clone(),
487                                                });
488                                            }
489                                            Some(_) => unreachable!(),
490                                            _ => {}
491                                        }
492                                    }
493                                }
494                                EventType::Pointer(data) => {
495                                    let event_handlers = element.events_handlers();
496                                    if let Some(event_handlers) = event_handlers {
497                                        match event_handlers.get(&event_name) {
498                                            Some(EventHandlerType::Pointer(handler)) => {
499                                                handler.call(Event {
500                                                    data: data.clone(),
501                                                    propagate: propagate.clone(),
502                                                    default: default.clone(),
503                                                });
504                                            }
505                                            Some(_) => unreachable!(),
506                                            _ => {}
507                                        }
508                                    }
509                                }
510                                EventType::File(data) => {
511                                    let event_handlers = element.events_handlers();
512                                    if let Some(event_handlers) = event_handlers {
513                                        match event_handlers.get(&event_name) {
514                                            Some(EventHandlerType::File(handler)) => {
515                                                handler.call(Event {
516                                                    data: data.clone(),
517                                                    propagate: propagate.clone(),
518                                                    default: default.clone(),
519                                                });
520                                            }
521                                            Some(_) => unreachable!(),
522                                            _ => {}
523                                        }
524                                    }
525                                }
526                                EventType::ImePreedit(data) => {
527                                    let event_handlers = element.events_handlers();
528                                    if let Some(event_handlers) = event_handlers {
529                                        match event_handlers.get(&event_name) {
530                                            Some(EventHandlerType::ImePreedit(handler)) => {
531                                                handler.call(Event {
532                                                    data: data.clone(),
533                                                    propagate: propagate.clone(),
534                                                    default: default.clone(),
535                                                });
536                                            }
537                                            Some(_) => unreachable!(),
538                                            _ => {}
539                                        }
540                                    }
541                                }
542                            }
543
544                            // Bubble up if desired
545                            if *propagate.borrow() {
546                                if path.len() > 1 {
547                                    // Change the target to this element parent (still in the same Scope)
548                                    current_target
549                                        .replace((path[..path.len() - 1].to_vec(), scope_id));
550                                } else {
551                                    let mut parent_scope_id = scope.borrow().parent_id;
552                                    // Otherwise change the target to this element parent in the parent Scope
553                                    loop {
554                                        if let Some(parent_id) = parent_scope_id.take() {
555                                            let parent_scope = self.scopes.get(&parent_id).unwrap();
556                                            let path =
557                                                parent_scope.borrow().nodes.find_path(|value| {
558                                                    value
559                                                        == Some(&PathNode {
560                                                            node_id: scope
561                                                                .borrow()
562                                                                .parent_node_id_in_parent,
563                                                            scope_id: None,
564                                                        })
565                                                });
566                                            if let Some(path) = path {
567                                                current_target.replace((path, parent_id));
568                                                break;
569                                            } else {
570                                                parent_scope_id = parent_scope.borrow().parent_id;
571                                            }
572                                        } else {
573                                            return;
574                                        }
575                                    }
576                                }
577                            }
578                        })
579                    }
580                }
581            });
582        }
583        *default.borrow()
584    }
585
586    #[cfg_attr(feature = "hotpath", hotpath::measure)]
587    pub async fn handle_events(&mut self) {
588        self.handle_events_with(&mut |_| {}).await
589    }
590
591    /// Like [Self::handle_events], notifying the observer around every tasks polling batch.
592    pub async fn handle_events_with(&mut self, observer: &mut dyn FnMut(TasksPollStage)) {
593        loop {
594            while let Ok(msg) = self.receiver.try_recv() {
595                match msg {
596                    Message::MarkScopeAsDirty(scope_id) => {
597                        self.dirty_scopes.insert(scope_id);
598                    }
599                    Message::PollTask(task_id) => {
600                        self.dirty_tasks.push_back(task_id);
601                    }
602                }
603            }
604
605            if !self.dirty_scopes.is_empty() {
606                return;
607            }
608
609            self.poll_dirty_tasks(observer);
610
611            if !self.dirty_scopes.is_empty() {
612                return;
613            }
614
615            let Some(msg) = self.receiver.next().await else {
616                return;
617            };
618            match msg {
619                Message::MarkScopeAsDirty(scope_id) => {
620                    self.dirty_scopes.insert(scope_id);
621                }
622                Message::PollTask(task_id) => {
623                    self.dirty_tasks.push_back(task_id);
624                }
625            }
626        }
627    }
628
629    /// Like [Self::handle_events], but returns instead of waiting for more work.
630    #[cfg_attr(feature = "hotpath", hotpath::measure)]
631    pub fn handle_events_immediately(&mut self) {
632        self.handle_events_immediately_with(&mut |_| {})
633    }
634
635    /// Like [Self::handle_events_immediately], notifying the observer around every tasks polling
636    /// batch.
637    ///
638    /// Tasks are polled even when there are dirty scopes, otherwise a constant flow of events
639    /// would starve them.
640    pub fn handle_events_immediately_with(&mut self, observer: &mut dyn FnMut(TasksPollStage)) {
641        while let Ok(msg) = self.receiver.try_recv() {
642            match msg {
643                Message::MarkScopeAsDirty(scope_id) => {
644                    self.dirty_scopes.insert(scope_id);
645                }
646                Message::PollTask(task_id) => {
647                    self.dirty_tasks.push_back(task_id);
648                }
649            }
650        }
651
652        self.poll_dirty_tasks(observer);
653    }
654
655    /// Poll the dirty tasks, notifying the observer around the batch.
656    fn poll_dirty_tasks(&mut self, observer: &mut dyn FnMut(TasksPollStage)) {
657        if self.dirty_tasks.is_empty() {
658            return;
659        }
660
661        observer(TasksPollStage::Started);
662
663        while let Some(task_id) = self.dirty_tasks.pop_front() {
664            let Some(task) = self.tasks.borrow().get(&task_id).cloned() else {
665                continue;
666            };
667            let mut task = task.borrow_mut();
668            let waker = task.waker.clone();
669
670            let mut cx = std::task::Context::from_waker(&waker);
671
672            CurrentContext::run(
673                {
674                    let Some(scope) = self.scopes.get(&task.scope_id) else {
675                        continue;
676                    };
677                    self.create_context(scope.borrow().id)
678                },
679                || {
680                    let poll_result = task.future.poll(&mut cx);
681                    if poll_result.is_ready() {
682                        let _ = self.tasks.borrow_mut().remove(&task_id);
683                    }
684                },
685            );
686        }
687
688        observer(TasksPollStage::Finished);
689    }
690
691    #[cfg_attr(feature = "hotpath", hotpath::measure)]
692    pub fn sync_and_update(&mut self) -> Mutations {
693        self.handle_events_immediately();
694        use itertools::Itertools;
695
696        // No need to check anything
697        if self.dirty_scopes.is_empty() {
698            return Mutations::default();
699        }
700
701        #[cfg(all(debug_assertions, feature = "debug-integrity"))]
702        self.verify_scopes_integrity();
703
704        let mut mutations = Mutations::default();
705        let mut pending_reorders = FxHashMap::default();
706
707        let dirty_scopes = self
708            .dirty_scopes
709            .drain()
710            .filter_map(|id| self.scopes.get(&id).cloned())
711            .sorted_by_key(|s| s.borrow().height)
712            .map(|s| s.borrow().id)
713            .collect::<Box<[_]>>();
714
715        let mut visited_scopes = FxHashSet::default();
716
717        for scope_id in dirty_scopes {
718            // No need to run scopes more than once
719            if visited_scopes.contains(&scope_id) {
720                continue;
721            }
722
723            let Some(scope_rc) = self.scopes.get(&scope_id).cloned() else {
724                continue;
725            };
726
727            let scope_id = scope_rc.borrow().id;
728
729            let element = CurrentContext::run_with_reactive(self.create_context(scope_id), || {
730                let scope = scope_rc.borrow();
731                #[cfg(feature = "hotreload")]
732                {
733                    subsecond::call(|| (scope.comp)(scope.props.clone()))
734                }
735                #[cfg(not(feature = "hotreload"))]
736                {
737                    (scope.comp)(scope.props.clone())
738                }
739            });
740
741            let path_element = PathElement::from_element(vec![0], element);
742            let mut diff = Diff::default();
743            path_element.diff(scope_rc.borrow().element.as_ref(), &mut diff);
744
745            self.apply_diff(
746                &scope_rc,
747                diff,
748                &mut mutations,
749                &path_element,
750                &mut pending_reorders,
751            );
752
753            self.run_scope(
754                &scope_rc,
755                &path_element,
756                &mut mutations,
757                &mut visited_scopes,
758                &mut pending_reorders,
759            );
760
761            let mut scopes_storages = self.scopes_storages.borrow_mut();
762            let scope_storage = scopes_storages.get_mut(&scope_rc.borrow().id).unwrap();
763            scope_storage.current_value = 0;
764            scope_storage.current_run += 1;
765
766            scope_rc.borrow_mut().element = Some(path_element);
767        }
768
769        // Collect final child orders after all scopes render, including replaced component roots.
770        for (parent_node_id, path) in pending_reorders {
771            let Some(scope_id) = self.node_to_scope.get(&parent_node_id) else {
772                continue;
773            };
774            let scope = self.scopes[scope_id].borrow();
775            let mut children = Vec::with_capacity(scope.nodes.len(&path).unwrap_or_default());
776            scope.nodes.traverse_1_level(&path, |_, node| {
777                children.push(match node.scope_id {
778                    Some(scope_id) => self.find_scope_root_node_id(scope_id),
779                    None => node.node_id,
780                });
781            });
782            mutations.moved.insert(parent_node_id, children);
783        }
784
785        mutations
786    }
787
788    pub fn run_in<T>(&self, run: impl FnOnce() -> T) -> T {
789        CurrentContext::run(self.create_context(ScopeId::ROOT), run)
790    }
791
792    #[cfg_attr(feature = "hotpath", hotpath::measure)]
793    fn run_scope(
794        &mut self,
795        scope: &Rc<RefCell<Scope>>,
796        element: &PathElement,
797        mutations: &mut Mutations,
798        visited_scopes: &mut FxHashSet<ScopeId>,
799        pending_reorders: &mut ReorderedParents,
800    ) {
801        visited_scopes.insert(scope.borrow().id);
802        match element {
803            PathElement::Component {
804                comp,
805                props,
806                key,
807                path,
808            } => {
809                // Safe to unwrap because this is a component
810                let assigned_scope_id = scope
811                    .borrow()
812                    .nodes
813                    .get(path)
814                    .and_then(|path_node| path_node.scope_id)
815                    .unwrap();
816
817                let parent_node_id = if path.as_ref() == [0] {
818                    scope.borrow().parent_node_id_in_parent
819                } else {
820                    scope
821                        .borrow()
822                        .nodes
823                        .get(&path[..path.len() - 1])
824                        .unwrap()
825                        .node_id
826                };
827
828                if let Some(Ok(mut existing_scope)) = self
829                    .scopes
830                    .get(&assigned_scope_id)
831                    .map(|s| s.try_borrow_mut())
832                {
833                    let key_changed = existing_scope.key != *key;
834                    // Colliding keys can pair components of different types, which requires a full reset
835                    let type_changed = (existing_scope.props.as_ref() as &dyn Any).type_id()
836                        != (props.as_ref() as &dyn Any).type_id();
837                    if key_changed || type_changed || existing_scope.props.changed(props.as_ref()) {
838                        self.dirty_scopes.insert(assigned_scope_id);
839                        existing_scope.props = props.clone();
840
841                        if key_changed || type_changed {
842                            existing_scope.key = key.clone();
843                            existing_scope.comp = comp.clone();
844                            self.scopes_storages
845                                .borrow_mut()
846                                .get_mut(&assigned_scope_id)
847                                .unwrap()
848                                .reset();
849                        }
850                    }
851                } else {
852                    self.scopes.insert(
853                        assigned_scope_id,
854                        Rc::new(RefCell::new(Scope {
855                            parent_node_id_in_parent: parent_node_id,
856                            path_in_parent: path.clone(),
857                            height: scope.borrow().height + 1,
858                            parent_id: Some(scope.borrow().id),
859                            id: assigned_scope_id,
860                            key: key.clone(),
861                            comp: comp.clone(),
862                            props: props.clone(),
863                            element: None,
864                            nodes: PathGraph::default(),
865                        })),
866                    );
867                    self.scopes_storages.borrow_mut().insert(
868                        assigned_scope_id,
869                        ScopeStorage::new(Some(scope.borrow().id), |writer| {
870                            ReactiveContext::new_for_scope(
871                                self.sender.clone(),
872                                assigned_scope_id,
873                                writer,
874                            )
875                        }),
876                    );
877                    self.dirty_scopes.insert(assigned_scope_id);
878                }
879
880                let was_dirty = self.dirty_scopes.remove(&assigned_scope_id);
881
882                if !was_dirty {
883                    // No need to rerun scope if it is not dirty
884                    return;
885                }
886
887                let scope_rc = self.scopes.get(&assigned_scope_id).cloned().unwrap();
888
889                let element = hotpath::measure_block!("Scope Rendering", {
890                    CurrentContext::run_with_reactive(
891                        self.create_context(assigned_scope_id),
892                        || {
893                            let scope = scope_rc.borrow();
894                            #[cfg(feature = "hotreload")]
895                            {
896                                subsecond::call(|| (scope.comp)(scope.props.clone()))
897                            }
898                            #[cfg(not(feature = "hotreload"))]
899                            {
900                                (scope.comp)(scope.props.clone())
901                            }
902                        },
903                    )
904                });
905
906                let path_element = PathElement::from_element(vec![0], element);
907                let mut diff = Diff::default();
908                path_element.diff(scope_rc.borrow().element.as_ref(), &mut diff);
909
910                self.apply_diff(&scope_rc, diff, mutations, &path_element, pending_reorders);
911
912                self.run_scope(
913                    &scope_rc,
914                    &path_element,
915                    mutations,
916                    visited_scopes,
917                    pending_reorders,
918                );
919                let mut scopes_storages = self.scopes_storages.borrow_mut();
920                let scope_storage = scopes_storages.get_mut(&assigned_scope_id).unwrap();
921                scope_storage.current_value = 0;
922                scope_storage.current_run += 1;
923
924                scope_rc.borrow_mut().element = Some(path_element);
925            }
926            PathElement::Element { elements, .. } => {
927                for element in elements.iter() {
928                    self.run_scope(scope, element, mutations, visited_scopes, pending_reorders);
929                }
930            }
931        }
932    }
933
934    /// Recursively traverse up in the scopes tree until a suitable (non-root) slot is found to put an element.
935    /// Returns a parent node id and a slot index pointing to one of its children.
936    fn find_scope_root_parent_info(
937        &self,
938        parent_id: Option<ScopeId>,
939        parent_node_id: NodeId,
940        scope_id: ScopeId,
941    ) -> (NodeId, u32) {
942        let mut index_inside_parent = 0;
943
944        if let Some(parent_id) = parent_id {
945            let mut buffer = Some((parent_id, parent_node_id, scope_id));
946            while let Some((parent_id, parent_node_id, scope_id)) = buffer.take() {
947                let parent_scope = self.scopes.get(&parent_id).unwrap();
948                let parent_scope = parent_scope.borrow();
949
950                let scope = self.scopes.get(&scope_id).unwrap();
951                let scope = scope.borrow();
952
953                let path_node_parent = parent_scope.nodes.find(|v| {
954                    if let Some(v) = v {
955                        v.node_id == parent_node_id
956                    } else {
957                        false
958                    }
959                });
960
961                if let Some(path_node_parent) = path_node_parent {
962                    if let Some(scope_id) = path_node_parent.scope_id {
963                        if let Some(parent_id) = parent_scope.parent_id {
964                            // The found element turns out to be a component so go to it to continue looking
965                            buffer.replace((
966                                parent_id,
967                                parent_scope.parent_node_id_in_parent,
968                                scope_id,
969                            ));
970                        } else {
971                            assert_eq!(scope_id, ScopeId::ROOT);
972                        }
973                    } else {
974                        // Found an Element parent so we get the index from the path
975                        index_inside_parent = *scope.path_in_parent.last().unwrap();
976                        return (parent_node_id, index_inside_parent);
977                    }
978                } else if let Some(new_parent_id) = parent_scope.parent_id {
979                    // If no element was found we go to the parent scope
980                    buffer.replace((
981                        new_parent_id,
982                        parent_scope.parent_node_id_in_parent,
983                        parent_id,
984                    ));
985                }
986            }
987        } else {
988            assert_eq!(scope_id, ScopeId::ROOT);
989        }
990
991        (parent_node_id, index_inside_parent)
992    }
993
994    /// Recursively finds the root element [NodeId] of a scope.
995    /// When a scope's first child is another component (scope), this follows
996    /// the chain until it finds the first actual element.
997    fn find_scope_root_node_id(&self, scope_id: ScopeId) -> NodeId {
998        let scope_rc = self.scopes.get(&scope_id).unwrap();
999        let scope = scope_rc.borrow();
1000        let path_node = scope.nodes.get(&[0]).unwrap();
1001        if let Some(child_scope_id) = path_node.scope_id {
1002            self.find_scope_root_node_id(child_scope_id)
1003        } else {
1004            path_node.node_id
1005        }
1006    }
1007
1008    /// Inserts an added node in `scope.nodes` and emits the matching mutation. Parents are
1009    /// resolved by identity because paths in `scope.nodes` shift while a diff is applied.
1010    fn process_addition(
1011        &mut self,
1012        scope: &Rc<RefCell<Scope>>,
1013        added: &[u32],
1014        path_element: &PathElement,
1015        mutations: &mut Mutations,
1016        parents_to_resync_scopes: &mut FxHashSet<Box<[u32]>>,
1017        addition_parents: &mut AdditionParents,
1018    ) {
1019        let (parent_node_id, index_inside_parent, insertion_path) = if added == [0] {
1020            let parent_id = scope.borrow().parent_id;
1021            let scope_id = scope.borrow().id;
1022            let parent_node_id = scope.borrow().parent_node_id_in_parent;
1023            let (parent_node_id, index_inside_parent) =
1024                self.find_scope_root_parent_info(parent_id, parent_node_id, scope_id);
1025            (parent_node_id, index_inside_parent, vec![0])
1026        } else {
1027            let parent_path = &added[..added.len() - 1];
1028            parents_to_resync_scopes.insert(Box::from(parent_path));
1029
1030            let (parent_node_id, cached_path) = addition_parents.get_mut(parent_path).unwrap();
1031            let parent_node_id = *parent_node_id;
1032
1033            // Pre existing parents are found once and cached, added parents get cached on insertion
1034            let mut insertion_path = cached_path
1035                .get_or_insert_with(|| {
1036                    // The parent usually already sits at its new path, search only if it shifted
1037                    let is_at_new_path = scope
1038                        .borrow()
1039                        .nodes
1040                        .get(parent_path)
1041                        .is_some_and(|node| node.node_id == parent_node_id);
1042                    if is_at_new_path {
1043                        parent_path.to_vec()
1044                    } else {
1045                        scope
1046                            .borrow()
1047                            .nodes
1048                            .find_path(|v| v.is_some_and(|node| node.node_id == parent_node_id))
1049                            .unwrap()
1050                    }
1051                })
1052                .clone();
1053            insertion_path.push(added[added.len() - 1]);
1054            (parent_node_id, added[added.len() - 1], insertion_path)
1055        };
1056
1057        self.node_id_counter += 1;
1058
1059        path_element.with_element(added, |element| {
1060            let scope_id = match element {
1061                PathElement::Component { .. } => {
1062                    self.scope_id_counter += 1;
1063                    Some(self.scope_id_counter)
1064                }
1065                PathElement::Element { element, .. } => {
1066                    mutations.added.push(MutationAdd {
1067                        node_id: self.node_id_counter,
1068                        parent_id: parent_node_id,
1069                        index: index_inside_parent,
1070                        element: element.clone(),
1071                    });
1072                    self.node_to_scope
1073                        .insert(self.node_id_counter, scope.borrow().id);
1074                    None
1075                }
1076            };
1077
1078            scope.borrow_mut().nodes.insert(
1079                &insertion_path,
1080                PathNode {
1081                    node_id: self.node_id_counter,
1082                    scope_id,
1083                },
1084            );
1085            addition_parents.insert(
1086                Box::from(added),
1087                (self.node_id_counter, Some(insertion_path)),
1088            );
1089        });
1090    }
1091
1092    #[cfg_attr(feature = "hotpath", hotpath::measure)]
1093    fn apply_diff(
1094        &mut self,
1095        scope: &Rc<RefCell<Scope>>,
1096        diff: Diff,
1097        mutations: &mut Mutations,
1098        path_element: &PathElement,
1099        pending_reorders: &mut ReorderedParents,
1100    ) {
1101        let mut moved_nodes = FxHashMap::<Box<[u32]>, Vec<Option<NodeId>>>::default();
1102        let mut parents_to_resync_scopes = FxHashSet::default();
1103
1104        // Children of each reordered parent, by old index
1105        for parent in diff.moved.keys() {
1106            parents_to_resync_scopes.insert(parent.clone());
1107            // `parent` is a new-tree path. If the parent itself was moved, its path in the
1108            // old nodes tree will differ, so resolve it before any lookup.
1109            let old_parent = resolve_old_path(parent, &diff.moved);
1110            let parent_node_id = scope.borrow().nodes.get(&old_parent).unwrap().node_id;
1111            let mut children =
1112                vec![None; scope.borrow().nodes.len(&old_parent).unwrap_or_default()];
1113            scope
1114                .borrow()
1115                .nodes
1116                .traverse_1_level(&old_parent, |path, node| {
1117                    children[path[path.len() - 1] as usize] = Some(node.node_id);
1118                });
1119            pending_reorders.insert(parent_node_id, parent.clone());
1120            moved_nodes.insert(parent.clone(), children);
1121        }
1122
1123        // Capture the identity of addition parents while their old tree paths are still valid
1124        let mut addition_parents = FxHashMap::default();
1125        for added in &diff.added {
1126            if added.len() <= 1 {
1127                continue;
1128            }
1129            let parent = &added[..added.len() - 1];
1130            if addition_parents.contains_key(parent) {
1131                continue;
1132            }
1133            let old_parent = resolve_old_path(parent, &diff.moved);
1134            if let Some(node) = scope.borrow().nodes.get(&old_parent) {
1135                addition_parents.insert(Box::from(parent), (node.node_id, None));
1136            }
1137        }
1138
1139        // Collect a set of branches to remove in cascade
1140        let mut selected_roots: FxHashMap<&[u32], FxHashSet<&[u32]>> = FxHashMap::default();
1141        let mut scope_removal_buffer = vec![];
1142
1143        // Given some removals like:
1144        // [
1145        //     [0,2],
1146        //     [0,1,0,1],
1147        //     [0,1,0,2],
1148        //     [0,3],
1149        //     [0,1,5,8],
1150        // ]
1151        //
1152        // We want them ordered like:
1153        // [
1154        //     [0,3],
1155        //     [0,2],
1156        //     [0,1,5,8],
1157        //     [0,1,0,2],
1158        //     [0,1,0,1],
1159        // ]
1160        //
1161        // This way any removal does not move the next removals
1162        'remove: for removed in diff.removed.iter().sorted_by(|a, b| {
1163            for (x, y) in a.iter().zip(b.iter()) {
1164                match x.cmp(y) {
1165                    Ordering::Equal => continue,
1166                    non_eq => return non_eq.reverse(),
1167                }
1168            }
1169            b.len().cmp(&a.len())
1170        }) {
1171            parents_to_resync_scopes.insert(Box::from(&removed[..removed.len() - 1]));
1172
1173            let path_node = scope.borrow().nodes.get(removed).cloned();
1174            if let Some(PathNode { node_id, scope_id }) = path_node {
1175                if scope_id.is_none() {
1176                    let removal = if removed.as_ref() == [0] {
1177                        MutationRemove::Scope { id: node_id }
1178                    } else {
1179                        MutationRemove::Element {
1180                            id: node_id,
1181                            index: removed[removed.len() - 1],
1182                        }
1183                    };
1184                    mutations.removed.push(removal);
1185                }
1186
1187                // Skip if this removed path is already covered by a previously selected root
1188                for (root, inner) in &mut selected_roots {
1189                    if is_descendant(removed, root) {
1190                        inner.insert(removed);
1191                        continue 'remove;
1192                    }
1193                }
1194
1195                // Remove any previously selected roots that are descendants of this new (higher) removed path
1196                selected_roots.retain(|root, _| !is_descendant(root, removed));
1197
1198                selected_roots
1199                    .entry(&removed[..removed.len() - 1])
1200                    .or_default()
1201                    .insert(removed);
1202            } else {
1203                unreachable!()
1204            }
1205        }
1206
1207        // Traverse each chosen branch root and queue nested scopes
1208        for (root, removed) in selected_roots.iter().sorted_by(|(a, _), (b, _)| {
1209            for (x, y) in a.iter().zip(b.iter()) {
1210                match x.cmp(y) {
1211                    Ordering::Equal => continue,
1212                    non_eq => return non_eq.reverse(),
1213                }
1214            }
1215            b.len().cmp(&a.len())
1216        }) {
1217            scope.borrow_mut().nodes.retain(
1218                root,
1219                |p, _| !removed.contains(p),
1220                |_, &PathNode { scope_id, node_id }| {
1221                    if let Some(scope_id) = scope_id {
1222                        // Queue scope to be removed
1223                        scope_removal_buffer.push(self.scopes.get(&scope_id).cloned().unwrap());
1224                    } else {
1225                        self.node_to_scope.remove(&node_id).unwrap();
1226                    }
1227                },
1228            );
1229        }
1230
1231        let mut scope_removal_queue = VecDeque::new();
1232
1233        while let Some(scope_rc) = scope_removal_buffer.pop() {
1234            // Push the scopes to a queue that will remove
1235            // them starting from the deepest to the highest ones
1236            scope_removal_queue.push_front(scope_rc.clone());
1237
1238            let scope = scope_rc.borrow_mut();
1239
1240            let mut scope_root_node_id = None;
1241
1242            // Queue nested scopes to be removed
1243            scope
1244                .nodes
1245                .traverse(&[], |path, &PathNode { scope_id, node_id }| {
1246                    if let Some(scope_id) = scope_id {
1247                        scope_removal_buffer.push(self.scopes.get(&scope_id).cloned().unwrap());
1248                    } else {
1249                        self.node_to_scope.remove(&node_id).unwrap();
1250                    }
1251                    if path == [0] {
1252                        scope_root_node_id = Some(node_id);
1253                    }
1254                });
1255
1256            // Nodes that have a scope id are components, so no need to mark those as removed in the tree
1257            // Instead we get their root node id and remove it
1258            mutations.removed.push(MutationRemove::Scope {
1259                id: scope_root_node_id.unwrap(),
1260            });
1261        }
1262
1263        // Finally drops the scopes and their storage
1264        for scope_rc in scope_removal_queue {
1265            let scope = scope_rc.borrow_mut();
1266
1267            self.scopes.remove(&scope.id);
1268
1269            // Dropped hooks might e.g spawn forever tasks, so they need access to the context
1270            CurrentContext::run_with_reactive(self.create_context(scope.id), || {
1271                // TODO: Scopes could also maintain its own registry of assigned tasks
1272                let mut _removed_tasks = Vec::new();
1273
1274                self.tasks.borrow_mut().retain(|task_id, task| {
1275                    if task.borrow().scope_id == scope.id {
1276                        _removed_tasks.push((*task_id, task.clone()));
1277                        false
1278                    } else {
1279                        true
1280                    }
1281                });
1282                drop(_removed_tasks);
1283                // This is very important, the scope storage must be dropped after the borrow in `scopes_storages` has been released
1284                let _scope = self.scopes_storages.borrow_mut().remove(&scope.id);
1285            });
1286        }
1287
1288        // Given some additions like:
1289        // [
1290        //     [0,2],
1291        //     [0,1,0,1],
1292        //     [0,1,0,2],
1293        //     [0,3],
1294        //     [0,1,5,8],
1295        // ]
1296        //
1297        // We want them ordered like:
1298        // [
1299        //     [0,1,0,1],
1300        //     [0,1,0,2],
1301        //     [0,1,5,8],
1302        //     [0,2],
1303        //     [0,3],
1304        // ]
1305        //
1306        // This way, no addition offsets the next additions in line.
1307        for added in diff
1308            .added
1309            .iter()
1310            .sorted_by(|a, b| {
1311                for (x, y) in a.iter().zip(b.iter()) {
1312                    match x.cmp(y) {
1313                        Ordering::Equal => continue,
1314                        non_eq => return non_eq.reverse(),
1315                    }
1316                }
1317                b.len().cmp(&a.len())
1318            })
1319            .rev()
1320        {
1321            self.process_addition(
1322                scope,
1323                added,
1324                path_element,
1325                mutations,
1326                &mut parents_to_resync_scopes,
1327                &mut addition_parents,
1328            );
1329        }
1330
1331        let added = if diff.moved.is_empty() {
1332            FxHashSet::default()
1333        } else {
1334            diff.added.iter().map(|path| path.as_ref()).collect()
1335        };
1336
1337        for (parent, movements) in diff.moved.into_iter().sorted_by(|(a, _), (b, _)| {
1338            for (x, y) in a.iter().zip(b.iter()) {
1339                match x.cmp(y) {
1340                    Ordering::Equal => continue,
1341                    non_eq => return non_eq.reverse(),
1342                }
1343            }
1344            a.len().cmp(&b.len())
1345        }) {
1346            let old_children = moved_nodes.remove(&parent).unwrap();
1347            // New index to old index
1348            let sources = movements
1349                .into_iter()
1350                .map(|(from, to)| (to, from))
1351                .collect::<FxHashMap<_, _>>();
1352
1353            // Child count of the new render
1354            let mut child_count = 0;
1355            path_element.with_element(&parent, |element| {
1356                if let PathElement::Element { elements, .. } = element {
1357                    child_count = elements.len() as u32;
1358                }
1359            });
1360
1361            let mut to_path = Vec::with_capacity(parent.len() + 1);
1362            to_path.extend_from_slice(&parent);
1363            to_path.push(0);
1364
1365            // Walk the final order, move only what is out of place
1366            for to in 0..child_count {
1367                to_path[parent.len()] = to;
1368
1369                let is_added = added.contains(to_path.as_slice());
1370                let node_id = if is_added {
1371                    addition_parents.get(to_path.as_slice()).unwrap().0
1372                } else {
1373                    // Unmoved children keep their index
1374                    let from = sources.get(&to).copied().unwrap_or(to);
1375                    old_children[from as usize].expect("surviving child must have an old node")
1376                };
1377
1378                if scope
1379                    .borrow()
1380                    .nodes
1381                    .get(&to_path)
1382                    .is_some_and(|node| node.node_id == node_id)
1383                {
1384                    continue;
1385                }
1386
1387                let current_path = scope
1388                    .borrow()
1389                    .nodes
1390                    .find_child_path(&parent, |v| v.is_some_and(|v| v.node_id == node_id))
1391                    .unwrap();
1392
1393                // Everything before `to` is already final
1394                let path_entry = scope.borrow_mut().nodes.remove(&current_path).unwrap();
1395                scope.borrow_mut().nodes.insert_entry(&to_path, path_entry);
1396            }
1397        }
1398
1399        for (modified, flags) in diff.modified {
1400            path_element.with_element(&modified, |element| match element {
1401                PathElement::Component { .. } => {
1402                    // Components never change when being diffed
1403                    unreachable!()
1404                }
1405                PathElement::Element { element, .. } => {
1406                    let node_id = scope
1407                        .borrow()
1408                        .nodes
1409                        .get(&modified)
1410                        .map(|path_node| path_node.node_id)
1411                        .unwrap();
1412                    mutations.modified.push(MutationModified {
1413                        node_id,
1414                        element: element.clone(),
1415                        flags,
1416                    });
1417                }
1418            });
1419        }
1420
1421        // When a parent gets a new child, or a child is removed or moved we
1422        // resync its 1 level children scopes with their new path
1423        for parent in parents_to_resync_scopes {
1424            // But only if the parent already existed before otherwise its pointless
1425            // as Scopes will be created with the latest path already
1426            if diff.added.contains(&parent) {
1427                // TODO: Maybe do this check before inserting
1428                continue;
1429            }
1430
1431            // Update all the nested scopes in this Scope with their up to date paths
1432            scope
1433                .borrow_mut()
1434                .nodes
1435                .traverse_1_level(&parent, |p, path_node| {
1436                    if let Some(scope_id) = path_node.scope_id
1437                        && let Some(scope_rc) = self.scopes.get(&scope_id)
1438                    {
1439                        let mut scope = scope_rc.borrow_mut();
1440                        scope.path_in_parent = Box::from(p);
1441                    }
1442                });
1443        }
1444    }
1445
1446    /// Reloads the runner for a hot-reload: cancels tasks, reloads every scope's hooks
1447    /// (contexts are preserved), and marks every scope dirty. Task cancellation must
1448    /// happen first so stale wakers can't fire [`Message::PollTask`] against
1449    /// freshly-reloaded scopes.
1450    pub fn reload(&mut self) {
1451        self.tasks.borrow_mut().clear();
1452        self.dirty_tasks.clear();
1453        while self.receiver.try_recv().is_ok() {}
1454
1455        let scopes = self
1456            .scopes
1457            .iter()
1458            .sorted_by_key(|(_, s)| s.borrow().height)
1459            .map(|(_, s)| s.borrow().id)
1460            .collect::<Vec<_>>();
1461
1462        for scope_id in scopes {
1463            CurrentContext::run(self.create_context(scope_id), || {
1464                let _hooks = self
1465                    .scopes_storages
1466                    .borrow_mut()
1467                    .get_mut(&scope_id)
1468                    .map(|storage| storage.reset_hooks());
1469            });
1470        }
1471
1472        self.dirty_scopes.extend(self.scopes.keys());
1473        let _ = self
1474            .sender
1475            .unbounded_send(Message::MarkScopeAsDirty(ScopeId::ROOT));
1476    }
1477}
1478
1479pub type Diff = tiny_diff::Diff<DiffModifies>;
1480
1481/// Converts a new-tree path to its corresponding old-tree path by checking, for each
1482/// segment, whether that position was the destination of a movement in `moved`. If so,
1483/// the original (`from`) index is substituted so the result can be used to look up nodes
1484/// in the pre-diff nodes tree.
1485fn resolve_old_path(new_path: &[u32], moved: &FxHashMap<Box<[u32]>, Vec<(u32, u32)>>) -> Vec<u32> {
1486    let mut old_path = Vec::with_capacity(new_path.len());
1487    for i in 0..new_path.len() {
1488        let new_parent = &new_path[..i];
1489        let new_index = new_path[i];
1490        if let Some(movements) = moved.get(new_parent)
1491            && let Some(&(from, _)) = movements.iter().find(|(_, to)| *to == new_index)
1492        {
1493            old_path.push(from);
1494            continue;
1495        }
1496        old_path.push(new_index);
1497    }
1498    old_path
1499}
1500
1501fn is_descendant(candidate: &[u32], ancestor: &[u32]) -> bool {
1502    if ancestor.len() > candidate.len() {
1503        return false;
1504    }
1505    candidate[..ancestor.len()] == *ancestor
1506}