Skip to main content

ogeom_algo/
history.rs

1//! What an operation did to its inputs.
2//!
3//! `docs/DATA_MODEL.md` ยง7. Every operation in this crate and above it reports
4//! three things about each shape it was given:
5//!
6//! - **generated**: new entities made *from* it that did not exist before. A
7//!   prism's side faces are generated from the profile's edges.
8//! - **modified**: what it *became*. A face split in two is modified into both
9//!   halves.
10//! - **deleted**: it has no image in the result at all.
11//!
12//! A modification may also be marked an **exact copy**: the image is a new
13//! node standing for the same thing, on the same surface or curve within the
14//! same boundary, its vertices where they were to within their tolerances.
15//! A boolean's faces the tool never touched come through so, and a caller
16//! holding names or meshes for the input can carry them across unchanged.
17//!
18//! A parametric application records "fillet *that* edge" and must still find
19//! that edge after the model is rebuilt with different dimensions. It does so
20//! by walking history. Half-populated history does not error. It reopens the
21//! document with the wrong faces filleted, which is why every operation
22//! populates it.
23//!
24//! # Composition is the hard part
25//!
26//! Operations chain. If A modifies `x` into `y` and B then modifies `y` into
27//! `z`, the composed history must say A-then-B modified `x` into `z`. Getting
28//! that wrong is how a reference survives one rebuild and dies on the next,
29//! which is far harder to diagnose than dying immediately. See
30//! [`History::then`].
31
32use ogeom_core::{FastMap, FastSet};
33
34use ogeom_topo::{SameKey, Shape};
35
36/// A record of what one operation, or a chain of them, did.
37///
38/// Keyed by [`SameKey`]: node and placement, ignoring orientation. An edge and
39/// its reverse are the same edge, and history about one is history about both;
40/// keying on orientation would silently split every record in two.
41#[derive(Debug, Clone, Default)]
42pub struct History {
43    generated: FastMap<SameKey, Vec<Shape>>,
44    modified: FastMap<SameKey, Vec<Shape>>,
45    deleted: FastSet<SameKey>,
46    /// The modifications that are exact copies.
47    copied: FastSet<SameKey>,
48}
49
50impl History {
51    /// An empty history.
52    #[must_use]
53    pub fn new() -> Self {
54        Self::default()
55    }
56
57    /// A history in which nothing happened to anything.
58    ///
59    /// The identity for [`History::then`]: composing with it changes nothing.
60    #[must_use]
61    pub fn identity() -> Self {
62        Self::new()
63    }
64
65    /// Record that `input` produced `output` as a new entity.
66    pub fn generate(&mut self, input: &Shape, output: Shape) {
67        self.generated
68            .entry(SameKey(input.clone()))
69            .or_default()
70            .push(output);
71    }
72
73    /// Record that `input` became `output`.
74    ///
75    /// Withdraws any deletion recorded for the same shape. Deletion and
76    /// modification are contradictory claims, and the guard has to run both
77    /// ways: clearing modifications on delete but not deletions on modify
78    /// leaves a history that says both, and two callers reach opposite
79    /// conclusions from it.
80    pub fn modify(&mut self, input: &Shape, output: Shape) {
81        let key = SameKey(input.clone());
82        self.deleted.remove(&key);
83        self.modified.entry(key).or_default().push(output);
84    }
85
86    /// Record that `input` has no image in the result.
87    ///
88    /// A shape cannot be both deleted and modified: if it became something, it
89    /// was not deleted. Recording a deletion drops any modification record for
90    /// the same shape, so the two can never disagree.
91    ///
92    /// Deletion and *generation* are a different matter, and coexist freely: a
93    /// swept profile edge is consumed by the sweep (deleted) while generating
94    /// the side face that grew from it. Anything that treats a deletion as the
95    /// end of the story about a shape loses that face's ancestry.
96    pub fn delete(&mut self, input: &Shape) {
97        let key = SameKey(input.clone());
98        self.modified.remove(&key);
99        self.copied.remove(&key);
100        self.deleted.insert(key);
101    }
102
103    /// Record that `input` became `output`, an exact copy of it: a new node
104    /// standing for the same thing, on the same surface or curve within the
105    /// same boundary.
106    ///
107    /// A modification like any other to every query but
108    /// [`History::copy_of`]; a second image recorded for the same input
109    /// makes it a split, no longer a copy.
110    pub fn copy(&mut self, input: &Shape, output: Shape) {
111        self.modify(input, output);
112        self.copied.insert(SameKey(input.clone()));
113    }
114
115    /// The exact copy `input` became, where it became exactly one.
116    #[must_use]
117    pub fn copy_of(&self, input: &Shape) -> Option<&Shape> {
118        let key = SameKey(input.clone());
119        match self.modified.get(&key).map(Vec::as_slice) {
120            Some([image]) if self.copied.contains(&key) => Some(image),
121            _ => None,
122        }
123    }
124
125    /// New entities made from `input`.
126    #[must_use]
127    pub fn generated(&self, input: &Shape) -> &[Shape] {
128        self.generated
129            .get(&SameKey(input.clone()))
130            .map_or(&[], Vec::as_slice)
131    }
132
133    /// What `input` became.
134    ///
135    /// Empty for a shape the operation left alone: "unchanged" and "modified
136    /// into nothing" are different, and the second is [`History::is_deleted`].
137    #[must_use]
138    pub fn modified(&self, input: &Shape) -> &[Shape] {
139        self.modified
140            .get(&SameKey(input.clone()))
141            .map_or(&[], Vec::as_slice)
142    }
143
144    /// Whether `input` has no image in the result.
145    #[must_use]
146    pub fn is_deleted(&self, input: &Shape) -> bool {
147        self.deleted.contains(&SameKey(input.clone()))
148    }
149
150    /// Whether the operation touched `input` at all.
151    #[must_use]
152    pub fn is_affected(&self, input: &Shape) -> bool {
153        let key = SameKey(input.clone());
154        self.deleted.contains(&key)
155            || self.modified.contains_key(&key)
156            || self.generated.contains_key(&key)
157    }
158
159    /// Where `input` ended up: what it became, or itself if it was untouched.
160    ///
161    /// The question a caller resolving a stored reference actually has. A shape
162    /// an operation ignored is still there, and reporting nothing for it would
163    /// make every caller special-case the common path.
164    ///
165    /// Returns an empty slice only for a shape that was deleted.
166    #[must_use]
167    pub fn trace<'a>(&'a self, input: &'a Shape) -> &'a [Shape] {
168        if self.is_deleted(input) {
169            return &[];
170        }
171        let images = self.modified(input);
172        if images.is_empty() {
173            core::slice::from_ref(input)
174        } else {
175            images
176        }
177    }
178
179    /// Every shape this history has something to say about.
180    #[must_use]
181    pub fn inputs(&self) -> Vec<Shape> {
182        let mut out: Vec<Shape> = Vec::new();
183        let mut seen = FastSet::default();
184        for key in self
185            .generated
186            .keys()
187            .chain(self.modified.keys())
188            .chain(self.deleted.iter())
189        {
190            if seen.insert(key.clone()) {
191                out.push(key.0.clone());
192            }
193        }
194        out
195    }
196
197    /// This history with each input's images listed once.
198    ///
199    /// Composing with a step that joins several shapes into one gives an
200    /// input that became each of them that one shape several times over;
201    /// the caller who knows the step joined shapes asks for the plain list.
202    #[must_use]
203    pub fn without_repeated_images(mut self) -> Self {
204        for images in self
205            .modified
206            .values_mut()
207            .chain(self.generated.values_mut())
208        {
209            let mut kept: Vec<Shape> = Vec::with_capacity(images.len());
210            for image in images.drain(..) {
211                if !kept.iter().any(|k| k.is_same(&image)) {
212                    kept.push(image);
213                }
214            }
215            *images = kept;
216        }
217        self
218    }
219
220    /// Whether this history records nothing.
221    #[must_use]
222    pub fn is_empty(&self) -> bool {
223        self.generated.is_empty() && self.modified.is_empty() && self.deleted.is_empty()
224    }
225
226    /// This history followed by `later`.
227    ///
228    /// Composition, and the operation everything chained depends on. For each
229    /// shape either history knows about, the result answers where it ended up
230    /// after both steps.
231    ///
232    /// The subtlety is telling *unchanged* from *modified into itself*. A shape
233    /// neither step touched must come out with no record at all, not a
234    /// modification saying it became itself; otherwise composing with an empty
235    /// history would invent records, and composition would not have an
236    /// identity. So a modification is recorded only when one of the two steps
237    /// actually reported one.
238    #[must_use]
239    pub fn then(&self, later: &Self) -> Self {
240        // A step that touched nothing leaves every answer as it was.
241        if later.is_empty() {
242            return self.clone();
243        }
244        let mut subjects: Vec<Shape> = self.inputs();
245        let mut known: FastSet<SameKey> = subjects.iter().cloned().map(SameKey).collect();
246        for input in later.inputs() {
247            if known.insert(SameKey(input.clone())) {
248                subjects.push(input);
249            }
250        }
251        self.then_for(later, subjects)
252    }
253
254    /// The records [`History::then`] gives each of `subjects`, and nothing
255    /// for any other input.
256    fn then_for(&self, later: &Self, subjects: impl IntoIterator<Item = Shape>) -> Self {
257        let mut out = Self::new();
258        for input in subjects {
259            let gone_already = self.is_deleted(&input);
260
261            // What this input is after the first step. Nothing, if it was
262            // consumed; its images, if it changed; itself, if it was left alone.
263            let first_images = self.modified(&input);
264            let changed_by_self = !first_images.is_empty();
265            let after_first: Vec<Shape> = if gone_already {
266                Vec::new()
267            } else if changed_by_self {
268                first_images.to_vec()
269            } else {
270                vec![input.clone()]
271            };
272
273            if gone_already {
274                out.delete(&input);
275            } else {
276                let mut changed_by_later = false;
277                let mut final_images: Vec<Shape> = Vec::new();
278                for image in &after_first {
279                    if later.is_deleted(image) {
280                        // Surviving the first step but not the second is a
281                        // change, and one that ends in nothing.
282                        changed_by_later = true;
283                        continue;
284                    }
285                    if !later.modified(image).is_empty() {
286                        changed_by_later = true;
287                    }
288                    final_images.extend(later.trace(image).iter().cloned());
289                }
290
291                // A copy of a copy is a copy, and so is a copy the other
292                // step left alone; anything else that changed it is a change.
293                let copied = self.copy_of(&input).is_some() || !changed_by_self;
294                let copied = copied
295                    && after_first
296                        .iter()
297                        .all(|image| later.copy_of(image).is_some() || !later.is_affected(image));
298                if final_images.is_empty() {
299                    out.delete(&input);
300                } else if changed_by_self || changed_by_later {
301                    if copied && let [image] = final_images.as_slice() {
302                        out.copy(&input, image.clone());
303                    } else {
304                        for image in final_images {
305                            out.modify(&input, image);
306                        }
307                    }
308                }
309                // Otherwise neither step touched it, and silence is the answer.
310            }
311
312            // Generation is reported whether or not the input survived: a
313            // consumed edge still explains where the face that replaced it came
314            // from, and that ancestry is the whole point of the record.
315            for made in self.generated(&input) {
316                for image in later.trace(made) {
317                    out.generate(&input, image.clone());
318                }
319            }
320            for image in &after_first {
321                for made in later.generated(image) {
322                    out.generate(&input, made.clone());
323                }
324            }
325        }
326
327        out
328    }
329
330    /// This history followed by `later`, as [`History::then`] answers it,
331    /// reworking only the inputs `later` reaches.
332    ///
333    /// An input whose images and generated shapes `later` knows nothing of
334    /// keeps its records as they stand, so a step that touched a few shapes
335    /// of a large result costs what it touched.
336    #[must_use]
337    pub fn followed_by(mut self, later: &Self) -> Self {
338        if later.is_empty() {
339            return self;
340        }
341        let reached = |shape: &Shape| {
342            let key = SameKey(shape.clone());
343            later.deleted.contains(&key)
344                || later.modified.contains_key(&key)
345                || later.generated.contains_key(&key)
346        };
347        // The inputs to rework: this history's inputs whose images or
348        // generated shapes `later` reaches, then `later`'s own inputs this
349        // history does not know, in the order `then` meets them.
350        let mut subjects: Vec<Shape> = Vec::new();
351        let mut known: FastSet<SameKey> = FastSet::default();
352        for input in self.inputs() {
353            let key = SameKey(input.clone());
354            let images = match self.modified.get(&key) {
355                _ if self.deleted.contains(&key) => &[][..],
356                Some(images) => images.as_slice(),
357                None => core::slice::from_ref(&input),
358            };
359            let made = self.generated.get(&key).map_or(&[][..], Vec::as_slice);
360            if images.iter().chain(made).any(reached) {
361                subjects.push(input);
362            }
363            known.insert(key);
364        }
365        for input in later.inputs() {
366            if known.insert(SameKey(input.clone())) {
367                subjects.push(input);
368            }
369        }
370        let reworked = self.then_for(later, subjects.iter().cloned());
371        for input in &subjects {
372            let key = SameKey(input.clone());
373            self.generated.remove(&key);
374            self.modified.remove(&key);
375            self.deleted.remove(&key);
376            self.copied.remove(&key);
377        }
378        self.generated.extend(reworked.generated);
379        self.modified.extend(reworked.modified);
380        self.deleted.extend(reworked.deleted);
381        self.copied.extend(reworked.copied);
382        self
383    }
384
385    /// Fold a sequence of histories into one, in order.
386    #[must_use]
387    pub fn chain(steps: &[Self]) -> Self {
388        steps
389            .iter()
390            .fold(Self::identity(), |acc, step| acc.then(step))
391    }
392}
393
394/// A shape together with the history of the operation that produced it.
395///
396/// The return type of every operation. Bundling them means an operation cannot
397/// return a result without saying what it did to get there.
398#[derive(Debug, Clone)]
399pub struct Built {
400    /// The result.
401    pub shape: Shape,
402    /// What the operation did to its inputs.
403    pub history: History,
404}
405
406impl Built {
407    /// A result with its history.
408    #[must_use]
409    pub const fn new(shape: Shape, history: History) -> Self {
410        Self { shape, history }
411    }
412
413    /// A result of an operation that had no inputs to report on.
414    ///
415    /// For a primitive built from numbers rather than from existing topology,
416    /// there is nothing for the history to say.
417    #[must_use]
418    pub fn from_nothing(shape: Shape) -> Self {
419        Self::new(shape, History::identity())
420    }
421}
422
423/// Make every node below `root` the root's alone, as
424/// [`Model::unshare`](ogeom_topo::Model::unshare) does, and say so: each
425/// face, edge and vertex copied, as it stands below `root`, is recorded as
426/// an exact copy of the one it stands in for.
427///
428/// What an operation that edits below a shape it was given calls first, so
429/// the edit reaches no other shape holding the same nodes.
430///
431/// # Errors
432///
433/// As [`Model::unshare`](ogeom_topo::Model::unshare).
434pub fn unshare(model: &mut ogeom_topo::Model, root: &Shape) -> ogeom_core::OgeomResult<History> {
435    let copies = model.unshare(root)?;
436    copies_below(model, root, &copies)
437}
438
439/// `run` on `root` and the shapes `parts` below it, once every node below
440/// `root` is the root's alone: what other shapes hold as well is copied
441/// first, `root` itself included where it is held whole
442/// ([`Model::unshare_each`](ogeom_topo::Model::unshare_each)), and each
443/// part is carried to its copy. The history runs from the shapes as
444/// given.
445///
446/// What an operation that returns what it makes of a shape, and edits
447/// below it on the way, runs inside.
448///
449/// # Errors
450///
451/// As [`Model::unshare`](ogeom_topo::Model::unshare), and as `run`.
452pub fn on_own_nodes(
453    model: &mut ogeom_topo::Model,
454    root: &Shape,
455    parts: &[Shape],
456    run: impl FnOnce(&mut ogeom_topo::Model, &Shape, &[Shape]) -> ogeom_core::OgeomResult<Built>,
457) -> ogeom_core::OgeomResult<Built> {
458    on_own_nodes_with(model, root, parts, run, |built| &mut built.history)
459}
460
461/// As [`on_own_nodes`], for an operation whose answer holds its history
462/// beside other things: `history` finds it in the answer.
463///
464/// # Errors
465///
466/// As [`on_own_nodes`].
467pub fn on_own_nodes_with<R>(
468    model: &mut ogeom_topo::Model,
469    root: &Shape,
470    parts: &[Shape],
471    run: impl FnOnce(&mut ogeom_topo::Model, &Shape, &[Shape]) -> ogeom_core::OgeomResult<R>,
472    history: impl FnOnce(&mut R) -> &mut History,
473) -> ogeom_core::OgeomResult<R> {
474    let unshared = model.unshare_each(std::slice::from_ref(root))?;
475    if unshared.copies.is_empty() {
476        return run(model, root, parts);
477    }
478    let own = unshared.shapes[0].clone();
479    let mut copied = copies_below(model, &own, &unshared.copies)?;
480    if own.node() != root.node() {
481        copied.copy(root, own.clone());
482    }
483    let parts: Vec<Shape> = parts
484        .iter()
485        .map(|part| match copied.copy_of(part) {
486            Some(copy) => Shape::new(copy.node(), part.location().clone(), part.orientation()),
487            None => part.clone(),
488        })
489        .collect();
490    let mut answer = run(model, &own, &parts)?;
491    let made = history(&mut answer);
492    *made = copied.then(made);
493    Ok(answer)
494}
495
496/// Each face, edge and vertex below `root` that is one of `copies`, as an
497/// exact copy of the one it stands in for.
498fn copies_below(
499    model: &ogeom_topo::Model,
500    root: &Shape,
501    copies: &[(ogeom_topo::TShapeId, ogeom_topo::TShapeId)],
502) -> ogeom_core::OgeomResult<History> {
503    use ogeom_topo::{ShapeType, explore_unique};
504
505    let mut history = History::new();
506    if copies.is_empty() {
507        return Ok(history);
508    }
509    let original: FastMap<ogeom_topo::TShapeId, ogeom_topo::TShapeId> =
510        copies.iter().map(|&(old, new)| (new, old)).collect();
511    for want in [ShapeType::Face, ShapeType::Edge, ShapeType::Vertex] {
512        for shape in explore_unique(model, root, want)? {
513            if let Some(&old) = original.get(&shape.node()) {
514                let was = Shape::new(old, shape.location().clone(), shape.orientation());
515                history.copy(&was, shape);
516            }
517        }
518    }
519    Ok(history)
520}
521
522#[cfg(test)]
523#[allow(clippy::unwrap_used)]
524mod tests {
525    use super::*;
526    use ogeom_math::Point;
527    use ogeom_topo::Model;
528
529    fn shapes(n: usize) -> (Model, Vec<Shape>) {
530        let mut model = Model::new();
531        #[allow(clippy::cast_precision_loss)]
532        let shapes = (0..n)
533            .map(|i| model.add_point(Point::new(i as f64, 0.0, 0.0)))
534            .collect();
535        (model, shapes)
536    }
537
538    #[test]
539    fn a_copy_is_a_modification_that_says_so() {
540        let (_, s) = shapes(3);
541        let mut h = History::new();
542        h.copy(&s[0], s[1].clone());
543        assert!(h.is_affected(&s[0]));
544        assert_eq!(h.trace(&s[0]).len(), 1);
545        assert!(h.copy_of(&s[0]).is_some_and(|c| c.is_same(&s[1])));
546        // A second image makes it a split.
547        h.modify(&s[0], s[2].clone());
548        assert!(h.copy_of(&s[0]).is_none());
549    }
550
551    #[test]
552    fn copies_compose_as_copies_and_a_change_breaks_them() {
553        let (_, s) = shapes(5);
554        let mut first = History::new();
555        first.copy(&s[0], s[1].clone());
556        let mut copy_again = History::new();
557        copy_again.copy(&s[1], s[2].clone());
558        let mut change = History::new();
559        change.modify(&s[1], s[3].clone());
560        let untouched = History::new();
561
562        let both = first.then(&copy_again);
563        assert!(both.copy_of(&s[0]).is_some_and(|c| c.is_same(&s[2])));
564        let left_alone = first.then(&untouched);
565        assert!(left_alone.copy_of(&s[0]).is_some_and(|c| c.is_same(&s[1])));
566        let changed = first.then(&change);
567        assert!(changed.copy_of(&s[0]).is_none());
568        assert!(changed.modified(&s[0])[0].is_same(&s[3]));
569        // A change first, a copy after, is a change.
570        let mut early = History::new();
571        early.modify(&s[0], s[1].clone());
572        assert!(early.then(&copy_again).copy_of(&s[0]).is_none());
573        // Deleting withdraws the copy.
574        let mut gone = History::new();
575        gone.copy(&s[0], s[4].clone());
576        gone.delete(&s[0]);
577        assert!(gone.copy_of(&s[0]).is_none());
578    }
579
580    #[test]
581    fn an_empty_history_reports_everything_as_untouched() {
582        let (_, s) = shapes(1);
583        let h = History::new();
584        assert!(h.is_empty());
585        assert!(!h.is_affected(&s[0]));
586        assert!(!h.is_deleted(&s[0]));
587        assert!(h.generated(&s[0]).is_empty());
588        assert!(h.modified(&s[0]).is_empty());
589        // Tracing an untouched shape finds the shape itself, not nothing.
590        assert_eq!(h.trace(&s[0]).len(), 1);
591        assert!(h.trace(&s[0])[0].is_same(&s[0]));
592    }
593
594    #[test]
595    fn generated_modified_and_deleted_are_distinct_claims() {
596        let (_, s) = shapes(4);
597        let mut h = History::new();
598        h.generate(&s[0], s[1].clone());
599        h.modify(&s[2], s[3].clone());
600        h.delete(&s[1]);
601
602        assert_eq!(h.generated(&s[0]).len(), 1);
603        assert!(h.modified(&s[0]).is_empty(), "generating is not modifying");
604        assert_eq!(h.modified(&s[2]).len(), 1);
605        assert!(h.is_deleted(&s[1]));
606        assert!(!h.is_deleted(&s[2]));
607    }
608
609    #[test]
610    fn a_shape_cannot_be_both_deleted_and_modified() {
611        // If it became something it was not deleted, and a record claiming both
612        // would let two callers reach opposite conclusions.
613        let (_, s) = shapes(2);
614        let mut h = History::new();
615        h.modify(&s[0], s[1].clone());
616        assert_eq!(h.modified(&s[0]).len(), 1);
617
618        h.delete(&s[0]);
619        assert!(h.is_deleted(&s[0]));
620        assert!(
621            h.modified(&s[0]).is_empty(),
622            "the modification was withdrawn"
623        );
624        assert!(h.trace(&s[0]).is_empty());
625    }
626
627    #[test]
628    fn history_is_keyed_ignoring_orientation() {
629        // An edge and its reverse are the same edge. Keying on orientation
630        // would split every record in two, so a caller holding the reversed
631        // handle would find nothing.
632        let (_, s) = shapes(2);
633        let mut h = History::new();
634        h.modify(&s[0], s[1].clone());
635
636        let reversed = s[0].reversed();
637        assert_eq!(h.modified(&reversed).len(), 1, "same edge, other way round");
638        assert!(h.is_affected(&reversed));
639    }
640
641    #[test]
642    fn composition_follows_a_shape_through_two_operations() {
643        // a -> b in the first, b -> c in the second. The composed history must
644        // say a -> c; anything else and a stored reference survives one rebuild
645        // and dies on the next.
646        let (_, s) = shapes(3);
647        let (a, b, c) = (&s[0], &s[1], &s[2]);
648
649        let mut first = History::new();
650        first.modify(a, b.clone());
651        let mut second = History::new();
652        second.modify(b, c.clone());
653
654        let composed = first.then(&second);
655        assert_eq!(composed.modified(a).len(), 1);
656        assert!(composed.modified(a)[0].is_same(c));
657        assert!(!composed.is_deleted(a));
658    }
659
660    #[test]
661    fn composition_reports_a_shape_deleted_by_the_second_step() {
662        let (_, s) = shapes(2);
663        let (a, b) = (&s[0], &s[1]);
664
665        let mut first = History::new();
666        first.modify(a, b.clone());
667        let mut second = History::new();
668        second.delete(b);
669
670        let composed = first.then(&second);
671        assert!(
672            composed.is_deleted(a),
673            "a survived the first step but not the second, so it is gone"
674        );
675        assert!(composed.trace(a).is_empty());
676    }
677
678    #[test]
679    fn composition_keeps_a_deletion_from_the_first_step() {
680        let (_, s) = shapes(2);
681        let mut first = History::new();
682        first.delete(&s[0]);
683        let second = History::new();
684        assert!(first.then(&second).is_deleted(&s[0]));
685    }
686
687    #[test]
688    fn composition_splits_when_the_second_step_splits() {
689        // a -> b, then b -> {c, d}. The composed answer is a -> {c, d}, which is
690        // what a caller filleting "that edge" needs after two rebuilds.
691        let (_, s) = shapes(4);
692        let (a, b, c, d) = (&s[0], &s[1], &s[2], &s[3]);
693
694        let mut first = History::new();
695        first.modify(a, b.clone());
696        let mut second = History::new();
697        second.modify(b, c.clone());
698        second.modify(b, d.clone());
699
700        let composed = first.then(&second);
701        assert_eq!(composed.modified(a).len(), 2);
702        assert!(composed.modified(a).iter().any(|s| s.is_same(c)));
703        assert!(composed.modified(a).iter().any(|s| s.is_same(d)));
704    }
705
706    #[test]
707    fn composition_carries_generated_entities_forward() {
708        // The first step generates b from a; the second turns b into c. What a
709        // generated from the pair is c, not b; the intermediate is gone.
710        let (_, s) = shapes(3);
711        let (a, b, c) = (&s[0], &s[1], &s[2]);
712
713        let mut first = History::new();
714        first.generate(a, b.clone());
715        let mut second = History::new();
716        second.modify(b, c.clone());
717
718        let composed = first.then(&second);
719        assert_eq!(composed.generated(a).len(), 1);
720        assert!(composed.generated(a)[0].is_same(c));
721    }
722
723    #[test]
724    fn composition_passes_through_shapes_only_the_later_step_knows() {
725        let (_, s) = shapes(3);
726        let (a, b, c) = (&s[0], &s[1], &s[2]);
727
728        let mut first = History::new();
729        first.modify(a, a.clone());
730        let mut second = History::new();
731        second.modify(b, c.clone());
732
733        let composed = first.then(&second);
734        assert_eq!(composed.modified(b).len(), 1, "the first step never saw b");
735        assert!(composed.modified(b)[0].is_same(c));
736    }
737
738    #[test]
739    fn the_empty_history_is_an_identity_for_composition() {
740        let (_, s) = shapes(3);
741        let mut h = History::new();
742        h.modify(&s[0], s[1].clone());
743        h.generate(&s[0], s[2].clone());
744        h.delete(&s[1]);
745
746        for composed in [h.then(&History::identity()), History::identity().then(&h)] {
747            assert_eq!(composed.modified(&s[0]).len(), h.modified(&s[0]).len());
748            assert_eq!(composed.generated(&s[0]).len(), h.generated(&s[0]).len());
749            assert_eq!(composed.is_deleted(&s[1]), h.is_deleted(&s[1]));
750        }
751    }
752
753    /// Composing in place answers every query as composing afresh does, over
754    /// histories mixing copies, splits, deletions and generation, where the
755    /// later step reaches some inputs' images and leaves the rest alone.
756    #[test]
757    fn composing_in_place_answers_as_composing_afresh() {
758        let (_, s) = shapes(24);
759        let mut state = 0x2545_f491_u64;
760        let mut next = |n: usize| {
761            state = state
762                .wrapping_mul(6_364_136_223_846_793_005)
763                .wrapping_add(1_442_695_040_888_963_407);
764            #[allow(clippy::cast_possible_truncation)]
765            let pick = (state >> 33) as usize % n;
766            pick
767        };
768        let random = |next: &mut dyn FnMut(usize) -> usize, records: usize| {
769            let mut h = History::new();
770            for _ in 0..records {
771                let (input, output) = (&s[next(24)], s[next(24)].clone());
772                match next(4) {
773                    0 => h.copy(input, output),
774                    1 => h.modify(input, output),
775                    2 => h.delete(input),
776                    _ => h.generate(input, output),
777                }
778            }
779            h
780        };
781        for _ in 0..200 {
782            let first = random(&mut next, 12);
783            let later = random(&mut next, 4);
784            let afresh = first.then(&later);
785            let in_place = first.clone().followed_by(&later);
786            for shape in &s {
787                let nodes = |images: &[Shape]| images.iter().map(Shape::node).collect::<Vec<_>>();
788                assert_eq!(afresh.is_deleted(shape), in_place.is_deleted(shape));
789                assert_eq!(afresh.is_affected(shape), in_place.is_affected(shape));
790                assert_eq!(
791                    nodes(afresh.modified(shape)),
792                    nodes(in_place.modified(shape))
793                );
794                assert_eq!(
795                    nodes(afresh.generated(shape)),
796                    nodes(in_place.generated(shape))
797                );
798                assert_eq!(
799                    afresh.copy_of(shape).map(Shape::node),
800                    in_place.copy_of(shape).map(Shape::node)
801                );
802            }
803        }
804    }
805
806    #[test]
807    fn composition_is_associative() {
808        // Three chained operations must give the same answer however the chain
809        // is bracketed, or a caller that batches differently gets a different
810        // model.
811        let (_, s) = shapes(4);
812        let (a, b, c, d) = (&s[0], &s[1], &s[2], &s[3]);
813
814        let mut one = History::new();
815        one.modify(a, b.clone());
816        let mut two = History::new();
817        two.modify(b, c.clone());
818        let mut three = History::new();
819        three.modify(c, d.clone());
820
821        let left = one.then(&two).then(&three);
822        let right = one.then(&two.then(&three));
823
824        assert_eq!(left.modified(a).len(), right.modified(a).len());
825        assert!(left.modified(a)[0].is_same(&right.modified(a)[0]));
826        assert!(left.modified(a)[0].is_same(d));
827    }
828
829    #[test]
830    fn chaining_a_sequence_matches_folding_it_by_hand() {
831        let (_, s) = shapes(4);
832        let mut steps = Vec::new();
833        for i in 0..3 {
834            let mut h = History::new();
835            h.modify(&s[i], s[i + 1].clone());
836            steps.push(h);
837        }
838        let chained = History::chain(&steps);
839        assert!(chained.modified(&s[0])[0].is_same(&s[3]));
840        assert!(History::chain(&[]).is_empty());
841    }
842
843    #[test]
844    fn inputs_lists_every_shape_the_history_mentions_once() {
845        let (_, s) = shapes(3);
846        let mut h = History::new();
847        h.modify(&s[0], s[1].clone());
848        h.generate(&s[0], s[2].clone());
849        h.delete(&s[1]);
850
851        let inputs = h.inputs();
852        assert_eq!(inputs.len(), 2, "s0 appears twice but is listed once");
853        assert!(inputs.iter().any(|x| x.is_same(&s[0])));
854        assert!(inputs.iter().any(|x| x.is_same(&s[1])));
855    }
856
857    #[test]
858    fn a_consumed_shape_still_reports_what_it_generated() {
859        // A sweep consumes its profile edge and grows a side face from it. Both
860        // facts are true at once, and losing the second loses that face's
861        // ancestry, which is what a rebuild needs to find it again.
862        let (_, s) = shapes(2);
863        let (edge, face) = (&s[0], &s[1]);
864
865        let mut sweep = History::new();
866        sweep.generate(edge, face.clone());
867        sweep.delete(edge);
868
869        assert!(sweep.is_deleted(edge));
870        assert_eq!(
871            sweep.generated(edge).len(),
872            1,
873            "deletion is not the end of the story"
874        );
875
876        // And composition must carry it through.
877        let composed = sweep.then(&History::identity());
878        assert!(composed.is_deleted(edge));
879        assert_eq!(composed.generated(edge).len(), 1);
880        assert!(composed.generated(edge)[0].is_same(face));
881    }
882
883    #[test]
884    fn a_modification_withdraws_an_earlier_deletion() {
885        // The guard has to run both ways. Clearing modifications on delete but
886        // not deletions on modify leaves a history saying both, and two callers
887        // reach opposite conclusions from it.
888        let (_, s) = shapes(2);
889        let mut h = History::new();
890        h.delete(&s[0]);
891        h.modify(&s[0], s[1].clone());
892        assert!(!h.is_deleted(&s[0]));
893        assert_eq!(h.modified(&s[0]).len(), 1);
894    }
895
896    #[test]
897    fn a_primitive_reports_a_history_with_nothing_in_it() {
898        // Built from numbers, not from topology: there is nothing to say, and
899        // saying nothing is different from failing to say anything.
900        let (_, s) = shapes(1);
901        let built = Built::from_nothing(s[0].clone());
902        assert!(built.history.is_empty());
903        assert!(built.shape.is_same(&s[0]));
904    }
905}