1use ogeom_core::{FastMap, FastSet};
33
34use ogeom_topo::{SameKey, Shape};
35
36#[derive(Debug, Clone, Default)]
42pub struct History {
43 generated: FastMap<SameKey, Vec<Shape>>,
44 modified: FastMap<SameKey, Vec<Shape>>,
45 deleted: FastSet<SameKey>,
46 copied: FastSet<SameKey>,
48}
49
50impl History {
51 #[must_use]
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 #[must_use]
61 pub fn identity() -> Self {
62 Self::new()
63 }
64
65 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 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 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 pub fn copy(&mut self, input: &Shape, output: Shape) {
111 self.modify(input, output);
112 self.copied.insert(SameKey(input.clone()));
113 }
114
115 #[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 #[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 #[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 #[must_use]
146 pub fn is_deleted(&self, input: &Shape) -> bool {
147 self.deleted.contains(&SameKey(input.clone()))
148 }
149
150 #[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 #[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 #[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 #[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 #[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 #[must_use]
239 pub fn then(&self, later: &Self) -> Self {
240 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 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 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 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 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 }
311
312 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 #[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 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 #[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#[derive(Debug, Clone)]
399pub struct Built {
400 pub shape: Shape,
402 pub history: History,
404}
405
406impl Built {
407 #[must_use]
409 pub const fn new(shape: Shape, history: History) -> Self {
410 Self { shape, history }
411 }
412
413 #[must_use]
418 pub fn from_nothing(shape: Shape) -> Self {
419 Self::new(shape, History::identity())
420 }
421}
422
423pub 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
439pub 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
461pub 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
496fn 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 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(©_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 let mut early = History::new();
571 early.modify(&s[0], s[1].clone());
572 assert!(early.then(©_again).copy_of(&s[0]).is_none());
573 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 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 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 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 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 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 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 #[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 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 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 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 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 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}