sauron_core/vdom/
diff.rs

1//! provides diffing algorithm which returns patches
2use super::{diff_lis, Attribute, Element, Node, Patch, TreePath};
3use super::{Tag, KEY, REPLACE, SKIP, SKIP_CRITERIA};
4use crate::dom::skip_diff::SkipAttrs;
5use crate::dom::SkipPath;
6use crate::vdom::AttributeValue;
7use crate::vdom::Leaf;
8use std::{cmp, mem};
9
10#[cfg(feature = "use-skipdiff")]
11static USE_SKIP_DIFF: bool = true;
12
13#[cfg(not(feature = "use-skipdiff"))]
14static USE_SKIP_DIFF: bool = false;
15
16/// Return the patches needed for `old_node` to have the same DOM as `new_node`
17///
18/// # Agruments
19/// * old_node - the old virtual dom node
20/// * new_node - the new virtual dom node
21/// * key - the literal name of key attribute, ie: "key"
22///
23/// # Example
24/// ```rust
25/// use sauron::{diff::*, vdom::element, *};
26///
27///
28/// let old: Node<()> = element(
29///     "main",
30///     vec![attr("class", "container")],
31///     vec![
32///         element("div", vec![attr("key", "1")], vec![]),
33///         element("div", vec![attr("key", "2")], vec![]),
34///     ],
35/// );
36///
37/// let new: Node<()> = element(
38///     "main",
39///     vec![attr("class", "container")],
40///     vec![element("div", vec![attr("key", "2")], vec![])],
41/// );
42///
43/// let diff = diff(&old, &new);
44/// assert_eq!(
45///     diff,
46///     vec![Patch::remove_node(
47///         Some(&"div"),
48///         TreePath::new(vec![ 0]),
49///     )
50///     ]
51/// );
52/// ```
53pub fn diff<'a, MSG>(old_node: &'a Node<MSG>, new_node: &'a Node<MSG>) -> Vec<Patch<'a, MSG>> {
54    diff_recursive(
55        old_node,
56        new_node,
57        &SkipPath {
58            path: TreePath::root(),
59            skip_diff: None,
60        },
61    )
62}
63
64fn is_any_keyed<MSG>(nodes: &[Node<MSG>]) -> bool {
65    nodes.iter().any(|child| is_keyed_node(child))
66}
67
68/// returns true any attributes of this node attribute has key in it
69fn is_keyed_node<MSG>(node: &Node<MSG>) -> bool {
70    if let Some(attributes) = node.attributes() {
71        attributes.iter().any(|att| att.name == *KEY)
72    } else {
73        false
74    }
75}
76
77fn should_replace<'a, MSG>(old_node: &'a Node<MSG>, new_node: &'a Node<MSG>) -> bool {
78    // replace if they have different enum variants
79    if mem::discriminant(old_node) != mem::discriminant(new_node) {
80        return true;
81    }
82    let replace = |_old_node: &'a Node<MSG>, new_node: &'a Node<MSG>| {
83        let explicit_replace_attr = new_node
84            .first_value(REPLACE)
85            .and_then(|v| v.as_bool())
86            .unwrap_or(false);
87
88        explicit_replace_attr
89    };
90    // handle explicit replace if the Rep fn evaluates to true
91    if replace(old_node, new_node) {
92        return true;
93    }
94
95    // replace if the old key does not match the new key
96    if let (Some(old_key), Some(new_key)) =
97        (old_node.attribute_value(KEY), new_node.attribute_value(KEY))
98    {
99        if old_key != new_key {
100            return true;
101        }
102    }
103    // replace if they have different element tag
104    if let (Node::Element(old_element), Node::Element(new_element)) = (old_node, new_node) {
105        // Replace if there are different element tags
106        if old_element.tag != new_element.tag {
107            return true;
108        }
109    }
110    false
111}
112
113/// diff the nodes recursively
114pub fn diff_recursive<'a, MSG>(
115    old_node: &'a Node<MSG>,
116    new_node: &'a Node<MSG>,
117    path: &SkipPath,
118) -> Vec<Patch<'a, MSG>> {
119    if let Some(skip_diff) = path.skip_diff.as_ref() {
120        if USE_SKIP_DIFF && skip_diff.shall_skip_node() {
121            return vec![];
122        }
123    }
124
125    let skip = |old_node: &'a Node<MSG>, new_node: &'a Node<MSG>| {
126        let new_skip_criteria = new_node.attribute_value(SKIP_CRITERIA);
127        let old_skip_criteria = old_node.attribute_value(SKIP_CRITERIA);
128        // if old and new skip_criteria didn't change skip diffing this nodes
129        match (new_skip_criteria, old_skip_criteria) {
130            (Some(new), Some(old)) => new == old,
131            _ => new_node
132                .first_value(SKIP)
133                .and_then(|v| v.as_bool())
134                .unwrap_or(false),
135        }
136    };
137    // skip diffing if the function evaluates to true
138    if skip(old_node, new_node) {
139        return vec![];
140    }
141
142    // replace node and return early
143    if should_replace(old_node, new_node) {
144        return vec![Patch::replace_node(
145            old_node.tag(),
146            path.path.clone(),
147            vec![new_node],
148        )];
149    }
150
151    let mut patches = vec![];
152
153    // The following comparison can only contain identical variants, other
154    // cases have already been handled above by comparing variant
155    // discriminants.
156    match (old_node, new_node) {
157        (Node::Leaf(old_leaf), Node::Leaf(new_leaf)) => {
158            match (old_leaf, new_leaf) {
159                (Leaf::Text(_), Leaf::Text(_))
160                | (Leaf::Symbol(_), Leaf::Symbol(_))
161                | (Leaf::Comment(_), Leaf::Comment(_))
162                | (Leaf::DocType(_), Leaf::DocType(_)) => {
163                    if old_leaf != new_leaf {
164                        let patch = Patch::replace_node(None, path.path.clone(), vec![new_node]);
165                        patches.push(patch);
166                    }
167                }
168                (Leaf::Fragment(old_nodes), Leaf::Fragment(new_nodes)) => {
169                    // we back track since Fragment is not a real node, but it would still
170                    // be traversed from the prior call
171                    let patch = diff_nodes(None, old_nodes, new_nodes, &path.backtrack());
172                    patches.extend(patch);
173                }
174                (Leaf::NodeList(_old_elements), Leaf::NodeList(_new_elements)) => {
175                    panic!("Node list must have already unrolled when creating an element");
176                }
177                (Leaf::StatelessComponent(old_comp), Leaf::StatelessComponent(new_comp)) => {
178                    let new_path = SkipPath {
179                        path: path.path.clone(),
180                        skip_diff: old_comp.view.skip_diff(),
181                    };
182
183                    let old_real_view = old_comp.view.unwrap_template_ref();
184                    let new_real_view = new_comp.view.unwrap_template_ref();
185
186                    assert!(
187                        !old_real_view.is_template(),
188                        "old comp view should not be a template"
189                    );
190                    assert!(
191                        !new_real_view.is_template(),
192                        "new comp view should not be a template"
193                    );
194                    let patch = diff_recursive(old_real_view, new_real_view, &new_path);
195                    patches.extend(patch);
196                }
197                (Leaf::StatefulComponent(old_comp), Leaf::StatefulComponent(new_comp)) => {
198                    let attr_patches = create_attribute_patches(
199                        &"component",
200                        &old_comp.attrs,
201                        &new_comp.attrs,
202                        path,
203                    );
204                    if !attr_patches.is_empty() {
205                        log::info!("stateful component attr_patches: {attr_patches:#?}");
206                    }
207                    patches.extend(attr_patches);
208                    let patch = diff_nodes(None, &old_comp.children, &new_comp.children, path);
209                    if !patch.is_empty() {
210                        log::info!("stateful component patch: {patch:#?}");
211                    }
212                    patches.extend(patch);
213                }
214                (Leaf::TemplatedView(_old_view), _) => {
215                    unreachable!("templated view should not be diffed..")
216                }
217                (_, Leaf::TemplatedView(_new_view)) => {
218                    unreachable!("templated view should not be diffed..")
219                }
220                _ => {
221                    let patch = Patch::replace_node(None, path.path.clone(), vec![new_node]);
222                    patches.push(patch);
223                }
224            }
225        }
226        // We're comparing two element nodes
227        (Node::Element(old_element), Node::Element(new_element)) => {
228            let skip_attributes = if let Some(skip_diff) = path.skip_diff.as_ref() {
229                USE_SKIP_DIFF && skip_diff.shall_skip_attributes()
230            } else {
231                false
232            };
233
234            if !skip_attributes {
235                let attr_patches = create_attribute_patches(
236                    old_element.tag(),
237                    old_element.attributes(),
238                    new_element.attributes(),
239                    path,
240                );
241                patches.extend(attr_patches);
242            }
243
244            let more_patches = diff_nodes(
245                Some(old_element.tag()),
246                old_element.children(),
247                new_element.children(),
248                path,
249            );
250
251            patches.extend(more_patches);
252        }
253        _ => {
254            unreachable!("Unequal variant discriminants should already have been handled");
255        }
256    };
257
258    patches
259}
260
261fn diff_nodes<'a, MSG>(
262    old_tag: Option<&'a Tag>,
263    old_children: &'a [Node<MSG>],
264    new_children: &'a [Node<MSG>],
265    path: &SkipPath,
266) -> Vec<Patch<'a, MSG>> {
267    let diff_as_keyed = is_any_keyed(old_children) || is_any_keyed(new_children);
268
269    if diff_as_keyed {
270        let keyed_patches = diff_lis::diff_keyed_nodes(old_tag, old_children, new_children, path);
271        keyed_patches
272    } else {
273        let non_keyed_patches = diff_non_keyed_nodes(old_tag, old_children, new_children, path);
274        non_keyed_patches
275    }
276}
277
278/// In diffing non_keyed nodes,
279///  we reuse existing DOM elements as much as possible
280///
281///  The algorithm used here is very simple.
282///
283///  If there are more children in the old_element than the new_element
284///  the excess children is all removed.
285///
286///  If there are more children in the new_element than the old_element
287///  it will be all appended in the old_element.
288fn diff_non_keyed_nodes<'a, MSG>(
289    old_element_tag: Option<&'a Tag>,
290    old_children: &'a [Node<MSG>],
291    new_children: &'a [Node<MSG>],
292    path: &SkipPath,
293) -> Vec<Patch<'a, MSG>> {
294    let mut patches = vec![];
295    let old_child_count = old_children.len();
296    let new_child_count = new_children.len();
297
298    // if there is no new children, then clear the children of this element
299    if old_child_count > 0 && new_child_count == 0 {
300        return vec![Patch::clear_children(old_element_tag, path.path.clone())];
301    }
302
303    let min_count = cmp::min(old_child_count, new_child_count);
304    for index in 0..min_count {
305        // if we iterate trough the old elements, a new child_path is created for that iteration
306        let child_path = path.traverse(index);
307
308        let old_child = &old_children.get(index).expect("No old_node child node");
309        let new_child = &new_children.get(index).expect("No new child node");
310
311        let more_patches = diff_recursive(old_child, new_child, &child_path);
312        patches.extend(more_patches);
313    }
314
315    // If there are more new child than old_node child, we make a patch to append the excess element
316    // starting from old_child_count to the last item of the new_elements
317    if new_child_count > old_child_count {
318        patches.push(Patch::append_children(
319            old_element_tag,
320            path.path.clone(),
321            new_children.iter().skip(old_child_count).collect(),
322        ));
323    }
324
325    if new_child_count < old_child_count {
326        let remove_node_patches = old_children
327            .iter()
328            .skip(new_child_count)
329            .enumerate()
330            .map(|(i, old_child)| {
331                Patch::remove_node(old_child.tag(), path.traverse(new_child_count + i).path)
332            })
333            .collect::<Vec<_>>();
334
335        patches.extend(remove_node_patches);
336    }
337
338    patches
339}
340
341///
342/// Note: The performance bottlenecks
343///     - allocating new vec
344///     - merging attributes of the same name
345#[allow(clippy::type_complexity)]
346fn create_attribute_patches<'a, MSG>(
347    old_tag: &'a Tag,
348    old_attributes: &'a [Attribute<MSG>],
349    new_attributes: &'a [Attribute<MSG>],
350    path: &SkipPath,
351) -> Vec<Patch<'a, MSG>> {
352    let skip_indices = if let Some(skip_diff) = &path.skip_diff {
353        if let SkipAttrs::Indices(skip_indices) = &skip_diff.skip_attrs {
354            skip_indices.clone()
355        } else {
356            vec![]
357        }
358    } else {
359        vec![]
360    };
361
362    let has_skip_indices = !skip_indices.is_empty();
363
364    let mut patches = vec![];
365
366    // return early if both attributes are empty
367    if old_attributes.is_empty() && new_attributes.is_empty() {
368        return vec![];
369    }
370
371    let mut add_attributes: Vec<&Attribute<MSG>> = vec![];
372    let mut remove_attributes: Vec<&Attribute<MSG>> = vec![];
373
374    let new_attributes_grouped = Element::group_indexed_attributes_per_name(new_attributes);
375    let old_attributes_grouped = Element::group_indexed_attributes_per_name(old_attributes);
376
377    // for all new elements that doesn't exist in the old elements
378    // or the values differ
379    // add it to the AddAttribute patches
380    for (new_attr_name, new_attrs) in new_attributes_grouped.iter() {
381        let old_indexed_attr_values = old_attributes_grouped.get(new_attr_name).map(|attrs| {
382            attrs
383                .iter()
384                .map(|(i, attr)| (*i, &attr.value))
385                .collect::<Vec<_>>()
386        });
387
388        let new_indexed_attr_values = new_attributes_grouped.get(new_attr_name).map(|attrs| {
389            attrs
390                .iter()
391                .map(|(i, attr)| (*i, &attr.value))
392                .collect::<Vec<_>>()
393        });
394
395        if let Some(old_indexed_attr_values) = old_indexed_attr_values {
396            let new_indexed_attr_values =
397                new_indexed_attr_values.expect("must have new attr values");
398            let (_new_indices, new_attr_values): (Vec<usize>, Vec<&Vec<AttributeValue<MSG>>>) =
399                new_indexed_attr_values.into_iter().unzip();
400            let (old_indices, old_attr_values): (Vec<usize>, Vec<&Vec<AttributeValue<MSG>>>) =
401                old_indexed_attr_values.into_iter().unzip();
402            if USE_SKIP_DIFF && has_skip_indices && is_subset_of(&old_indices, &skip_indices) {
403                //
404            } else if old_attr_values != new_attr_values {
405                for (_i, new_att) in new_attrs {
406                    add_attributes.push(new_att);
407                }
408            }
409        } else {
410            // these are new attributes
411            for (_i, new_att) in new_attrs {
412                add_attributes.push(new_att);
413            }
414        }
415    }
416
417    // if this attribute name does not exist anymore
418    // to the new element, remove it
419    for (old_attr_name, old_indexed_attrs) in old_attributes_grouped.into_iter() {
420        let (old_indices, old_attrs): (Vec<usize>, Vec<&Attribute<MSG>>) =
421            old_indexed_attrs.into_iter().unzip();
422        if USE_SKIP_DIFF && has_skip_indices && is_subset_of(&old_indices, &skip_indices) {
423            //
424        } else if !new_attributes_grouped.contains_key(old_attr_name) {
425            remove_attributes.extend(old_attrs.clone());
426        }
427    }
428
429    if !add_attributes.is_empty() {
430        patches.push(Patch::add_attributes(
431            old_tag,
432            path.path.clone(),
433            add_attributes,
434        ));
435    }
436    if !remove_attributes.is_empty() {
437        patches.push(Patch::remove_attributes(
438            old_tag,
439            path.path.clone(),
440            remove_attributes,
441        ));
442    }
443    patches
444}
445
446/// returns true if all the elements in subset is in big_set
447/// This also returns the indices of big_set that are not found in the subset
448fn is_subset_of<T: PartialEq>(subset: &[T], big_set: &[T]) -> bool {
449    subset.iter().all(|set| big_set.contains(set))
450}