Skip to main content

devpage_design/scene/
edit.rs

1//! Edits: how one scene becomes the next. Each names its nodes by id — a
2//! node's own attributes changed, a subtree inserted after a sibling, a
3//! subtree removed, a subtree moved — and applying them to a scene is all or
4//! nothing, at the cost of the edits alone.
5
6use std::collections::{HashMap, HashSet};
7
8use super::{Ids, ImageFiles, ImageId, Node, Scene};
9use crate::fonts::{FontFiles, FontId};
10use crate::tree::{NodeId, Order, OrderKey};
11
12/// One step from a scene to the next, naming its nodes by id.
13#[derive(Debug, Clone, PartialEq)]
14pub enum Edit {
15    /// The node of this id takes these own attributes; its children stay.
16    Change(Node),
17    /// This subtree goes under the node `parent`, after its child `after`
18    /// or first — or, with no parent, replaces the scene.
19    Insert {
20        parent: Option<NodeId>,
21        after: Option<NodeId>,
22        node: Node,
23    },
24    /// The subtree of this id goes.
25    Remove(NodeId),
26    /// The subtree of this id moves under `parent`, after `after` or first.
27    Move {
28        id: NodeId,
29        parent: NodeId,
30        after: Option<NodeId>,
31    },
32}
33
34/// Edits with the image and font files they bring: everything that takes a
35/// scene to the next. Of the files, only those the scene before didn't hold
36/// — each travels once.
37#[derive(Debug, Clone, PartialEq, Default)]
38pub struct Edits {
39    pub list: Vec<Edit>,
40    pub images: ImageFiles,
41    pub fonts: FontFiles,
42    /// Whether the scene they make is themed.
43    pub themed: bool,
44}
45
46impl Edits {
47    /// The edits that turn `old` into `new`, by their nodes' ids: an insert
48    /// before the first of 100,000 siblings is one insert. It compares the
49    /// two scenes whole; a design resolved again finds most edits without
50    /// one, from what a save reached.
51    pub fn between(old: &Scene, new: &Scene) -> Edits {
52        Edits::onto(old, diff(old, new), &new.images, &new.fonts, new.themed)
53    }
54
55    /// `list` to apply to `scene`, with of `images` and `fonts` — the files
56    /// the scene they make may use — those `scene` doesn't hold.
57    pub fn onto(
58        scene: &Scene,
59        list: Vec<Edit>,
60        images: &ImageFiles,
61        fonts: &FontFiles,
62        themed: bool,
63    ) -> Edits {
64        Edits {
65            list,
66            images: images
67                .iter()
68                .filter(|(id, _)| !scene.images.contains_key(id))
69                .map(|(id, f)| (*id, f.clone()))
70                .collect(),
71            fonts: fonts
72                .iter()
73                .filter(|(id, _)| !scene.fonts.contains_key(id))
74                .map(|(id, f)| (*id, f.clone()))
75                .collect(),
76            themed,
77        }
78    }
79
80    /// `scene` whole, as edits from nothing: its root, and every file.
81    pub fn whole(scene: &Scene) -> Edits {
82        Edits {
83            list: vec![Edit::Insert {
84                parent: None,
85                after: None,
86                node: scene.root.clone(),
87            }],
88            images: scene.images.clone(),
89            fonts: scene.fonts.clone(),
90            themed: scene.themed,
91        }
92    }
93}
94
95/// The edits from `old` to `new`. Each node of `new` that `old` has is
96/// kept — moved if it has another parent or falls out of the longest run of
97/// siblings still in their order, and changed if its own attributes did;
98/// each one `old` hasn't is inserted, whole where nothing under it is kept,
99/// alone where something is, which is then moved into it. Last, what `old`
100/// has and `new` doesn't goes: by then nothing kept is under it.
101fn diff(old: &Scene, new: &Scene) -> Vec<Edit> {
102    if old.root.id != new.root.id {
103        return vec![Edit::Insert {
104            parent: None,
105            after: None,
106            node: new.root.clone(),
107        }];
108    }
109    subtree(old, &[], &new.root)
110}
111
112/// The edits that turn the subtree of `old` at `at` — whose root has the id
113/// `new`'s root has — into `new`: what a part of a scene resolved again is
114/// brought across by, costing that part alone.
115pub fn subtree(old: &Scene, at: &[OrderKey], new: &Node) -> Vec<Edit> {
116    let within = |id: &NodeId| old.ids.get(id).is_some_and(|o| o.starts_with(at));
117    let mut out = Vec::new();
118    // Under each node of `new`, whether something `old` has is.
119    fn kept_below(n: &Node, within: &dyn Fn(&NodeId) -> bool, out: &mut HashSet<NodeId>) -> bool {
120        let mut any = false;
121        for c in &n.children {
122            any |= kept_below(c, within, out) | within(&c.id);
123        }
124        if any {
125            out.insert(n.id.clone());
126        }
127        any
128    }
129    let mut holds = HashSet::new();
130    kept_below(new, &within, &mut holds);
131    let Some(was) = old.at(at) else {
132        return out;
133    };
134    if !was.same_own(new) {
135        out.push(Edit::Change(new.own()));
136    }
137    let mut todo = vec![new];
138    while let Some(p) = todo.pop() {
139        // The children `old` had under this same parent, by their places
140        // there: those in the longest run still in order stay.
141        let old_parent = old
142            .ids
143            .get(&p.id)
144            .filter(|o| o.starts_with(at))
145            .and_then(|o| old.at(o));
146        let rank: HashMap<&NodeId, usize> = old_parent
147            .map(|op| {
148                op.children
149                    .iter()
150                    .enumerate()
151                    .map(|(i, c)| (&c.id, i))
152                    .collect()
153            })
154            .unwrap_or_default();
155        let ranks: Vec<Option<usize>> = p
156            .children
157            .iter()
158            .map(|c| rank.get(&c.id).copied())
159            .collect();
160        let stay = longest_run(&ranks);
161        let mut prev: Option<&NodeId> = None;
162        for (i, c) in p.children.iter().enumerate() {
163            match old.node(&c.id).filter(|_| within(&c.id)) {
164                Some(was) => {
165                    if !stay[i] {
166                        out.push(Edit::Move {
167                            id: c.id.clone(),
168                            parent: p.id.clone(),
169                            after: prev.cloned(),
170                        });
171                    }
172                    if !was.same_own(c) {
173                        out.push(Edit::Change(c.own()));
174                    }
175                    todo.push(c);
176                }
177                None => {
178                    let node = if holds.contains(&c.id) {
179                        todo.push(c);
180                        c.own()
181                    } else {
182                        c.clone()
183                    };
184                    out.push(Edit::Insert {
185                        parent: Some(p.id.clone()),
186                        after: prev.cloned(),
187                        node,
188                    });
189                }
190            }
191            prev = Some(&c.id);
192        }
193    }
194    let kept: HashSet<&NodeId> = {
195        fn ids<'a>(n: &'a Node, out: &mut HashSet<&'a NodeId>) {
196            out.insert(&n.id);
197            for c in &n.children {
198                ids(c, out);
199            }
200        }
201        let mut out = HashSet::new();
202        ids(new, &mut out);
203        out
204    };
205    fn gone(n: &Node, kept: &HashSet<&NodeId>, out: &mut Vec<Edit>) {
206        for c in &n.children {
207            if kept.contains(&c.id) {
208                gone(c, kept, out);
209            } else {
210                out.push(Edit::Remove(c.id.clone()));
211            }
212        }
213    }
214    gone(was, &kept, &mut out);
215    out
216}
217
218/// Which of a list stay where they are: the longest increasing run of their
219/// places before, `None` for those that weren't there.
220fn longest_run(ranks: &[Option<usize>]) -> Vec<bool> {
221    // Patience sorting: the smallest last place of a run of each length,
222    // and each element's predecessor in its run.
223    let mut tails: Vec<usize> = Vec::new();
224    let mut before: Vec<Option<usize>> = vec![None; ranks.len()];
225    for (i, r) in ranks.iter().enumerate() {
226        let Some(r) = r else { continue };
227        let at = tails.partition_point(|&t| ranks[t].expect("ranked") < *r);
228        before[i] = at.checked_sub(1).map(|a| tails[a]);
229        if at == tails.len() {
230            tails.push(i);
231        } else {
232            tails[at] = i;
233        }
234    }
235    let mut stay = vec![false; ranks.len()];
236    let mut at = tails.last().copied();
237    while let Some(i) = at {
238        stay[i] = true;
239        at = before[i];
240    }
241    stay
242}
243
244/// What edits did, by id, for a drawing kept beside the scene to follow:
245/// the subtrees that left their place — removed, or moved away — the
246/// subtrees placed — inserted, or moved here — and the nodes that kept their
247/// place and took new own attributes, with the ones they had.
248#[derive(Debug, Default, Clone, PartialEq)]
249pub struct Applied {
250    pub removed: Vec<NodeId>,
251    pub placed: Vec<NodeId>,
252    pub changed: Vec<(NodeId, Node)>,
253}
254
255/// One operation done, and how to undo it.
256enum Step {
257    /// Put back these own attributes at this order.
258    Own(Order, Node),
259    /// Take out the subtree at this order.
260    Take(Order),
261    /// Put this subtree back under the parent at this order, under this key.
262    Put(Order, OrderKey, Node),
263    /// Move the subtree now at this order back under this parent and key.
264    Back(Order, Order, OrderKey),
265    /// Put this root back.
266    Root(Node),
267}
268
269/// Applies `edits` to `scene`, whose files `uses` counts, or says why it
270/// can't — leaving both as they were. The work is the edits': each finds its
271/// nodes by id, and the files no node uses any more are dropped by count,
272/// never by a walk of the scene.
273pub fn apply(scene: &mut Scene, uses: &mut Uses, edits: &Edits) -> Result<Applied, String> {
274    applied(scene, uses, edits).map(|(applied, _)| applied)
275}
276
277/// [`apply`], and what puts the scene back as it was before them, at their
278/// cost again: for a holder that finds only after applying them that the
279/// scene they made can't be drawn, and draws the one before.
280pub fn applied(
281    scene: &mut Scene,
282    uses: &mut Uses,
283    edits: &Edits,
284) -> Result<(Applied, Undo), String> {
285    let (images, fonts) = (&edits.images, &edits.fonts);
286    let mut undo = Vec::new();
287    let mut applied = Applied::default();
288    let done =
289        run(scene, uses, &edits.list, &mut undo, &mut applied).and_then(|()| {
290            // Every file a node uses is held or arrives with the patch.
291            if let Some(missing) = uses.images.iter().find(|(id, c)| {
292                **c > 0 && !scene.images.contains_key(id) && !images.contains_key(id)
293            }) {
294                return Err(format!(
295                    "the stream never sent the image file {:?}",
296                    missing.0
297                ));
298            }
299            if let Some(missing) = uses
300                .fonts
301                .iter()
302                .find(|(id, c)| **c > 0 && !scene.fonts.contains_key(id) && !fonts.contains_key(id))
303            {
304                return Err(format!(
305                    "the stream never sent the font file {:?}",
306                    missing.0
307                ));
308            }
309            Ok(())
310        });
311    if let Err(e) = done {
312        for u in undo.into_iter().rev() {
313            revert(scene, uses, u);
314        }
315        uses.settle();
316        return Err(e);
317    }
318    scene
319        .images
320        .extend(images.iter().map(|(id, f)| (*id, f.clone())));
321    scene
322        .fonts
323        .extend(fonts.iter().map(|(id, f)| (*id, f.clone())));
324    uses.settle();
325    let mut back = Undo {
326        steps: undo,
327        images: ImageFiles::new(),
328        fonts: FontFiles::new(),
329        themed: scene.themed,
330    };
331    scene.images.retain(|id, f| {
332        let kept = uses.images.contains_key(id);
333        if !kept {
334            back.images.insert(*id, f.clone());
335        }
336        kept
337    });
338    scene.fonts.retain(|id, f| {
339        let kept = uses.fonts.contains_key(id);
340        if !kept {
341            back.fonts.insert(*id, f.clone());
342        }
343        kept
344    });
345    scene.themed = edits.themed;
346    Ok((applied, back))
347}
348
349/// What puts a scene back as it was before edits applied to it: each step
350/// undone, last first, the files they let go held again, and whether it was
351/// themed.
352pub struct Undo {
353    steps: Vec<Step>,
354    images: ImageFiles,
355    fonts: FontFiles,
356    themed: bool,
357}
358
359impl Undo {
360    /// Puts `scene`, whose files `uses` counts, back as it was before the
361    /// edits this undoes — which must be the last applied to it.
362    pub fn undo(self, scene: &mut Scene, uses: &mut Uses) {
363        for step in self.steps.into_iter().rev() {
364            revert(scene, uses, step);
365        }
366        uses.settle();
367        scene.images.extend(self.images);
368        scene.fonts.extend(self.fonts);
369        scene.images.retain(|id, _| uses.images.contains_key(id));
370        scene.fonts.retain(|id, _| uses.fonts.contains_key(id));
371        scene.themed = self.themed;
372    }
373}
374
375/// Where a design's one scene is held: what resolving it again reads the
376/// scene from, and brings it across by — by edits applied to it there,
377/// once, and to whatever is kept beside it, like a drawing.
378pub trait Holder {
379    /// The scene as it is now.
380    fn scene(&self) -> &Scene;
381
382    /// Applies `edits` to the scene, or says why they don't apply, leaving
383    /// it as it was.
384    fn apply(&mut self, edits: &Edits) -> Result<Applied, String>;
385}
386
387/// A scene held with nothing beside it, counting the edits applied to it:
388/// what the engine's tests resolve again into.
389#[cfg(test)]
390pub(crate) struct Bare {
391    pub scene: Scene,
392    pub uses: Uses,
393    /// How many times edits were applied to it.
394    pub applies: usize,
395}
396
397#[cfg(test)]
398impl Bare {
399    pub fn new(scene: Scene) -> Bare {
400        Bare {
401            uses: Uses::of(&scene.root),
402            scene,
403            applies: 0,
404        }
405    }
406}
407
408#[cfg(test)]
409impl Holder for Bare {
410    fn scene(&self) -> &Scene {
411        &self.scene
412    }
413
414    fn apply(&mut self, edits: &Edits) -> Result<Applied, String> {
415        self.applies += 1;
416        apply(&mut self.scene, &mut self.uses, edits)
417    }
418}
419
420fn run(
421    scene: &mut Scene,
422    uses: &mut Uses,
423    ops: &[Edit],
424    undo: &mut Vec<Step>,
425    applied: &mut Applied,
426) -> Result<(), String> {
427    for o in ops {
428        match o {
429            Edit::Change(new) => {
430                let order = order_of(scene, &new.id)?;
431                let new = new.clone();
432                let place = scene.at_mut(&order).expect("indexed");
433                let mut own = std::mem::replace(place, new);
434                std::mem::swap(&mut place.children, &mut own.children);
435                uses.take_own(&own);
436                uses.add_own(place);
437                applied.changed.push((own.id.clone(), own.clone()));
438                undo.push(Step::Own(order, own));
439            }
440            Edit::Insert {
441                parent,
442                after,
443                node,
444            } => {
445                let node = node.clone();
446                let Some(parent) = parent.as_ref() else {
447                    // A new root: the scene, replaced.
448                    if Ids::of(&node).len() != count(&node) {
449                        return Err(format!("{} holds a node twice", node.id));
450                    }
451                    let old = std::mem::replace(&mut scene.root, node);
452                    uses.take(&old);
453                    uses.add(&scene.root);
454                    scene.ids = Ids::of(&scene.root);
455                    applied.removed.push(old.id.clone());
456                    applied.placed.push(scene.root.id.clone());
457                    undo.push(Step::Root(old));
458                    continue;
459                };
460                let parent = order_of(scene, parent)?;
461                let after = after_key(scene, &parent, after.as_ref())?;
462                check_unique(scene, &node, None)?;
463                uses.add(&node);
464                let id = node.id.clone();
465                let order = place(scene, &parent, after.as_ref(), node);
466                applied.placed.push(id);
467                undo.push(Step::Take(order));
468            }
469            Edit::Remove(id) => {
470                let order = order_of(scene, id)?;
471                if order.is_empty() {
472                    return Err("the root can't be removed".into());
473                }
474                let (parent, key) = (
475                    order[..order.len() - 1].to_vec(),
476                    order[order.len() - 1].clone(),
477                );
478                let node = take(scene, &order);
479                uses.take(&node);
480                applied.removed.push(id.clone());
481                undo.push(Step::Put(parent, key, node));
482            }
483            Edit::Move { id, parent, after } => {
484                let from = order_of(scene, id)?;
485                if from.is_empty() {
486                    return Err("the root can't be moved".into());
487                }
488                let parent = order_of(scene, parent)?;
489                if parent.starts_with(&from) {
490                    return Err(format!("{id} can't move under itself"));
491                }
492                let key = from[from.len() - 1].clone();
493                let old_parent = from[..from.len() - 1].to_vec();
494                let after = after_key(scene, &parent, after.as_ref())?;
495                if after.as_ref() == Some(&key) && parent == old_parent {
496                    return Err(format!("{id} can't move after itself"));
497                }
498                let node = take(scene, &from);
499                let to = place(scene, &parent, after.as_ref(), node);
500                applied.removed.push(id.clone());
501                applied.placed.push(id.clone());
502                undo.push(Step::Back(to, old_parent, key));
503            }
504        }
505    }
506    Ok(())
507}
508
509fn count(n: &Node) -> usize {
510    1 + n.children.iter().map(count).sum::<usize>()
511}
512
513fn order_of(scene: &Scene, id: &NodeId) -> Result<Order, String> {
514    scene
515        .ids
516        .get(id)
517        .cloned()
518        .ok_or_else(|| format!("no node {id}"))
519}
520
521/// The key, under the parent at `parent`, of its child `after`.
522fn after_key(
523    scene: &Scene,
524    parent: &Order,
525    after: Option<&NodeId>,
526) -> Result<Option<OrderKey>, String> {
527    let Some(id) = after else { return Ok(None) };
528    let o = order_of(scene, id)?;
529    if o.len() != parent.len() + 1 || !o.starts_with(parent) {
530        return Err(format!("{id} isn't a child of the node it is placed under"));
531    }
532    Ok(o.last().cloned())
533}
534
535/// Says if `node`'s subtree names a node the scene has, but for `but`.
536fn check_unique(scene: &Scene, node: &Node, but: Option<&NodeId>) -> Result<(), String> {
537    if Some(&node.id) != but && scene.ids.contains(&node.id) {
538        return Err(format!("{} is in the scene already", node.id));
539    }
540    let mut seen = HashSet::new();
541    fn walk<'a>(n: &'a Node, scene: &Scene, seen: &mut HashSet<&'a NodeId>) -> Result<(), String> {
542        if !seen.insert(&n.id) {
543            return Err(format!("{} is in the subtree twice", n.id));
544        }
545        for c in &n.children {
546            if scene.ids.contains(&c.id) {
547                return Err(format!("{} is in the scene already", c.id));
548            }
549            walk(c, scene, seen)?;
550        }
551        Ok(())
552    }
553    walk(node, scene, &mut seen)
554}
555
556/// Places `node` under the parent at `parent`, after its child `after` or
557/// first, indexing its subtree; returns where it is.
558fn place(scene: &mut Scene, parent: &Order, after: Option<&OrderKey>, node: Node) -> Order {
559    let p = scene.at_mut(parent).expect("indexed");
560    let key = p.children.insert_after(after, node);
561    let mut order = parent.clone();
562    order.push(key.clone());
563    index(scene, &order);
564    order
565}
566
567/// Indexes the subtree at `order`.
568fn index(scene: &mut Scene, order: &Order) {
569    let mut at = &scene.root;
570    for k in order {
571        at = at.children.get(k).expect("placed");
572    }
573    scene.ids.add(at, order.clone());
574}
575
576/// Takes the subtree at `order` out of the scene and its index.
577fn take(scene: &mut Scene, order: &Order) -> Node {
578    let (parent, key) = (&order[..order.len() - 1], &order[order.len() - 1]);
579    let node = scene
580        .at_mut(parent)
581        .expect("indexed")
582        .children
583        .remove(key)
584        .expect("indexed");
585    scene.ids.remove(&node);
586    node
587}
588
589fn revert(scene: &mut Scene, uses: &mut Uses, u: Step) {
590    match u {
591        Step::Own(order, mut own) => {
592            let place = scene.at_mut(&order).expect("done");
593            std::mem::swap(&mut place.children, &mut own.children);
594            let new = std::mem::replace(place, own);
595            uses.take_own(&new);
596            uses.add_own(scene.at(&order).expect("done"));
597        }
598        Step::Take(order) => {
599            let node = take(scene, &order);
600            uses.take(&node);
601        }
602        Step::Put(parent, key, node) => {
603            uses.add(&node);
604            let p = scene.at_mut(&parent).expect("done");
605            p.children.put(key.clone(), node);
606            let mut order = parent;
607            order.push(key);
608            index(scene, &order);
609        }
610        Step::Back(now, parent, key) => {
611            let node = take(scene, &now);
612            let p = scene.at_mut(&parent).expect("done");
613            p.children.put(key.clone(), node);
614            let mut order = parent;
615            order.push(key);
616            index(scene, &order);
617        }
618        Step::Root(old) => {
619            let new = std::mem::replace(&mut scene.root, old);
620            uses.take(&new);
621            uses.add(&scene.root);
622            scene.ids = Ids::of(&scene.root);
623        }
624    }
625}
626
627/// How many nodes of a scene show each image file and are set in each font
628/// file: what lets edits drop the files no node uses any more without
629/// walking the scene.
630#[derive(Debug, Default, Clone, PartialEq)]
631pub struct Uses {
632    images: std::collections::HashMap<ImageId, usize>,
633    fonts: std::collections::HashMap<FontId, usize>,
634}
635
636impl Uses {
637    /// The files `root` and every node under it use.
638    pub fn of(root: &Node) -> Uses {
639        let mut uses = Uses::default();
640        uses.add(root);
641        uses
642    }
643
644    fn add(&mut self, n: &Node) {
645        self.add_own(n);
646        for c in &n.children {
647            self.add(c);
648        }
649    }
650
651    fn add_own(&mut self, n: &Node) {
652        if let Some(i) = &n.image {
653            *self.images.entry(i.id).or_default() += 1;
654        }
655        if let Some(font) = n.text.as_ref().and_then(|t| t.font) {
656            *self.fonts.entry(font).or_default() += 1;
657        }
658    }
659
660    fn take(&mut self, n: &Node) {
661        self.take_own(n);
662        for c in &n.children {
663            self.take(c);
664        }
665    }
666
667    fn take_own(&mut self, n: &Node) {
668        let less = |count: Option<&mut usize>| {
669            if let Some(c) = count {
670                *c -= 1;
671            }
672        };
673        if let Some(i) = &n.image {
674            less(self.images.get_mut(&i.id));
675        }
676        if let Some(font) = n.text.as_ref().and_then(|t| t.font) {
677            less(self.fonts.get_mut(&font));
678        }
679    }
680
681    /// Drops the files no node uses any more.
682    fn settle(&mut self) {
683        self.images.retain(|_, c| *c > 0);
684        self.fonts.retain(|_, c| *c > 0);
685    }
686}
687
688#[cfg(test)]
689mod tests;