freya_core/
path_element.rs

1use std::{
2    collections::VecDeque,
3    rc::Rc,
4};
5
6use rustc_hash::FxHashMap;
7
8use crate::{
9    diff_key::DiffKey,
10    element::{
11        ComponentProps,
12        Element,
13        ElementExt,
14    },
15    runner::Diff,
16};
17
18pub enum PathElement {
19    Component {
20        key: DiffKey,
21
22        comp: Rc<dyn Fn(Rc<dyn ComponentProps>) -> Element>,
23
24        props: Rc<dyn ComponentProps>,
25
26        path: Box<[u32]>,
27    },
28    Element {
29        key: DiffKey,
30
31        element: Rc<dyn ElementExt>,
32        elements: Box<[PathElement]>,
33        path: Box<[u32]>,
34    },
35}
36
37impl std::fmt::Debug for PathElement {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            PathElement::Component { key, path, .. } => f
41                .debug_struct("Component")
42                .field("key", key)
43                .field("path", path)
44                .field("comp", &"<fn>")
45                .field("props", &"<props>")
46                .finish(),
47            PathElement::Element { elements, path, .. } => f
48                .debug_struct("Element")
49                .field("path", path)
50                .field("elements", elements)
51                .field("element", &"<element>")
52                .finish(),
53        }
54    }
55}
56
57impl PathElement {
58    #[inline(always)]
59    pub fn with_element<D>(&self, target_path: &[u32], with: D)
60    where
61        D: FnOnce(&PathElement),
62    {
63        match self {
64            Self::Component { path, .. } | Self::Element { path, .. }
65                if path.as_ref() == target_path =>
66            {
67                with(self);
68            }
69            Self::Element { elements, path, .. } if target_path.starts_with(path) => {
70                let next_step = target_path[path.len()];
71                elements[next_step as usize].with_element(target_path, with);
72            }
73            _ => {}
74        }
75    }
76
77    #[cfg_attr(feature = "hotpath", hotpath::measure)]
78    pub fn from_element(path: Vec<u32>, element: Element) -> Self {
79        match element {
80            Element::Component { key, comp, props } => PathElement::Component {
81                key,
82                comp,
83                props,
84                path: path.into_boxed_slice(),
85            },
86            Element::Element {
87                elements,
88                element,
89                key,
90            } => PathElement::Element {
91                elements: elements
92                    .into_iter()
93                    .enumerate()
94                    .map(|(i, e)| {
95                        let mut path = path.clone();
96                        path.push(i as u32);
97                        PathElement::from_element(path, e)
98                    })
99                    .collect::<Box<[PathElement]>>(),
100                path: path.into_boxed_slice(),
101                element,
102                key,
103            },
104        }
105    }
106
107    #[cfg_attr(feature = "hotpath", hotpath::measure)]
108    pub fn diff(&self, previous: Option<&Self>, diff: &mut Diff) {
109        match previous {
110            None => {
111                match self {
112                    PathElement::Component { path, .. } => {
113                        diff.added.push(path.clone());
114                    }
115                    PathElement::Element { path, elements, .. } => {
116                        diff.added.push(path.clone());
117
118                        // For Elements, recurse into children to mark them as added if needed
119                        for element in elements {
120                            element.diff(None, diff);
121                        }
122                    }
123                }
124            }
125            Some(previous) => match (self, previous) {
126                (
127                    PathElement::Component { key: k1, path, .. },
128                    PathElement::Component {
129                        key: k2,
130                        path: path2,
131                        ..
132                    },
133                ) => {
134                    if k1 != k2 || diff.removed.iter().any(|p| **p == path2[..path2.len() - 1]) {
135                        diff.added.push(path.clone());
136                        diff.removed.push(path2.clone());
137                    } else if !path.is_empty() && path[path.len() - 1] != path2[path2.len() - 1] {
138                        diff.moved
139                            .entry(Box::from(path[..path.len() - 1].to_vec()))
140                            .or_default()
141                            .push((*path2.last().unwrap(), *path.last().unwrap()));
142                    }
143                }
144                (
145                    PathElement::Element {
146                        elements: e1,
147                        element: element1,
148                        path,
149                        key: k1,
150                        ..
151                    },
152                    PathElement::Element {
153                        elements: e2,
154                        element: element2,
155                        path: path2,
156                        key: k2,
157                        ..
158                    },
159                ) => {
160                    if k1 != k2 || diff.removed.iter().any(|p| **p == path2[..path2.len() - 1]) {
161                        diff.added.push(path.clone());
162                        diff.removed.push(path2.clone());
163                    } else {
164                        let diff_flags = element1.diff(element2);
165                        if !diff_flags.is_empty() {
166                            diff.modified.push((path.clone(), diff_flags));
167                        } else if !path.is_empty() && path[path.len() - 1] != path2[path2.len() - 1]
168                        {
169                            diff.moved
170                                .entry(Box::from(path[..path.len() - 1].to_vec()))
171                                .or_default()
172                                .push((*path2.last().unwrap(), *path.last().unwrap()));
173                        }
174                    }
175
176                    let mut previous_keys = FxHashMap::<&DiffKey, VecDeque<usize>>::default();
177
178                    for (i, e) in e2.iter().enumerate() {
179                        let (PathElement::Element { key, .. } | PathElement::Component { key, .. }) =
180                            e;
181                        previous_keys.entry(key).or_default().push_back(i)
182                    }
183
184                    for e in e1 {
185                        let (PathElement::Element { key, .. } | PathElement::Component { key, .. }) =
186                            e;
187                        if let Some(old_i) =
188                            previous_keys.get_mut(key).and_then(VecDeque::pop_front)
189                        {
190                            e.diff(Some(&e2[old_i]), diff);
191                        } else {
192                            e.diff(None, diff);
193                        }
194                    }
195
196                    for indexes in previous_keys.values() {
197                        for i in indexes {
198                            let (PathElement::Element { path, .. }
199                            | PathElement::Component { path, .. }) = &e2[*i];
200                            // The same element might have mistakenly gotten marked as moved in a previous call
201                            diff.moved.remove(path);
202                            diff.removed.push(path.clone());
203                            // No need to remove recursively here because scope and tree diff handling already runs recursively
204                        }
205                    }
206                }
207                (s, o) => {
208                    // Removed old
209                    let (PathElement::Element { path, .. } | PathElement::Component { path, .. }) =
210                        o;
211                    diff.removed.push(path.clone());
212
213                    // Add new recursively
214                    s.diff(None, diff);
215                }
216            },
217        }
218    }
219}