Skip to main content

devpage_design/ids/
mod.rs

1//! Element ids kept across edits: a file's new parse matched against its
2//! last, sibling list by sibling list, so an element keeps its id however
3//! the lines around it change. Siblings with a `key` are matched by it; the
4//! others by a shortest edit script over the list (Myers' diff), comparing
5//! each element whole — its name, its own attributes and everything in it —
6//! and then, among what is left between two matches, an element with one of
7//! the same name, in order, as an edit of it. A matched element's children
8//! are matched the same way; an element left over is new and takes the
9//! file's next id.
10//!
11//! The cost is a pass over the file: lists are trimmed of what they share at
12//! either end, and Myers' diff, which costs a list's length times its edits,
13//! is searched to [`MAX_EDITS`] edits. Past that — a save that rewrote the
14//! list — the list is matched by a histogram diff: every element that occurs
15//! once on each side is matched wherever it now is, the longest run of them
16//! in order splits the list into gaps, and each gap is matched again the same
17//! way. That is near-linear however many edits there are, and never pairs
18//! the list by position.
19//!
20//! A store that keeps designs between saves reads its files through
21//! [`Remembering`], which keeps each design file's last parse and matches
22//! the next against it.
23
24use std::collections::HashMap;
25
26use crate::parse::{Attr, Child, Element, File, Value};
27
28mod remembering;
29
30pub use remembering::{Parses, Remembering};
31
32/// The longest edit script searched for between two lists of siblings.
33pub const MAX_EDITS: usize = 256;
34
35/// How deep the histogram diff recurses into the gaps between its matches.
36const MAX_DEPTH: usize = 64;
37
38/// `new`, its elements given the ids of the elements of `last` they match,
39/// and fresh ones from `last`'s next id where they match none.
40pub fn matched(mut new: File, last: &File) -> File {
41    let mut next = last.next_id;
42    if new.root.name == last.root.name {
43        pair(&mut new.root, &last.root, &mut next);
44    } else {
45        fresh(&mut new.root, &mut next);
46    }
47    new.next_id = next;
48    new
49}
50
51/// The key an element names itself by among its siblings, if it has one.
52pub fn key(el: &Element) -> Option<&str> {
53    el.attrs.iter().find_map(|a| match (&*a.name, &a.value) {
54        ("key", Value::Str(s, _)) => Some(s.as_str()),
55        _ => None,
56    })
57}
58
59fn fresh(el: &mut Element, next: &mut u32) {
60    el.id = *next;
61    *next += 1;
62    for child in &mut el.children {
63        if let Child::Element(e) = child {
64            fresh(e, next);
65        }
66    }
67}
68
69fn pair(new: &mut Element, old: &Element, next: &mut u32) {
70    new.id = old.id;
71    let olds: Vec<&Element> = elements(&old.children).collect();
72    let mut news: Vec<&mut Element> = new
73        .children
74        .iter_mut()
75        .filter_map(|c| match c {
76            Child::Element(e) => Some(e),
77            Child::Text(..) => None,
78        })
79        .collect();
80    let matches = siblings(&news.iter().map(|e| &**e).collect::<Vec<_>>(), &olds);
81    for (n, el) in news.iter_mut().enumerate() {
82        match matches[n] {
83            Some(o) => pair(el, olds[o], next),
84            None => fresh(el, next),
85        }
86    }
87}
88
89fn elements(children: &[Child]) -> impl Iterator<Item = &Element> {
90    children.iter().filter_map(|c| match c {
91        Child::Element(e) => Some(e),
92        Child::Text(..) => None,
93    })
94}
95
96/// For each of `news`, the one of `olds` it is, if any.
97fn siblings(news: &[&Element], olds: &[&Element]) -> Vec<Option<usize>> {
98    let mut out = vec![None; news.len()];
99    let mut taken = vec![false; olds.len()];
100    // By key, wherever each now is.
101    let mut keyed: HashMap<(&str, &str), usize> = HashMap::new();
102    for (o, el) in olds.iter().enumerate() {
103        if let Some(k) = key(el) {
104            keyed.entry((&el.name, k)).or_insert(o);
105        }
106    }
107    for (n, el) in news.iter().enumerate() {
108        if let Some(k) = key(el)
109            && let Some(&o) = keyed.get(&(el.name.as_str(), k))
110            && !taken[o]
111        {
112            out[n] = Some(o);
113            taken[o] = true;
114        }
115    }
116    // The rest, by an edit script over the elements without a key.
117    let free_new: Vec<usize> = (0..news.len())
118        .filter(|&n| out[n].is_none() && key(news[n]).is_none())
119        .collect();
120    let free_old: Vec<usize> = (0..olds.len())
121        .filter(|&o| !taken[o] && key(olds[o]).is_none())
122        .collect();
123    // The edit script keeps what is the same throughout — its own
124    // attributes and everything in it — so a row inserted before a run of
125    // rows alike in their own attributes is that row, not a shift of them all.
126    let shapes_new: Vec<u64> = free_new.iter().map(|&n| shape(news[n])).collect();
127    let shapes_old: Vec<u64> = free_old.iter().map(|&o| shape(olds[o])).collect();
128    let same = |n: usize, o: usize| {
129        shapes_new[n] == shapes_old[o] && same_own(news[free_new[n]], olds[free_old[o]])
130    };
131    let pairs = common(
132        free_new.len(),
133        free_old.len(),
134        &same,
135        &shapes_new,
136        &shapes_old,
137    );
138    let mut new_taken = vec![false; free_new.len()];
139    let mut old_taken = vec![false; free_old.len()];
140    for &(n, o) in &pairs {
141        out[free_new[n]] = Some(free_old[o]);
142        new_taken[n] = true;
143        old_taken[o] = true;
144    }
145    // Between two matches in order, what is left pairs up by name, in order;
146    // a match out of order — an element moved — is taken, not a bound.
147    let anchors = increasing(&pairs);
148    let mut last = (0, 0);
149    let ends = anchors
150        .iter()
151        .copied()
152        .chain([(free_new.len(), free_old.len())]);
153    for (an, ao) in ends {
154        let mut olds_left: Vec<usize> = (last.1..ao).filter(|&o| !old_taken[o]).collect();
155        for n in (last.0..an).filter(|&n| !new_taken[n]) {
156            let name = &news[free_new[n]].name;
157            if let Some(at) = olds_left
158                .iter()
159                .position(|&o| olds[free_old[o]].name == *name)
160            {
161                let o = olds_left.remove(at);
162                out[free_new[n]] = Some(free_old[o]);
163                // What was before it can no longer pair with what is after.
164                olds_left.drain(..at);
165            }
166        }
167        last = (an + 1, ao + 1);
168    }
169    out
170}
171
172/// The pairs `(n, o)` of elements of two lists `same` compares that are the
173/// same element, sorted by `n`: a longest common subsequence where Myers'
174/// diff finds one within [`MAX_EDITS`] edits, and otherwise a histogram
175/// diff's matches, which may be out of order — elements moved. `keys` are a
176/// hash of each element, which `same` agrees with.
177fn common(
178    n: usize,
179    m: usize,
180    same: &dyn Fn(usize, usize) -> bool,
181    keys_new: &[u64],
182    keys_old: &[u64],
183) -> Vec<(usize, usize)> {
184    let mut out = Vec::new();
185    let news: Vec<usize> = (0..n).collect();
186    let olds: Vec<usize> = (0..m).collect();
187    diff(&news, &olds, same, keys_new, keys_old, 0, &mut out);
188    out.sort_unstable();
189    out
190}
191
192/// Adds to `out` the matches between the elements `news` and `olds` — each
193/// list of indices in order: shared ends trimmed, then Myers' diff, or past
194/// its cap the histogram diff, recursing into the gaps between the matches it
195/// keeps in order.
196fn diff(
197    news: &[usize],
198    olds: &[usize],
199    same: &dyn Fn(usize, usize) -> bool,
200    keys_new: &[u64],
201    keys_old: &[u64],
202    depth: usize,
203    out: &mut Vec<(usize, usize)>,
204) {
205    let (n, m) = (news.len(), olds.len());
206    let mut pre = 0;
207    while pre < n && pre < m && same(news[pre], olds[pre]) {
208        out.push((news[pre], olds[pre]));
209        pre += 1;
210    }
211    let mut suf = 0;
212    while suf < n - pre && suf < m - pre && same(news[n - 1 - suf], olds[m - 1 - suf]) {
213        out.push((news[n - 1 - suf], olds[m - 1 - suf]));
214        suf += 1;
215    }
216    let (news, olds) = (&news[pre..n - suf], &olds[pre..m - suf]);
217    if news.is_empty() || olds.is_empty() {
218        return;
219    }
220    if let Some(pairs) = myers(news.len(), olds.len(), &|i, j| same(news[i], olds[j])) {
221        out.extend(pairs.into_iter().map(|(i, j)| (news[i], olds[j])));
222        return;
223    }
224    // Past the cap: every element that occurs once on each side is matched,
225    // wherever it now is.
226    let mut count: HashMap<u64, (u32, u32, usize, usize)> = HashMap::new();
227    for (i, &x) in news.iter().enumerate() {
228        let c = count.entry(keys_new[x]).or_insert((0, 0, 0, 0));
229        c.0 += 1;
230        c.2 = i;
231    }
232    for (j, &y) in olds.iter().enumerate() {
233        if let Some(c) = count.get_mut(&keys_old[y]) {
234            c.1 += 1;
235            c.3 = j;
236        }
237    }
238    let mut unique: Vec<(usize, usize)> = count
239        .values()
240        .filter(|&&(a, b, i, j)| a == 1 && b == 1 && same(news[i], olds[j]))
241        .map(|&(_, _, i, j)| (i, j))
242        .collect();
243    // Gaps nest only as deep as uniqueness keeps appearing inside them; past
244    // [`MAX_DEPTH`] what is left pairs by name in place, so no list can
245    // recurse without bound.
246    if unique.is_empty() || depth == MAX_DEPTH {
247        // Nothing tells these apart: what is left pairs by name in place.
248        return;
249    }
250    unique.sort_unstable();
251    out.extend(unique.iter().map(|&(i, j)| (news[i], olds[j])));
252    // The longest run of them in order splits the lists into gaps, each
253    // matched again — without the elements matched out of order.
254    let mut used_new = vec![false; news.len()];
255    let mut used_old = vec![false; olds.len()];
256    for &(i, j) in &unique {
257        used_new[i] = true;
258        used_old[j] = true;
259    }
260    let mut last = (0, 0);
261    for (i, j) in increasing(&unique)
262        .into_iter()
263        .chain([(news.len(), olds.len())])
264    {
265        let gap_new: Vec<usize> = (last.0..i)
266            .filter(|&k| !used_new[k])
267            .map(|k| news[k])
268            .collect();
269        let gap_old: Vec<usize> = (last.1..j)
270            .filter(|&k| !used_old[k])
271            .map(|k| olds[k])
272            .collect();
273        diff(&gap_new, &gap_old, same, keys_new, keys_old, depth + 1, out);
274        last = (i + 1, j + 1);
275    }
276}
277
278/// A longest run of `pairs` — sorted by their first — increasing in their
279/// second too: the matches that are in order. `n log n`.
280fn increasing(pairs: &[(usize, usize)]) -> Vec<(usize, usize)> {
281    // `tails[k]`: the index in `pairs` of the smallest second ending a run of
282    // length `k + 1`; `prev`: each pair's predecessor in its run.
283    let mut tails: Vec<usize> = Vec::new();
284    let mut prev = vec![usize::MAX; pairs.len()];
285    for (at, &(_, o)) in pairs.iter().enumerate() {
286        let k = tails.partition_point(|&t| pairs[t].1 < o);
287        if k > 0 {
288            prev[at] = tails[k - 1];
289        }
290        if k == tails.len() {
291            tails.push(at);
292        } else {
293            tails[k] = at;
294        }
295    }
296    let mut run = Vec::with_capacity(tails.len());
297    let mut at = tails.last().copied().unwrap_or(usize::MAX);
298    while at != usize::MAX {
299        run.push(pairs[at]);
300        at = prev[at];
301    }
302    run.reverse();
303    run
304}
305
306/// Myers' shortest edit script between two lists of lengths `n` and `m`,
307/// as the pairs it keeps; `None` when it would take more than [`MAX_EDITS`].
308fn myers(n: usize, m: usize, same: &dyn Fn(usize, usize) -> bool) -> Option<Vec<(usize, usize)>> {
309    if n == 0 || m == 0 {
310        return Some(Vec::new());
311    }
312    let max = (n + m).min(MAX_EDITS);
313    let off = max as isize + 1;
314    let mut v = vec![0isize; 2 * max + 3];
315    let mut trace: Vec<Vec<isize>> = Vec::new();
316    for d in 0..=max as isize {
317        trace.push(v.clone());
318        let mut k = -d;
319        while k <= d {
320            let at = (k + off) as usize;
321            let mut x = if k == -d || (k != d && v[at - 1] < v[at + 1]) {
322                v[at + 1]
323            } else {
324                v[at - 1] + 1
325            };
326            let mut y = x - k;
327            while (x as usize) < n && y >= 0 && (y as usize) < m && same(x as usize, y as usize) {
328                x += 1;
329                y += 1;
330            }
331            v[at] = x;
332            if x >= n as isize && y >= m as isize {
333                return Some(backtrack(&trace, d, n, m, off));
334            }
335            k += 2;
336        }
337    }
338    None
339}
340
341/// The pairs the edit script found keeps, walked back from its end: each
342/// step `d` taken from the state `trace[d]` it was made from.
343fn backtrack(
344    trace: &[Vec<isize>],
345    d: isize,
346    n: usize,
347    m: usize,
348    off: isize,
349) -> Vec<(usize, usize)> {
350    let mut out = Vec::new();
351    let (mut x, mut y) = (n as isize, m as isize);
352    for d in (1..=d).rev() {
353        let v = &trace[d as usize];
354        let k = x - y;
355        let at = |k: isize| v[(k + off) as usize];
356        let prev_k = if k == -d || (k != d && at(k - 1) < at(k + 1)) {
357            k + 1
358        } else {
359            k - 1
360        };
361        let prev_x = at(prev_k);
362        let prev_y = prev_x - prev_k;
363        // Where the snake that ends at `(x, y)` starts: just past the move.
364        let (sx, sy) = if prev_k == k + 1 {
365            (prev_x, prev_y + 1)
366        } else {
367            (prev_x + 1, prev_y)
368        };
369        while x > sx && y > sy {
370            x -= 1;
371            y -= 1;
372            out.push((x as usize, y as usize));
373        }
374        x = prev_x;
375        y = prev_y;
376    }
377    while x > 0 && y > 0 {
378        x -= 1;
379        y -= 1;
380        out.push((x as usize, y as usize));
381    }
382    out.reverse();
383    out
384}
385
386/// A hash of an element and everything in it but for where it is written
387/// and its id: what two elements the same throughout share.
388fn shape(el: &Element) -> u64 {
389    use std::hash::{Hash, Hasher};
390    fn value(v: &Value, h: &mut std::collections::hash_map::DefaultHasher) {
391        match v {
392            Value::Str(s, _) => (0u8, s).hash(h),
393            Value::Theme(s, _) => (1u8, s).hash(h),
394            Value::Num(n) => (2u8, n.to_bits()).hash(h),
395            Value::Bool(b) => (3u8, b).hash(h),
396            Value::Prop(p) => (4u8, p).hash(h),
397            Value::Array(items) => {
398                (5u8, items.len()).hash(h);
399                for i in items {
400                    value(i, h);
401                }
402            }
403            Value::Object(fields) => {
404                (6u8, fields.len()).hash(h);
405                for (k, v) in fields {
406                    k.hash(h);
407                    value(v, h);
408                }
409            }
410            other => format!("{other:?}").hash(h),
411        }
412    }
413    fn walk(el: &Element, h: &mut std::collections::hash_map::DefaultHasher) {
414        el.name.hash(h);
415        el.attrs.len().hash(h);
416        for a in &el.attrs {
417            a.name.hash(h);
418            value(&a.value, h);
419        }
420        el.children.len().hash(h);
421        for c in &el.children {
422            match c {
423                Child::Element(e) => walk(e, h),
424                Child::Text(parts, _) => {
425                    for p in parts {
426                        match p {
427                            crate::parse::TextPart::Literal(s) => (0u8, s).hash(h),
428                            crate::parse::TextPart::Prop(s) => (1u8, s).hash(h),
429                            crate::parse::TextPart::Theme(s, _) => (2u8, s).hash(h),
430                        }
431                    }
432                }
433            }
434        }
435    }
436    let mut h = std::collections::hash_map::DefaultHasher::new();
437    walk(el, &mut h);
438    h.finish()
439}
440
441/// Whether two elements are the same but for where they are written: the
442/// same name and the same attributes, in the same order, with the same
443/// values.
444pub(crate) fn same_own(a: &Element, b: &Element) -> bool {
445    a.name == b.name
446        && a.attrs.len() == b.attrs.len()
447        && a.attrs.iter().zip(&b.attrs).all(|(x, y)| same_attr(x, y))
448}
449
450fn same_attr(a: &Attr, b: &Attr) -> bool {
451    a.name == b.name && same_value(&a.value, &b.value)
452}
453
454pub(crate) fn same_value(a: &Value, b: &Value) -> bool {
455    match (a, b) {
456        (Value::Str(x, _), Value::Str(y, _)) => x == y,
457        (Value::Theme(x, _), Value::Theme(y, _)) => x == y,
458        (Value::Array(x), Value::Array(y)) => {
459            x.len() == y.len() && x.iter().zip(y).all(|(a, b)| same_value(a, b))
460        }
461        (Value::Object(x), Value::Object(y)) => {
462            x.len() == y.len()
463                && x.iter()
464                    .zip(y)
465                    .all(|((ka, a), (kb, b))| ka == kb && same_value(a, b))
466        }
467        _ => a == b,
468    }
469}
470
471#[cfg(test)]
472mod tests;