Skip to main content

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                        }
168                        if !path.is_empty() && path[path.len() - 1] != path2[path2.len() - 1] {
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                    #[cfg(debug_assertions)]
177                    {
178                        let mut seen = FxHashMap::<&DiffKey, &[u32]>::default();
179                        for child in e1 {
180                            let (PathElement::Element {
181                                key,
182                                path: child_path,
183                                ..
184                            }
185                            | PathElement::Component {
186                                key,
187                                path: child_path,
188                                ..
189                            }) = child;
190                            if matches!(key, DiffKey::None | DiffKey::DefaultU64(_)) {
191                                continue;
192                            }
193                            if let Some(prev) = seen.insert(key, child_path) {
194                                panic!(
195                                    "freya diff: duplicate sibling key {:?} under parent {:?} \
196                                     (paths {:?} and {:?})",
197                                    key, path, prev, child_path,
198                                );
199                            }
200                        }
201                    }
202
203                    let mut previous_keys = FxHashMap::<&DiffKey, VecDeque<usize>>::default();
204
205                    for (i, e) in e2.iter().enumerate() {
206                        let (PathElement::Element { key, .. } | PathElement::Component { key, .. }) =
207                            e;
208                        previous_keys.entry(key).or_default().push_back(i)
209                    }
210
211                    for e in e1 {
212                        let (PathElement::Element { key, .. } | PathElement::Component { key, .. }) =
213                            e;
214                        if let Some(old_i) =
215                            previous_keys.get_mut(key).and_then(VecDeque::pop_front)
216                        {
217                            e.diff(Some(&e2[old_i]), diff);
218                        } else {
219                            e.diff(None, diff);
220                        }
221                    }
222
223                    for indexes in previous_keys.values() {
224                        for i in indexes {
225                            let (PathElement::Element { path, .. }
226                            | PathElement::Component { path, .. }) = &e2[*i];
227                            // The same element might have mistakenly gotten marked as moved in a previous call
228                            diff.moved.remove(path);
229                            diff.removed.push(path.clone());
230                            // No need to remove recursively here because scope and tree diff handling already runs recursively
231                        }
232                    }
233                }
234                (s, o) => {
235                    // Removed old
236                    let (PathElement::Element { path, .. } | PathElement::Component { path, .. }) =
237                        o;
238                    diff.removed.push(path.clone());
239
240                    // Add new recursively
241                    s.diff(None, diff);
242                }
243            },
244        }
245    }
246}