seed/virtual_dom/patch/
patch_gen.rs

1//!
2//! This module decides how to update nodes.
3//!
4//! ### Keyless mode
5//!
6//! The patch algorithm starts with keyless mode.
7//! In this mode, a pair of nodes is taken: old and new.
8//! - if the node type, tag, and namespace are equal, the old node is updated with the new one.
9//! - if the nodes are different, then the new node replaces the old one.
10//! - If the old node is empty, the new node is inserted before the next non-empty old node.
11//! - If the new node is empty, the old node is deleted.
12//! - All remaining new nodes are added to the end.
13//! - All remaining old nodes are deleted.
14//!
15//! As soon as the old or new node has a key, the algorithm switches to the key mode.
16//!
17//! ### Keyed mode
18//!
19//! Suppose we have old and new child nodes with `el_key`s:
20//! ```text
21//! old: [a] [b] [c] [d] [e] [f] [g] [h]
22//! new: [a] [d] [e] [b] [c] [x] [f] [y]
23//! ```
24//!
25//! The algorithm starts by taking nodes from the source iterators one by one in turn (new and old)
26//! and putting them in the corresponding queues:
27//! ```text
28//! old: [a]
29//! new: [a]
30//! ```
31//!
32//! Now we have the matching key in queues. The algorithm takes nodes from the queues and yields
33//! `patch [a] by [a]` command.
34//!
35//! But then nodes become diverse:
36//! ```text
37//! old: [b] [c]
38//! new: [d] [e] [b]
39//! ```
40//!
41//! As soon as the algorithm finds the matching key `[b]` it yields three commands:
42//! `insert [d] before old [b]`, `insert [e] before old [b]` and then `patch [b] by [b]`.
43//! Old node `[c]` remains in queue.
44//!
45//! The algorithm continues to fill queues and stops with matching nodes `[c]`. Then it issues `patch`
46//! command:
47//! ```text
48//! old: [c]
49//! new: [c]
50//! ```
51//!
52//! Then nodes again become diverse:
53//! ```text
54//! old: [d] [e] [f]
55//! new: [x] [f] [y]
56//! ```
57//!
58//! The algorithm stops when finds the matching key `[f]` and yields three commands:
59//! `replace [d] by [x]`, `remove [e]`, `patch [f] by [f]`.
60//!
61//! At this point the source iterator for the new nodes has been exhausted and the algorithm
62//! continues to take only old nodes.
63//! ```text
64//! old: [g] [h]
65//! new: [y]
66//! ```
67//!
68//! At this point both source iterators are exhausted and the algorithm yields:
69//! `replace [g] by [y]` and `remove [h]`.
70//! The append command means append as a last children.
71//!
72
73use crate::browser::dom::Namespace;
74use crate::virtual_dom::{El, ElKey, Node, Tag, Text};
75use std::borrow::Borrow;
76use std::collections::{BTreeSet, VecDeque};
77use std::iter::Peekable;
78
79#[allow(clippy::large_enum_variant)]
80pub enum PatchCommand<'a, Ms: 'static> {
81    AppendEl {
82        el_new: &'a mut El<Ms>,
83    },
84    AppendText {
85        text_new: &'a mut Text,
86    },
87    InsertEl {
88        el_new: &'a mut El<Ms>,
89        next_node: web_sys::Node,
90    },
91    InsertText {
92        text_new: &'a mut Text,
93        next_node: web_sys::Node,
94    },
95    PatchEl {
96        el_old: El<Ms>,
97        el_new: &'a mut El<Ms>,
98    },
99    PatchText {
100        text_old: Text,
101        text_new: &'a mut Text,
102    },
103    ReplaceElByEl {
104        el_old: El<Ms>,
105        el_new: &'a mut El<Ms>,
106    },
107    ReplaceElByText {
108        el_old: El<Ms>,
109        text_new: &'a mut Text,
110    },
111    ReplaceTextByEl {
112        text_old: Text,
113        el_new: &'a mut El<Ms>,
114    },
115    RemoveEl {
116        el_old: El<Ms>,
117    },
118    RemoveText {
119        text_old: Text,
120    },
121}
122
123/// `PatchKey` used to compare nodes during patching.
124///
125/// A function `find_matching` stores these keys to check if the key has already been seen.
126#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
127enum PatchKey {
128    Element {
129        namespace: Option<Namespace>,
130        tag: Tag,
131        el_key: Option<ElKey>,
132    },
133    Text,
134}
135
136impl PatchKey {
137    fn new<Ms: 'static>(node: &Node<Ms>) -> Option<Self> {
138        match node {
139            Node::Element(el) => Some(PatchKey::Element {
140                namespace: el.namespace.clone(),
141                tag: el.tag.clone(),
142                el_key: el.key.clone(),
143            }),
144            Node::Text(_) => Some(PatchKey::Text),
145            Node::Empty | Node::NoChange => None,
146        }
147    }
148}
149
150/// This is a command generator.
151/// See the module documenation for brief description of how this works.
152pub struct PatchGen<'a, Ms, OI, NI>
153where
154    Ms: 'static,
155    OI: Iterator<Item = Node<Ms>>,
156    NI: Iterator<Item = &'a mut Node<Ms>>,
157{
158    old_children_iter: Peekable<OI>,
159    new_children_iter: Peekable<NI>,
160    old_children: VecDeque<Node<Ms>>,
161    new_children: VecDeque<&'a mut Node<Ms>>,
162    matching_child_old: Option<OI::Item>,
163    matching_child_new: Option<NI::Item>,
164    matching_key: Option<PatchKey>,
165    keyed_mode: bool,
166}
167
168impl<'a, Ms, OI, NI> PatchGen<'a, Ms, OI, NI>
169where
170    Ms: 'static,
171    OI: Iterator<Item = Node<Ms>>,
172    NI: Iterator<Item = &'a mut Node<Ms>>,
173{
174    /// Creates the new `PatchGen` instance from the source iterators.
175    pub fn new(old_children_iter: OI, new_children_iter: NI) -> Self {
176        Self {
177            old_children_iter: old_children_iter.peekable(),
178            new_children_iter: new_children_iter.peekable(),
179            old_children: VecDeque::new(),
180            new_children: VecDeque::new(),
181            matching_child_old: None,
182            matching_child_new: None,
183            matching_key: None,
184            keyed_mode: false,
185        }
186    }
187
188    /// Decides what command to produce according to the internal state.
189    fn next_command(&mut self) -> Option<PatchCommand<'a, Ms>> {
190        if !self.keyed_mode {
191            return self.yield_keyless();
192        }
193        // Matching `PatchKey` has been already found.
194        if self.matching_key.is_some() {
195            return self.yield_keyed();
196        }
197        // Try to find matching key if at least one iterator has some nodes.
198        if self.old_children_iter.peek().is_some() || self.new_children_iter.peek().is_some() {
199            self.matching_key = find_matching(
200                &mut self.old_children_iter,
201                &mut self.old_children,
202                &mut self.new_children_iter,
203                &mut self.new_children,
204            );
205            // Matching key has been found.
206            if self.matching_key.is_some() {
207                return self.yield_keyed();
208            }
209        }
210        self.yield_remaining()
211    }
212
213    /// Takes a pair of old and new children from source iterators and decides how to update the
214    /// old child by the new one.
215    /// Sets `keyed_mode` to true and calls `yield_keyed` as soon as any child has an element key.
216    fn yield_keyless(&mut self) -> Option<PatchCommand<'a, Ms>> {
217        // Take a pair of old/new children but skip if both are `Some(Node::Empty)`.
218        let (child_old, child_new) = loop {
219            // First consume the children stored in the queue.
220            // When old child is `Empty` we call `find_next_node_ws` which
221            // moves some children from the source iterator to the queue.
222            let old = self
223                .old_children
224                .pop_back()
225                .or_else(|| self.old_children_iter.next());
226            let new = self.new_children_iter.next();
227            // We should not issue any command if both the old and the new nodes are `Empty`.
228            if matches!((&old, &new), (Some(Node::Empty), Some(Node::Empty))) {
229                continue;
230            }
231            break (old, new);
232        };
233
234        match (child_old, child_new) {
235            (Some(child_old), Some(child_new)) => {
236                if child_old.el_key().is_none() && child_new.el_key().is_none() {
237                    return self.patch_or_replace(child_old, child_new);
238                }
239
240                // Permanent switch to keyed mode.
241                self.keyed_mode = true;
242
243                let key_old = PatchKey::new(&child_old);
244                let key_new = PatchKey::new(child_new);
245                if key_old == key_new {
246                    self.matching_key = key_new;
247                }
248                if !child_old.is_empty() {
249                    self.old_children.push_back(child_old);
250                }
251                if !child_new.is_empty() {
252                    self.new_children.push_back(child_new);
253                }
254                self.next_command()
255            }
256            (None, Some(child_new)) => self.append(child_new),
257            (Some(child_old), None) => self.remove(child_old),
258            (None, None) => None,
259        }
260    }
261
262    /// Produces commands from children stored in the `old_children` and the `new_children` queues
263    /// until the child key is equal to `matching_key`, then returns the `PatchEl` or `ReplaceElByEl`
264    /// command.
265    ///
266    /// `self.matching_key` has to be set before calling this method.
267    fn yield_keyed(&mut self) -> Option<PatchCommand<'a, Ms>> {
268        // `self.matching_child_old` and `self.matching_child_new` are set only in this method.
269        // Therefore the first matching arm is always `(None, None)`.
270        match (
271            self.matching_child_old.as_ref(),
272            self.matching_child_new.as_ref(),
273        ) {
274            // No nodes with the matching key have been found.
275            (None, None) => {
276                // If the matching key is set then both the old and the new children queues
277                // have a node with this key.
278                let child_old = self
279                    .old_children
280                    .pop_back()
281                    .expect("old child from the queue");
282                let child_new = self
283                    .new_children
284                    .pop_back()
285                    .expect("new child from the queue");
286
287                let key_old = PatchKey::new(&child_old);
288                let key_new = PatchKey::new(child_new);
289
290                if key_old == self.matching_key && key_new == self.matching_key {
291                    self.matching_child_old = Some(child_old);
292                    self.matching_child_new = Some(child_new);
293                    return self.yield_keyed();
294                }
295                if key_old == self.matching_key {
296                    let next_node = child_old.node_ws().unwrap().clone();
297                    self.matching_child_old = Some(child_old);
298                    return self.insert(child_new, next_node);
299                }
300                if key_new == self.matching_key {
301                    self.matching_child_new = Some(child_new);
302                    return self.remove(child_old);
303                }
304                self.patch_or_replace(child_old, child_new)
305            }
306            // An old node with the matching key has been found in the queue.
307            (Some(child_old), None) => {
308                let child_new = self
309                    .new_children
310                    .pop_back()
311                    .expect("node with a matching key");
312
313                if PatchKey::new(child_new) == self.matching_key {
314                    self.matching_child_new = Some(child_new);
315                    return self.yield_keyed();
316                }
317                let next_node = child_old
318                    .node_ws()
319                    .expect("old node connected to web_sys node")
320                    .clone();
321                self.insert(child_new, next_node)
322            }
323            // A new node with the matching key has been found in the queue.
324            (None, Some(_)) => {
325                let child_old = self
326                    .old_children
327                    .pop_back()
328                    .expect("node with a matching key");
329
330                if PatchKey::new(&child_old) == self.matching_key {
331                    self.matching_child_old = Some(child_old);
332                    return self.yield_keyed();
333                }
334                self.remove(child_old)
335            }
336            // An old and a new node with the matching key have been found in queues.
337            (Some(_), Some(_)) => {
338                // We have found the matching node pair, we no longer need the key.
339                self.matching_key = None;
340                let child_old = self.matching_child_old.take().unwrap();
341                let child_new = self.matching_child_new.take().unwrap();
342                self.patch_or_replace(child_old, child_new)
343            }
344        }
345    }
346
347    /// Takes a pair of the remaining children stored in the queues and returns the command.
348    fn yield_remaining(&mut self) -> Option<PatchCommand<'a, Ms>> {
349        match (self.old_children.pop_back(), self.new_children.pop_back()) {
350            (Some(child_old), Some(child_new)) => self.patch_or_replace(child_old, child_new),
351            (Some(child_old), None) => self.remove(child_old),
352            (None, Some(child_new)) => self.append(child_new),
353            (None, None) => None,
354        }
355    }
356
357    fn append(&mut self, child_new: &'a mut Node<Ms>) -> Option<PatchCommand<'a, Ms>> {
358        Some(match child_new {
359            Node::Element(el_new) => PatchCommand::AppendEl { el_new },
360            Node::Text(text_new) => PatchCommand::AppendText { text_new },
361            Node::Empty | Node::NoChange => return self.next_command(),
362        })
363    }
364
365    fn insert(
366        &mut self,
367        child_new: &'a mut Node<Ms>,
368        next_node: web_sys::Node,
369    ) -> Option<PatchCommand<'a, Ms>> {
370        Some(match child_new {
371            Node::Element(el_new) => PatchCommand::InsertEl { el_new, next_node },
372            Node::Text(text_new) => PatchCommand::InsertText {
373                text_new,
374                next_node,
375            },
376            Node::Empty | Node::NoChange => return self.next_command(),
377        })
378    }
379
380    #[allow(clippy::option_if_let_else)]
381    fn patch_or_replace(
382        &mut self,
383        child_old: Node<Ms>,
384        child_new: &'a mut Node<Ms>,
385    ) -> Option<PatchCommand<'a, Ms>> {
386        Some(match child_old {
387            Node::Element(el_old) => match child_new {
388                Node::Element(el_new) => {
389                    if el_can_be_patched(&el_old, el_new) {
390                        PatchCommand::PatchEl { el_old, el_new }
391                    } else {
392                        PatchCommand::ReplaceElByEl { el_old, el_new }
393                    }
394                }
395                Node::Text(text_new) => PatchCommand::ReplaceElByText { el_old, text_new },
396                Node::Empty => PatchCommand::RemoveEl { el_old },
397                Node::NoChange => {
398                    *child_new = Node::Element(el_old);
399                    return self.next_command();
400                }
401            },
402            Node::Text(text_old) => match child_new {
403                Node::Element(el_new) => PatchCommand::ReplaceTextByEl { text_old, el_new },
404                Node::Text(text_new) => PatchCommand::PatchText { text_old, text_new },
405                Node::Empty => PatchCommand::RemoveText { text_old },
406                Node::NoChange => {
407                    *child_new = Node::Text(text_old);
408                    return self.next_command();
409                }
410            },
411            Node::Empty => match child_new {
412                Node::Element(el_new) => {
413                    if let Some(next_node) =
414                        find_next_node_ws(&mut self.old_children_iter, &mut self.old_children)
415                    {
416                        PatchCommand::InsertEl { el_new, next_node }
417                    } else {
418                        PatchCommand::AppendEl { el_new }
419                    }
420                }
421                Node::Text(text_new) => {
422                    if let Some(next_node) =
423                        find_next_node_ws(&mut self.old_children_iter, &mut self.old_children)
424                    {
425                        PatchCommand::InsertText {
426                            text_new,
427                            next_node,
428                        }
429                    } else {
430                        PatchCommand::AppendText { text_new }
431                    }
432                }
433                Node::Empty => return self.next_command(),
434                Node::NoChange => {
435                    *child_new = child_old;
436                    return self.next_command();
437                }
438            },
439            Node::NoChange => panic!("Node::NoChange cannot be an old VDOM node!"),
440        })
441    }
442
443    fn remove(&mut self, child_old: Node<Ms>) -> Option<PatchCommand<'a, Ms>> {
444        Some(match child_old {
445            Node::Element(el_old) => PatchCommand::RemoveEl { el_old },
446            Node::Text(text_old) => PatchCommand::RemoveText { text_old },
447            Node::Empty | Node::NoChange => return self.next_command(),
448        })
449    }
450}
451
452impl<'a, Ms, OI, NI> Iterator for PatchGen<'a, Ms, OI, NI>
453where
454    Ms: 'static,
455    OI: Iterator<Item = Node<Ms>>,
456    NI: Iterator<Item = &'a mut Node<Ms>>,
457{
458    type Item = PatchCommand<'a, Ms>;
459
460    fn next(&mut self) -> Option<Self::Item> {
461        self.next_command()
462    }
463}
464
465/// Checks whether the old element can be updated with a new one.
466pub fn el_can_be_patched<Ms>(el_old: &El<Ms>, el_new: &El<Ms>) -> bool {
467    el_old.namespace == el_new.namespace && el_old.tag == el_new.tag && el_old.key == el_new.key
468}
469
470/// Takes children from source iterators (new and old) and puts them in the
471/// corresponding queues.
472///
473/// Stops when:
474/// - The key of the new child matches to any key of the previously seen old children.
475/// - The key of the old child matches to any key of the previously seen new children.
476fn find_matching<OI, NI, ON, NN, Ms>(
477    old_children_iter: &mut Peekable<OI>,
478    old_children: &mut VecDeque<ON>,
479    new_children_iter: &mut Peekable<NI>,
480    new_children: &mut VecDeque<NN>,
481) -> Option<PatchKey>
482where
483    OI: Iterator<Item = ON>,
484    NI: Iterator<Item = NN>,
485    ON: Borrow<Node<Ms>>,
486    NN: Borrow<Node<Ms>>,
487    Ms: 'static,
488{
489    // First store all seen keys to the sets.
490    // One for the old children.
491    let mut seen_old_keys: BTreeSet<_> = old_children
492        .iter()
493        .filter_map(|node| PatchKey::new(node.borrow()))
494        .collect();
495    // And one for the new children.
496    let mut seen_new_keys: BTreeSet<_> = new_children
497        .iter()
498        .filter_map(|node| PatchKey::new(node.borrow()))
499        .collect();
500
501    while old_children_iter.peek().is_some() || new_children_iter.peek().is_some() {
502        // Fill the old/new children queues and keep the same queue lengths.
503        let should_pick_old_child = old_children_iter.peek().is_some()
504            && (new_children_iter.peek().is_none() || new_children.len() > old_children.len());
505
506        if should_pick_old_child {
507            if let Some(key) = fetch_next_item(old_children_iter, old_children)
508                .and_then(|child| PatchKey::new(child.borrow()))
509            {
510                if seen_new_keys.contains(&key) {
511                    return Some(key);
512                }
513                seen_old_keys.insert(key);
514            }
515        } else if new_children_iter.peek().is_some() {
516            if let Some(key) = fetch_next_item(new_children_iter, new_children)
517                .and_then(|child| PatchKey::new(child.borrow()))
518            {
519                if seen_old_keys.contains(&key) {
520                    return Some(key);
521                }
522                seen_new_keys.insert(key);
523            }
524        }
525    }
526    None
527}
528
529/// Searches for the next node with set `web_sys::Node` and returns a clone of that
530/// `web_sys::Node` or `None` if there is no such node.
531fn find_next_node_ws<I, N, Ms>(
532    children_iter: &mut Peekable<I>,
533    children: &mut VecDeque<N>,
534) -> Option<web_sys::Node>
535where
536    I: Iterator<Item = N>,
537    N: Borrow<Node<Ms>>,
538    Ms: 'static,
539{
540    // Search in the stored children first.
541    if let node_ws @ Some(_) = children.iter().find_map(|child| child.borrow().node_ws()) {
542        return node_ws.cloned();
543    }
544    // Consume the source iterator if there is no stored child with the searched node.
545    while let Some(child) = fetch_next_item(children_iter, children) {
546        if let node_ws @ Some(_) = child.borrow().node_ws() {
547            return node_ws.cloned();
548        }
549    }
550    None
551}
552
553/// Fetches the next item from the `source_iter` iterator, pushes this item to the
554/// `queue` and returns a reference to this item.
555fn fetch_next_item<'a, I, T>(source_iter: &'a mut I, queue: &'a mut VecDeque<T>) -> Option<&'a T>
556where
557    I: Iterator<Item = T>,
558{
559    source_iter.next().and_then(move |item| {
560        queue.push_front(item);
561        queue.front()
562    })
563}