1use std::collections::{HashMap, HashSet};
28
29use ogeom_topo::{SameKey, Shape};
30
31#[derive(Debug, Clone, Default)]
37pub struct History {
38 generated: HashMap<SameKey, Vec<Shape>>,
39 modified: HashMap<SameKey, Vec<Shape>>,
40 deleted: HashSet<SameKey>,
41}
42
43impl History {
44 #[must_use]
46 pub fn new() -> Self {
47 Self::default()
48 }
49
50 #[must_use]
54 pub fn identity() -> Self {
55 Self::new()
56 }
57
58 pub fn generate(&mut self, input: &Shape, output: Shape) {
60 self.generated
61 .entry(SameKey(input.clone()))
62 .or_default()
63 .push(output);
64 }
65
66 pub fn modify(&mut self, input: &Shape, output: Shape) {
74 let key = SameKey(input.clone());
75 self.deleted.remove(&key);
76 self.modified.entry(key).or_default().push(output);
77 }
78
79 pub fn delete(&mut self, input: &Shape) {
90 let key = SameKey(input.clone());
91 self.modified.remove(&key);
92 self.deleted.insert(key);
93 }
94
95 #[must_use]
97 pub fn generated(&self, input: &Shape) -> &[Shape] {
98 self.generated
99 .get(&SameKey(input.clone()))
100 .map_or(&[], Vec::as_slice)
101 }
102
103 #[must_use]
108 pub fn modified(&self, input: &Shape) -> &[Shape] {
109 self.modified
110 .get(&SameKey(input.clone()))
111 .map_or(&[], Vec::as_slice)
112 }
113
114 #[must_use]
116 pub fn is_deleted(&self, input: &Shape) -> bool {
117 self.deleted.contains(&SameKey(input.clone()))
118 }
119
120 #[must_use]
122 pub fn is_affected(&self, input: &Shape) -> bool {
123 let key = SameKey(input.clone());
124 self.deleted.contains(&key)
125 || self.modified.contains_key(&key)
126 || self.generated.contains_key(&key)
127 }
128
129 #[must_use]
137 pub fn trace<'a>(&'a self, input: &'a Shape) -> &'a [Shape] {
138 if self.is_deleted(input) {
139 return &[];
140 }
141 let images = self.modified(input);
142 if images.is_empty() {
143 core::slice::from_ref(input)
144 } else {
145 images
146 }
147 }
148
149 #[must_use]
151 pub fn inputs(&self) -> Vec<Shape> {
152 let mut out: Vec<Shape> = Vec::new();
153 let mut seen = HashSet::new();
154 for key in self
155 .generated
156 .keys()
157 .chain(self.modified.keys())
158 .chain(self.deleted.iter())
159 {
160 if seen.insert(key.clone()) {
161 out.push(key.0.clone());
162 }
163 }
164 out
165 }
166
167 #[must_use]
169 pub fn is_empty(&self) -> bool {
170 self.generated.is_empty() && self.modified.is_empty() && self.deleted.is_empty()
171 }
172
173 #[must_use]
186 pub fn then(&self, later: &Self) -> Self {
187 let mut out = Self::new();
188
189 let mut subjects: Vec<Shape> = self.inputs();
190 for input in later.inputs() {
191 if !subjects.iter().any(|s| s.is_same(&input)) {
192 subjects.push(input);
193 }
194 }
195
196 for input in subjects {
197 let gone_already = self.is_deleted(&input);
198
199 let first_images = self.modified(&input);
202 let changed_by_self = !first_images.is_empty();
203 let after_first: Vec<Shape> = if gone_already {
204 Vec::new()
205 } else if changed_by_self {
206 first_images.to_vec()
207 } else {
208 vec![input.clone()]
209 };
210
211 if gone_already {
212 out.delete(&input);
213 } else {
214 let mut changed_by_later = false;
215 let mut final_images: Vec<Shape> = Vec::new();
216 for image in &after_first {
217 if later.is_deleted(image) {
218 changed_by_later = true;
221 continue;
222 }
223 if !later.modified(image).is_empty() {
224 changed_by_later = true;
225 }
226 final_images.extend(later.trace(image).iter().cloned());
227 }
228
229 if final_images.is_empty() {
230 out.delete(&input);
231 } else if changed_by_self || changed_by_later {
232 for image in final_images {
233 out.modify(&input, image);
234 }
235 }
236 }
238
239 for made in self.generated(&input) {
243 for image in later.trace(made) {
244 out.generate(&input, image.clone());
245 }
246 }
247 for image in &after_first {
248 for made in later.generated(image) {
249 out.generate(&input, made.clone());
250 }
251 }
252 }
253
254 out
255 }
256
257 #[must_use]
259 pub fn chain(steps: &[Self]) -> Self {
260 steps
261 .iter()
262 .fold(Self::identity(), |acc, step| acc.then(step))
263 }
264}
265
266#[derive(Debug, Clone)]
271pub struct Built {
272 pub shape: Shape,
274 pub history: History,
276}
277
278impl Built {
279 #[must_use]
281 pub const fn new(shape: Shape, history: History) -> Self {
282 Self { shape, history }
283 }
284
285 #[must_use]
290 pub fn from_nothing(shape: Shape) -> Self {
291 Self::new(shape, History::identity())
292 }
293}
294
295#[cfg(test)]
296#[allow(clippy::unwrap_used)]
297mod tests {
298 use super::*;
299 use ogeom_math::Point;
300 use ogeom_topo::Model;
301
302 fn shapes(n: usize) -> (Model, Vec<Shape>) {
303 let mut model = Model::new();
304 #[allow(clippy::cast_precision_loss)]
305 let shapes = (0..n)
306 .map(|i| model.add_point(Point::new(i as f64, 0.0, 0.0)))
307 .collect();
308 (model, shapes)
309 }
310
311 #[test]
312 fn an_empty_history_reports_everything_as_untouched() {
313 let (_, s) = shapes(1);
314 let h = History::new();
315 assert!(h.is_empty());
316 assert!(!h.is_affected(&s[0]));
317 assert!(!h.is_deleted(&s[0]));
318 assert!(h.generated(&s[0]).is_empty());
319 assert!(h.modified(&s[0]).is_empty());
320 assert_eq!(h.trace(&s[0]).len(), 1);
322 assert!(h.trace(&s[0])[0].is_same(&s[0]));
323 }
324
325 #[test]
326 fn generated_modified_and_deleted_are_distinct_claims() {
327 let (_, s) = shapes(4);
328 let mut h = History::new();
329 h.generate(&s[0], s[1].clone());
330 h.modify(&s[2], s[3].clone());
331 h.delete(&s[1]);
332
333 assert_eq!(h.generated(&s[0]).len(), 1);
334 assert!(h.modified(&s[0]).is_empty(), "generating is not modifying");
335 assert_eq!(h.modified(&s[2]).len(), 1);
336 assert!(h.is_deleted(&s[1]));
337 assert!(!h.is_deleted(&s[2]));
338 }
339
340 #[test]
341 fn a_shape_cannot_be_both_deleted_and_modified() {
342 let (_, s) = shapes(2);
345 let mut h = History::new();
346 h.modify(&s[0], s[1].clone());
347 assert_eq!(h.modified(&s[0]).len(), 1);
348
349 h.delete(&s[0]);
350 assert!(h.is_deleted(&s[0]));
351 assert!(
352 h.modified(&s[0]).is_empty(),
353 "the modification was withdrawn"
354 );
355 assert!(h.trace(&s[0]).is_empty());
356 }
357
358 #[test]
359 fn history_is_keyed_ignoring_orientation() {
360 let (_, s) = shapes(2);
364 let mut h = History::new();
365 h.modify(&s[0], s[1].clone());
366
367 let reversed = s[0].reversed();
368 assert_eq!(h.modified(&reversed).len(), 1, "same edge, other way round");
369 assert!(h.is_affected(&reversed));
370 }
371
372 #[test]
373 fn composition_follows_a_shape_through_two_operations() {
374 let (_, s) = shapes(3);
378 let (a, b, c) = (&s[0], &s[1], &s[2]);
379
380 let mut first = History::new();
381 first.modify(a, b.clone());
382 let mut second = History::new();
383 second.modify(b, c.clone());
384
385 let composed = first.then(&second);
386 assert_eq!(composed.modified(a).len(), 1);
387 assert!(composed.modified(a)[0].is_same(c));
388 assert!(!composed.is_deleted(a));
389 }
390
391 #[test]
392 fn composition_reports_a_shape_deleted_by_the_second_step() {
393 let (_, s) = shapes(2);
394 let (a, b) = (&s[0], &s[1]);
395
396 let mut first = History::new();
397 first.modify(a, b.clone());
398 let mut second = History::new();
399 second.delete(b);
400
401 let composed = first.then(&second);
402 assert!(
403 composed.is_deleted(a),
404 "a survived the first step but not the second, so it is gone"
405 );
406 assert!(composed.trace(a).is_empty());
407 }
408
409 #[test]
410 fn composition_keeps_a_deletion_from_the_first_step() {
411 let (_, s) = shapes(2);
412 let mut first = History::new();
413 first.delete(&s[0]);
414 let second = History::new();
415 assert!(first.then(&second).is_deleted(&s[0]));
416 }
417
418 #[test]
419 fn composition_splits_when_the_second_step_splits() {
420 let (_, s) = shapes(4);
423 let (a, b, c, d) = (&s[0], &s[1], &s[2], &s[3]);
424
425 let mut first = History::new();
426 first.modify(a, b.clone());
427 let mut second = History::new();
428 second.modify(b, c.clone());
429 second.modify(b, d.clone());
430
431 let composed = first.then(&second);
432 assert_eq!(composed.modified(a).len(), 2);
433 assert!(composed.modified(a).iter().any(|s| s.is_same(c)));
434 assert!(composed.modified(a).iter().any(|s| s.is_same(d)));
435 }
436
437 #[test]
438 fn composition_carries_generated_entities_forward() {
439 let (_, s) = shapes(3);
442 let (a, b, c) = (&s[0], &s[1], &s[2]);
443
444 let mut first = History::new();
445 first.generate(a, b.clone());
446 let mut second = History::new();
447 second.modify(b, c.clone());
448
449 let composed = first.then(&second);
450 assert_eq!(composed.generated(a).len(), 1);
451 assert!(composed.generated(a)[0].is_same(c));
452 }
453
454 #[test]
455 fn composition_passes_through_shapes_only_the_later_step_knows() {
456 let (_, s) = shapes(3);
457 let (a, b, c) = (&s[0], &s[1], &s[2]);
458
459 let mut first = History::new();
460 first.modify(a, a.clone());
461 let mut second = History::new();
462 second.modify(b, c.clone());
463
464 let composed = first.then(&second);
465 assert_eq!(composed.modified(b).len(), 1, "the first step never saw b");
466 assert!(composed.modified(b)[0].is_same(c));
467 }
468
469 #[test]
470 fn the_empty_history_is_an_identity_for_composition() {
471 let (_, s) = shapes(3);
472 let mut h = History::new();
473 h.modify(&s[0], s[1].clone());
474 h.generate(&s[0], s[2].clone());
475 h.delete(&s[1]);
476
477 for composed in [h.then(&History::identity()), History::identity().then(&h)] {
478 assert_eq!(composed.modified(&s[0]).len(), h.modified(&s[0]).len());
479 assert_eq!(composed.generated(&s[0]).len(), h.generated(&s[0]).len());
480 assert_eq!(composed.is_deleted(&s[1]), h.is_deleted(&s[1]));
481 }
482 }
483
484 #[test]
485 fn composition_is_associative() {
486 let (_, s) = shapes(4);
490 let (a, b, c, d) = (&s[0], &s[1], &s[2], &s[3]);
491
492 let mut one = History::new();
493 one.modify(a, b.clone());
494 let mut two = History::new();
495 two.modify(b, c.clone());
496 let mut three = History::new();
497 three.modify(c, d.clone());
498
499 let left = one.then(&two).then(&three);
500 let right = one.then(&two.then(&three));
501
502 assert_eq!(left.modified(a).len(), right.modified(a).len());
503 assert!(left.modified(a)[0].is_same(&right.modified(a)[0]));
504 assert!(left.modified(a)[0].is_same(d));
505 }
506
507 #[test]
508 fn chaining_a_sequence_matches_folding_it_by_hand() {
509 let (_, s) = shapes(4);
510 let mut steps = Vec::new();
511 for i in 0..3 {
512 let mut h = History::new();
513 h.modify(&s[i], s[i + 1].clone());
514 steps.push(h);
515 }
516 let chained = History::chain(&steps);
517 assert!(chained.modified(&s[0])[0].is_same(&s[3]));
518 assert!(History::chain(&[]).is_empty());
519 }
520
521 #[test]
522 fn inputs_lists_every_shape_the_history_mentions_once() {
523 let (_, s) = shapes(3);
524 let mut h = History::new();
525 h.modify(&s[0], s[1].clone());
526 h.generate(&s[0], s[2].clone());
527 h.delete(&s[1]);
528
529 let inputs = h.inputs();
530 assert_eq!(inputs.len(), 2, "s0 appears twice but is listed once");
531 assert!(inputs.iter().any(|x| x.is_same(&s[0])));
532 assert!(inputs.iter().any(|x| x.is_same(&s[1])));
533 }
534
535 #[test]
536 fn a_consumed_shape_still_reports_what_it_generated() {
537 let (_, s) = shapes(2);
541 let (edge, face) = (&s[0], &s[1]);
542
543 let mut sweep = History::new();
544 sweep.generate(edge, face.clone());
545 sweep.delete(edge);
546
547 assert!(sweep.is_deleted(edge));
548 assert_eq!(
549 sweep.generated(edge).len(),
550 1,
551 "deletion is not the end of the story"
552 );
553
554 let composed = sweep.then(&History::identity());
556 assert!(composed.is_deleted(edge));
557 assert_eq!(composed.generated(edge).len(), 1);
558 assert!(composed.generated(edge)[0].is_same(face));
559 }
560
561 #[test]
562 fn a_modification_withdraws_an_earlier_deletion() {
563 let (_, s) = shapes(2);
567 let mut h = History::new();
568 h.delete(&s[0]);
569 h.modify(&s[0], s[1].clone());
570 assert!(!h.is_deleted(&s[0]));
571 assert_eq!(h.modified(&s[0]).len(), 1);
572 }
573
574 #[test]
575 fn a_primitive_reports_a_history_with_nothing_in_it() {
576 let (_, s) = shapes(1);
579 let built = Built::from_nothing(s[0].clone());
580 assert!(built.history.is_empty());
581 assert!(built.shape.is_same(&s[0]));
582 }
583}