1use crate::kernels::geometry::{js_round, Rect};
9use std::collections::{HashMap, HashSet};
10
11pub const QUALITY_BLOCK: usize = 16;
12
13#[inline]
14fn bit(bits: &[u8], p: usize) -> bool {
15 bits[p >> 3] & (1 << (p & 7)) != 0
16}
17#[inline]
18fn set_bit(bits: &mut [u8], p: usize) {
19 bits[p >> 3] |= 1 << (p & 7);
20}
21#[inline]
22fn clear_bit(bits: &mut [u8], p: usize) {
23 bits[p >> 3] &= !(1 << (p & 7));
24}
25
26pub struct OverwriteTile<'a> {
30 pub size: usize,
31 pub pixels: &'a mut [u8],
32 pub coverage: &'a mut [u8],
33 pub provisional: &'a mut [u8],
34 pub quality: &'a mut [u8],
35 pub conflicts: &'a mut [u8],
36 pub owner: &'a mut [u32],
37 pub frozen: &'a mut [u8],
38}
39
40#[derive(Default, Debug, PartialEq)]
41pub struct OverwriteStats {
42 pub added: u32,
43 pub conflict_pixels: u32,
46 pub provisional_delta: i32,
47 pub changed: bool,
48}
49
50#[allow(clippy::too_many_arguments)]
56pub fn overwrite_tile(
57 tile: &mut OverwriteTile<'_>,
58 rgba: &[u8],
59 img_width: i64,
60 blocks: &[(i64, i64)],
61 ox: i64,
62 oy: i64,
63 tx: i64,
64 ty: i64,
65 frame: u32,
66 confidence: f64,
67 stable: bool,
68) -> OverwriteStats {
69 let size = tile.size as i64;
70 let b = QUALITY_BLOCK as i64;
71 let per_tile = size / b;
72 let mut stats = OverwriteStats::default();
73 let quality = js_round(confidence * 255.0) as u8;
76 for &(bx, by) in blocks {
77 for y in by * b..by * b + b {
78 for x in bx * b..bx * b + b {
79 let src = (((y - oy) * img_width + (x - ox)) * 4) as usize;
80 let dst = (((y - ty * size) * size + (x - tx * size)) * 4) as usize;
81 tile.pixels[dst..dst + 4].copy_from_slice(&rgba[src..src + 4]);
82 let px = dst / 4;
83 if !bit(tile.coverage, px) {
84 set_bit(tile.coverage, px);
85 stats.added += 1;
86 }
87 if bit(tile.provisional, px) {
88 clear_bit(tile.provisional, px);
89 stats.provisional_delta -= 1;
90 }
91 stats.changed = true;
92 stats.conflict_pixels += 1;
93 }
94 }
95 let local_bx = bx - tx * per_tile;
96 let local_by = by - ty * per_tile;
97 let q = (local_by * per_tile + local_bx) as usize;
98 tile.owner[q] = frame + 1;
99 tile.quality[q] = quality;
100 tile.conflicts[q] = 1;
101 if stable {
102 tile.frozen[q] = 1;
103 }
104 }
105 stats
106}
107
108pub struct Component {
112 pub x0: i32,
113 pub y0: i32,
114 pub width: i32,
115 pub height: i32,
116 pub blocks: Vec<(i32, i32)>,
117}
118
119pub fn components(cells: &[(i32, i32)], size: i32) -> Vec<Component> {
126 let mut remaining: HashSet<(i32, i32)> = cells.iter().copied().collect();
127 let mut out = Vec::new();
128 for &seed in cells {
129 if !remaining.remove(&seed) {
130 continue;
131 }
132 let mut queue = vec![seed];
133 let mut i = 0;
134 let (mut x0, mut y0, mut x1, mut y1) = (i32::MAX, i32::MAX, i32::MIN, i32::MIN);
135 while i < queue.len() {
136 let (x, y) = queue[i];
137 x0 = x0.min(x);
138 x1 = x1.max(x);
139 y0 = y0.min(y);
140 y1 = y1.max(y);
141 for dy in -1..=1 {
142 for dx in -1..=1 {
143 let n = (x + dx, y + dy);
144 if remaining.remove(&n) {
145 queue.push(n);
146 }
147 }
148 }
149 i += 1;
150 }
151 out.push(Component {
152 x0: x0 * size,
153 y0: y0 * size,
154 width: (x1 - x0 + 1) * size,
155 height: (y1 - y0 + 1) * size,
156 blocks: queue,
157 });
158 }
159 out
160}
161
162fn rect_union(a: Rect, b: Rect) -> Rect {
166 if a.width <= 0.0 || a.height <= 0.0 {
167 return b;
168 }
169 let (x, y) = (a.x.min(b.x), a.y.min(b.y));
170 Rect {
171 x,
172 y,
173 width: (a.x + a.width).max(b.x + b.width) - x,
174 height: (a.y + a.height).max(b.y + b.height) - y,
175 }
176}
177
178fn rect_intersect(a: Rect, b: Rect) -> Rect {
179 let (x, y) = (a.x.max(b.x), a.y.max(b.y));
180 Rect {
181 x,
182 y,
183 width: ((a.x + a.width).min(b.x + b.width) - x).max(0.0),
184 height: ((a.y + a.height).min(b.y + b.height) - y).max(0.0),
185 }
186}
187
188fn persisted_blocks(keys: &HashSet<(i32, i32)>) -> Vec<(i32, i32)> {
192 let mut v: Vec<(i32, i32)> = keys.iter().copied().collect();
193 v.sort_by_key(|&(bx, by)| (by, bx));
194 v
195}
196
197#[derive(Clone, Debug, PartialEq)]
200pub struct TemporalRecord {
201 pub id: String,
202 pub rect: Rect,
203 pub keys: HashSet<(i32, i32)>,
204 pub chosen_frame: u32,
205 pub chosen_time: f64,
206 pub complete: bool,
207 pub revisions: u32,
208}
209
210#[derive(Default, Clone)]
216struct OrderedIds {
217 order: Vec<String>,
218}
219impl OrderedIds {
220 fn insert(&mut self, id: &str) {
221 if !self.order.iter().any(|x| x == id) {
222 self.order.push(id.to_string());
223 }
224 }
225 fn remove(&mut self, id: &str) {
226 if let Some(pos) = self.order.iter().position(|x| x == id) {
227 self.order.remove(pos);
228 }
229 }
230 fn take(&mut self) -> Vec<String> {
231 std::mem::take(&mut self.order)
232 }
233}
234
235#[derive(Default)]
239struct RecordMap {
240 order: Vec<String>,
241 map: HashMap<String, TemporalRecord>,
242}
243impl RecordMap {
244 fn get(&self, id: &str) -> Option<&TemporalRecord> {
245 self.map.get(id)
246 }
247 fn set(&mut self, record: TemporalRecord) {
248 if !self.map.contains_key(&record.id) {
249 self.order.push(record.id.clone());
250 }
251 self.map.insert(record.id.clone(), record);
252 }
253 fn delete(&mut self, id: &str) {
254 if self.map.remove(id).is_some() {
255 if let Some(pos) = self.order.iter().position(|x| x == id) {
256 self.order.remove(pos);
257 }
258 }
259 }
260 fn values_in_order(&self) -> impl Iterator<Item = &TemporalRecord> {
261 self.order.iter().map(move |id| &self.map[id])
262 }
263}
264
265struct PendingResolve {
269 record: TemporalRecord,
270 old_existed: bool,
271 expanded: bool,
272 choose: bool,
273 olds_to_delete: Vec<String>,
274 rect: Rect,
275 write_blocks: Vec<(i32, i32)>,
276 frame: u32,
277 time: f64,
278}
279
280#[derive(Default)]
281pub struct TemporalIndex {
282 records: RecordMap,
283 dirty: OrderedIds,
284 deleted: OrderedIds,
285 pending: Option<PendingResolve>,
286}
287
288pub struct CommitOut {
289 pub chosen: bool,
290 pub emit_incomplete: bool,
291 pub rect: Rect,
292}
293
294impl TemporalIndex {
295 pub fn new() -> Self {
296 Self::default()
297 }
298
299 pub fn load(&mut self, record: TemporalRecord) {
302 self.records.set(record);
303 }
304
305 #[allow(clippy::too_many_arguments)]
317 pub fn decide(
318 &mut self,
319 comp_bounds: Rect,
320 comp_blocks: &[(i32, i32)],
321 frame: u32,
322 time: f64,
323 visible: Rect,
324 latest_policy: bool,
325 mut next_seq: u64,
326 ) -> (usize, u64) {
327 let mut olds: Vec<String> = Vec::new();
330 for rec in self.records.values_in_order() {
331 let expanded_rect = Rect {
332 x: rec.rect.x - 16.0,
333 y: rec.rect.y - 16.0,
334 width: rec.rect.width + 32.0,
335 height: rec.rect.height + 32.0,
336 };
337 let ix = rect_intersect(expanded_rect, comp_bounds);
338 if ix.width > 0.0 && ix.height > 0.0 {
339 olds.push(rec.id.clone());
340 }
341 }
342 let old_id = olds.first().cloned();
343 let mut rect = comp_bounds;
348 let mut previous_size: usize = 0;
349 let mut largest_id: Option<String> = None;
350 let mut largest_size: usize = 0;
351 for id in &olds {
352 let rec = self.records.get(id).unwrap();
353 rect = rect_union(rect, rec.rect);
354 previous_size += rec.keys.len();
355 if largest_id.is_none() || rec.keys.len() > largest_size {
356 largest_size = rec.keys.len();
357 largest_id = Some(id.clone());
358 }
359 }
360 let mut mask: HashSet<(i32, i32)> = largest_id
361 .as_ref()
362 .map(|id| self.records.get(id).unwrap().keys.clone())
363 .unwrap_or_default();
364 for id in &olds {
365 if Some(id) != largest_id.as_ref() {
366 for k in &self.records.get(id).unwrap().keys {
367 mask.insert(*k);
368 }
369 }
370 }
371 for k in comp_blocks {
372 mask.insert(*k);
373 }
374 let old_rec = old_id.as_ref().and_then(|id| self.records.get(id)).cloned();
375 let geometry_changed = match &old_rec {
376 None => true,
377 Some(o) => {
378 rect.x != o.rect.x
379 || rect.y != o.rect.y
380 || rect.width != o.rect.width
381 || rect.height != o.rect.height
382 }
383 };
384 let membership_changed = match &old_rec {
388 None => true,
389 Some(_) => mask.len() != previous_size,
390 };
391 let expanded = geometry_changed || membership_changed;
392 let mut record = match &old_rec {
393 Some(o) => TemporalRecord {
394 id: o.id.clone(),
395 rect,
396 keys: mask.clone(),
397 chosen_frame: o.chosen_frame,
398 chosen_time: o.chosen_time,
399 complete: o.complete,
400 revisions: o.revisions,
401 },
402 None => {
403 let id = format!("{:010}", next_seq);
404 next_seq += 1;
405 TemporalRecord {
406 id,
407 rect,
408 keys: mask.clone(),
409 chosen_frame: frame,
410 chosen_time: time,
411 complete: false,
412 revisions: 0,
413 }
414 }
415 };
416 if expanded {
417 record.complete = false;
418 }
419 let iv = rect_intersect(rect, visible);
420 let complete_visible = iv.width == rect.width && iv.height == rect.height;
421 let choose = complete_visible
422 && (old_rec.is_none()
423 || !old_rec.as_ref().unwrap().complete
424 || expanded
425 || latest_policy);
426 let write_blocks = if choose {
427 persisted_blocks(&mask)
428 } else {
429 Vec::new()
430 };
431 let write_block_count = write_blocks.len();
432 let olds_to_delete: Vec<String> = olds.into_iter().filter(|id| *id != record.id).collect();
433 self.pending = Some(PendingResolve {
434 record,
435 old_existed: old_rec.is_some(),
436 expanded,
437 choose,
438 olds_to_delete,
439 rect,
440 write_blocks,
441 frame,
442 time,
443 });
444 (write_block_count, next_seq)
445 }
446
447 pub fn pending_write_blocks(&self) -> &[(i32, i32)] {
451 self.pending
452 .as_ref()
453 .map(|p| p.write_blocks.as_slice())
454 .unwrap_or(&[])
455 }
456
457 pub fn commit(&mut self, mask_complete_ok: bool) -> Option<CommitOut> {
462 let mut pending = self.pending.take()?;
463 let chosen = pending.choose && mask_complete_ok;
464 if chosen {
465 pending.record.chosen_frame = pending.frame;
466 pending.record.chosen_time = pending.time;
467 pending.record.complete = true;
468 pending.record.revisions += 1;
469 }
470 let emit_incomplete =
471 !pending.record.complete && (!pending.old_existed || pending.expanded);
472 let rect = pending.rect;
473 let record_id = pending.record.id.clone();
474 self.records.set(pending.record);
475 self.dirty.insert(&record_id);
476 self.deleted.remove(&record_id);
477 for id in &pending.olds_to_delete {
478 self.records.delete(id);
479 self.dirty.remove(id);
480 self.deleted.insert(id);
481 }
482 Some(CommitOut {
483 chosen,
484 emit_incomplete,
485 rect,
486 })
487 }
488
489 pub fn flush_sizes(&self) -> (usize, usize, usize) {
494 let dirty_block_total: usize = self
495 .dirty
496 .order
497 .iter()
498 .map(|id| self.records.get(id).map(|r| r.keys.len()).unwrap_or(0))
499 .sum();
500 (
501 self.deleted.order.len(),
502 self.dirty.order.len(),
503 dirty_block_total,
504 )
505 }
506
507 pub fn flush_take(&mut self) -> (Vec<String>, Vec<DirtyRow>) {
511 let deleted = self.deleted.take();
512 let dirty_ids = self.dirty.take();
513 let dirty = dirty_ids
514 .into_iter()
515 .map(|id| {
516 let rec = self.records.get(&id).unwrap().clone();
517 let blocks = persisted_blocks(&rec.keys);
518 (rec, blocks)
519 })
520 .collect();
521 (deleted, dirty)
522 }
523}
524
525pub type DirtyRow = (TemporalRecord, Vec<(i32, i32)>);
528
529fn occluded(occlusions: &[Rect], sx: f64, sy: f64) -> bool {
532 occlusions
533 .iter()
534 .any(|o| sx >= o.x && sx < o.x + o.width && sy >= o.y && sy < o.y + o.height)
535}
536
537#[allow(clippy::too_many_arguments)]
543pub fn mask_complete(
544 write_blocks: &[(i32, i32)],
545 labels: &[u8],
546 atlas_width: i64,
547 atlas_height: i64,
548 code: u8,
549 occlusions: &[Rect],
550 consistent: Option<&[u8]>,
551 ox: i64,
552 oy: i64,
553) -> bool {
554 let b = QUALITY_BLOCK as i64;
559 for &(bx, by) in write_blocks {
560 let (bx, by) = (bx as i64, by as i64);
561 for y in by * b..by * b + b {
562 let sy = y - oy;
563 for x in bx * b..bx * b + b {
564 let sx = x - ox;
565 let contains = sx >= 0
566 && sy >= 0
567 && sx < atlas_width
568 && sy < atlas_height
569 && labels[(sy * atlas_width + sx) as usize] == code;
570 if !contains || occluded(occlusions, sx as f64, sy as f64) {
571 return false;
572 }
573 if let Some(c) = consistent {
574 if c[(sy * atlas_width + sx) as usize] == 0 {
575 return false;
576 }
577 }
578 }
579 }
580 }
581 true
582}
583
584#[cfg(test)]
585mod tests {
586 use super::*;
587
588 #[test]
589 fn single_block() {
590 let comps = components(&[(3, 4)], 16);
591 assert_eq!(comps.len(), 1);
592 assert_eq!(
593 (comps[0].x0, comps[0].y0, comps[0].width, comps[0].height),
594 (48, 64, 16, 16)
595 );
596 assert_eq!(comps[0].blocks, vec![(3, 4)]);
597 }
598
599 #[test]
600 fn l_shape_stays_concave() {
601 let comps = components(&[(0, 0), (1, 0), (0, 1)], 16);
604 assert_eq!(comps.len(), 1);
605 assert_eq!(comps[0].blocks.len(), 3);
606 assert_eq!((comps[0].width, comps[0].height), (32, 32));
607 }
608
609 #[test]
610 fn diagonal_touch_connects() {
611 let comps = components(&[(0, 0), (1, 1)], 16);
613 assert_eq!(comps.len(), 1);
614 }
615
616 #[test]
617 fn disjoint_cells_split_and_keep_seed_order() {
618 let comps = components(&[(0, 0), (10, 10), (0, 1)], 16);
619 assert_eq!(comps.len(), 2);
620 assert_eq!(comps[0].blocks.len(), 2);
622 assert_eq!(comps[1].blocks, vec![(10, 10)]);
623 }
624
625 #[test]
626 fn overwrite_writes_pixels_and_evidence() {
627 let size = 32usize;
628 let blocks = (size / QUALITY_BLOCK).pow(2);
629 let mut pixels = vec![0u8; size * size * 4];
630 let mut coverage = vec![0u8; size * size / 8];
631 let mut provisional = vec![0xffu8; size * size / 8]; let (mut quality, mut conflicts, mut owner, mut frozen) = (
633 vec![0u8; blocks],
634 vec![0u8; blocks],
635 vec![0u32; blocks],
636 vec![0u8; blocks],
637 );
638 let mut tile = OverwriteTile {
639 size,
640 pixels: &mut pixels,
641 coverage: &mut coverage,
642 provisional: &mut provisional,
643 quality: &mut quality,
644 conflicts: &mut conflicts,
645 owner: &mut owner,
646 frozen: &mut frozen,
647 };
648 let img_width = 32i64;
649 let rgba: Vec<u8> = (0..img_width * img_width)
650 .flat_map(|i| [i as u8, 1, 2, 255])
651 .collect();
652 let stats = overwrite_tile(
653 &mut tile,
654 &rgba,
655 img_width,
656 &[(0, 0)],
657 0,
658 0,
659 0,
660 0,
661 3,
662 0.7,
663 true,
664 );
665 assert_eq!(stats.added, 256);
666 assert_eq!(stats.conflict_pixels, 256);
667 assert_eq!(stats.provisional_delta, -256);
668 assert!(stats.changed);
669 assert_eq!(tile.owner[0], 4);
670 assert_eq!(tile.quality[0], 179); assert_eq!(tile.conflicts[0], 1);
672 assert_eq!(tile.frozen[0], 1);
673 assert_eq!(tile.pixels[0], 0);
674 assert_eq!(tile.pixels[4], 1);
675 }
676
677 #[test]
678 fn overwrite_respects_non_stable_policy() {
679 let size = 16usize;
680 let mut pixels = vec![0u8; size * size * 4];
681 let mut coverage = vec![0u8; size * size / 8];
682 let mut provisional = vec![0u8; size * size / 8];
683 let (mut quality, mut conflicts, mut owner, mut frozen) =
684 (vec![0u8; 1], vec![0u8; 1], vec![0u32; 1], vec![0u8; 1]);
685 let mut tile = OverwriteTile {
686 size,
687 pixels: &mut pixels,
688 coverage: &mut coverage,
689 provisional: &mut provisional,
690 quality: &mut quality,
691 conflicts: &mut conflicts,
692 owner: &mut owner,
693 frozen: &mut frozen,
694 };
695 let rgba = vec![9u8; 16 * 16 * 4];
696 overwrite_tile(&mut tile, &rgba, 16, &[(0, 0)], 0, 0, 0, 0, 0, 0.0, false);
697 assert_eq!(tile.frozen[0], 0);
698 assert_eq!(tile.quality[0], 0); }
700
701 fn block_rect(bx: i32, by: i32, w: i32, h: i32) -> Rect {
705 Rect {
706 x: (bx * 16) as f64,
707 y: (by * 16) as f64,
708 width: (w * 16) as f64,
709 height: (h * 16) as f64,
710 }
711 }
712 fn keys(blocks: &[(i32, i32)]) -> HashSet<(i32, i32)> {
713 blocks.iter().copied().collect()
714 }
715 fn resolve(
720 index: &mut TemporalIndex,
721 comp_blocks: &[(i32, i32)],
722 frame: u32,
723 visible: Rect,
724 seq: &mut u64,
725 ) -> CommitOut {
726 let bounds = comp_blocks
727 .iter()
728 .fold(None::<Rect>, |acc, &(bx, by)| {
729 let r = block_rect(bx, by, 1, 1);
730 Some(acc.map_or(r, |a| rect_union(a, r)))
731 })
732 .unwrap();
733 let (_, next_seq) = index.decide(
734 bounds,
735 comp_blocks,
736 frame,
737 frame as f64,
738 visible,
739 false,
740 *seq,
741 );
742 *seq = next_seq;
743 index.commit(true).unwrap()
744 }
745
746 #[test]
747 fn flush_order_survives_a_delete_then_reinsert_shaped_sequence() {
748 let visible = block_rect(0, 0, 13, 1);
751 let mut index = TemporalIndex::new();
752 let mut seq = 0u64;
753 for &b in &[0, 4, 8, 12] {
754 resolve(&mut index, &[(b, 0)], b as u32, visible, &mut seq);
755 }
756 let ids = |n: u64| format!("{:010}", n);
757 assert_eq!(index.records.order, vec![ids(0), ids(1), ids(2), ids(3)]);
758 index.flush_take(); let out_bridge = resolve(
762 &mut index,
763 &[(4, 0), (5, 0), (6, 0), (7, 0), (8, 0)],
764 5,
765 visible,
766 &mut seq,
767 );
768 assert!(out_bridge.chosen);
769 index.decide(
770 block_rect(12, 0, 1, 1),
771 &[(12, 0)],
772 5,
773 5.0,
774 visible,
775 false,
776 seq,
777 );
778 assert_eq!(
779 index.pending_write_blocks().len(),
780 0,
781 "D neither expands nor re-chooses — decide() must not offer a mask to check"
782 );
783 let out_d = index.commit(false).unwrap();
784 assert!(!out_d.chosen);
785 assert_eq!(index.dirty.order, vec![ids(1), ids(3)]);
786 assert_eq!(index.deleted.order, vec![ids(2)]);
787 let all_blocks: Vec<(i32, i32)> = (0..=12).map(|bx| (bx, 0)).collect();
789 let out_mega = resolve(&mut index, &all_blocks, 6, visible, &mut seq);
790 assert!(out_mega.chosen);
791 assert_eq!(index.dirty.order, vec![ids(0)], "B and D — both still dirty and never flushed — are deleted before ever reaching a flush");
792 assert_eq!(index.deleted.order, vec![ids(2), ids(1), ids(3)]);
793 let (deleted, dirty) = index.flush_take();
794 assert_eq!(deleted, vec![ids(2), ids(1), ids(3)]);
795 assert_eq!(dirty.len(), 1);
796 assert_eq!(dirty[0].0.id, ids(0));
797 assert_eq!(dirty[0].1.len(), 13);
798 assert_eq!(index.dirty.order.len(), 0);
799 assert_eq!(index.deleted.order.len(), 0);
800 }
801
802 #[test]
803 fn fresh_id_colliding_with_a_live_record_overwrites_it_in_place() {
804 let visible = block_rect(0, 0, 6, 1);
805 let mut index = TemporalIndex::new();
806 index.load(TemporalRecord {
807 id: "0000000000".into(),
808 rect: block_rect(5, 0, 1, 1),
809 keys: keys(&[(5, 0)]),
810 chosen_frame: 0,
811 chosen_time: 0.0,
812 complete: true,
813 revisions: 1,
814 });
815 let mut seq = 0u64;
816 let out = resolve(&mut index, &[(0, 0)], 1, visible, &mut seq);
817 assert!(out.chosen);
818 assert_eq!(
819 index.records.order,
820 vec!["0000000000".to_string()],
821 "no second row — the id collision clobbers the loaded one in place"
822 );
823 assert_eq!(
824 index.records.get("0000000000").unwrap().rect,
825 block_rect(0, 0, 1, 1)
826 );
827 assert_eq!(index.deleted.order.len(), 0);
828 }
829
830 #[test]
831 fn fresh_id_colliding_with_a_deleted_id_is_appended_and_undeletes_it() {
832 let visible = block_rect(0, 0, 12, 1);
833 let mut index = TemporalIndex::new();
834 index.load(TemporalRecord {
835 id: "0000000000".into(),
836 rect: block_rect(0, 0, 1, 1),
837 keys: keys(&[(0, 0)]),
838 chosen_frame: 0,
839 chosen_time: 0.0,
840 complete: true,
841 revisions: 1,
842 });
843 index.load(TemporalRecord {
844 id: "0000000001".into(),
845 rect: block_rect(1, 0, 1, 1),
846 keys: keys(&[(1, 0)]),
847 chosen_frame: 0,
848 chosen_time: 0.0,
849 complete: true,
850 revisions: 1,
851 });
852 let mut seq = 0u64;
853 resolve(&mut index, &[(0, 0), (1, 0)], 1, visible, &mut seq);
855 assert_eq!(index.deleted.order, vec!["0000000001".to_string()]);
856 resolve(&mut index, &[(5, 0)], 2, visible, &mut seq);
859 resolve(&mut index, &[(9, 0)], 2, visible, &mut seq);
860 assert_eq!(
861 index.deleted.order.len(),
862 0,
863 "no delete() should ever be emitted for \"0000000001\" now"
864 );
865 assert_eq!(
866 index.records.order,
867 vec!["0000000000".to_string(), "0000000001".to_string()]
868 );
869 assert_eq!(
870 index.records.get("0000000001").unwrap().rect,
871 block_rect(9, 0, 1, 1)
872 );
873 let (deleted, dirty) = index.flush_take();
874 assert_eq!(deleted.len(), 0);
875 assert_eq!(
876 dirty.iter().map(|(r, _)| r.id.clone()).collect::<Vec<_>>(),
877 vec!["0000000000".to_string(), "0000000001".to_string()]
878 );
879 }
880}