use crate::kernels::geometry::{js_round, Rect};
use std::collections::{HashMap, HashSet};
pub const QUALITY_BLOCK: usize = 16;
#[inline]
fn bit(bits: &[u8], p: usize) -> bool {
bits[p >> 3] & (1 << (p & 7)) != 0
}
#[inline]
fn set_bit(bits: &mut [u8], p: usize) {
bits[p >> 3] |= 1 << (p & 7);
}
#[inline]
fn clear_bit(bits: &mut [u8], p: usize) {
bits[p >> 3] &= !(1 << (p & 7));
}
pub struct OverwriteTile<'a> {
pub size: usize,
pub pixels: &'a mut [u8],
pub coverage: &'a mut [u8],
pub provisional: &'a mut [u8],
pub quality: &'a mut [u8],
pub conflicts: &'a mut [u8],
pub owner: &'a mut [u32],
pub frozen: &'a mut [u8],
}
#[derive(Default, Debug, PartialEq)]
pub struct OverwriteStats {
pub added: u32,
pub conflict_pixels: u32,
pub provisional_delta: i32,
pub changed: bool,
}
#[allow(clippy::too_many_arguments)]
pub fn overwrite_tile(
tile: &mut OverwriteTile<'_>,
rgba: &[u8],
img_width: i64,
blocks: &[(i64, i64)],
ox: i64,
oy: i64,
tx: i64,
ty: i64,
frame: u32,
confidence: f64,
stable: bool,
) -> OverwriteStats {
let size = tile.size as i64;
let b = QUALITY_BLOCK as i64;
let per_tile = size / b;
let mut stats = OverwriteStats::default();
let quality = js_round(confidence * 255.0) as u8;
for &(bx, by) in blocks {
for y in by * b..by * b + b {
for x in bx * b..bx * b + b {
let src = (((y - oy) * img_width + (x - ox)) * 4) as usize;
let dst = (((y - ty * size) * size + (x - tx * size)) * 4) as usize;
tile.pixels[dst..dst + 4].copy_from_slice(&rgba[src..src + 4]);
let px = dst / 4;
if !bit(tile.coverage, px) {
set_bit(tile.coverage, px);
stats.added += 1;
}
if bit(tile.provisional, px) {
clear_bit(tile.provisional, px);
stats.provisional_delta -= 1;
}
stats.changed = true;
stats.conflict_pixels += 1;
}
}
let local_bx = bx - tx * per_tile;
let local_by = by - ty * per_tile;
let q = (local_by * per_tile + local_bx) as usize;
tile.owner[q] = frame + 1;
tile.quality[q] = quality;
tile.conflicts[q] = 1;
if stable {
tile.frozen[q] = 1;
}
}
stats
}
pub struct Component {
pub x0: i32,
pub y0: i32,
pub width: i32,
pub height: i32,
pub blocks: Vec<(i32, i32)>,
}
pub fn components(cells: &[(i32, i32)], size: i32) -> Vec<Component> {
let mut remaining: HashSet<(i32, i32)> = cells.iter().copied().collect();
let mut out = Vec::new();
for &seed in cells {
if !remaining.remove(&seed) {
continue;
}
let mut queue = vec![seed];
let mut i = 0;
let (mut x0, mut y0, mut x1, mut y1) = (i32::MAX, i32::MAX, i32::MIN, i32::MIN);
while i < queue.len() {
let (x, y) = queue[i];
x0 = x0.min(x);
x1 = x1.max(x);
y0 = y0.min(y);
y1 = y1.max(y);
for dy in -1..=1 {
for dx in -1..=1 {
let n = (x + dx, y + dy);
if remaining.remove(&n) {
queue.push(n);
}
}
}
i += 1;
}
out.push(Component {
x0: x0 * size,
y0: y0 * size,
width: (x1 - x0 + 1) * size,
height: (y1 - y0 + 1) * size,
blocks: queue,
});
}
out
}
fn rect_union(a: Rect, b: Rect) -> Rect {
if a.width <= 0.0 || a.height <= 0.0 {
return b;
}
let (x, y) = (a.x.min(b.x), a.y.min(b.y));
Rect {
x,
y,
width: (a.x + a.width).max(b.x + b.width) - x,
height: (a.y + a.height).max(b.y + b.height) - y,
}
}
fn rect_intersect(a: Rect, b: Rect) -> Rect {
let (x, y) = (a.x.max(b.x), a.y.max(b.y));
Rect {
x,
y,
width: ((a.x + a.width).min(b.x + b.width) - x).max(0.0),
height: ((a.y + a.height).min(b.y + b.height) - y).max(0.0),
}
}
fn persisted_blocks(keys: &HashSet<(i32, i32)>) -> Vec<(i32, i32)> {
let mut v: Vec<(i32, i32)> = keys.iter().copied().collect();
v.sort_by_key(|&(bx, by)| (by, bx));
v
}
#[derive(Clone, Debug, PartialEq)]
pub struct TemporalRecord {
pub id: String,
pub rect: Rect,
pub keys: HashSet<(i32, i32)>,
pub chosen_frame: u32,
pub chosen_time: f64,
pub complete: bool,
pub revisions: u32,
}
#[derive(Default, Clone)]
struct OrderedIds {
order: Vec<String>,
}
impl OrderedIds {
fn insert(&mut self, id: &str) {
if !self.order.iter().any(|x| x == id) {
self.order.push(id.to_string());
}
}
fn remove(&mut self, id: &str) {
if let Some(pos) = self.order.iter().position(|x| x == id) {
self.order.remove(pos);
}
}
fn take(&mut self) -> Vec<String> {
std::mem::take(&mut self.order)
}
}
#[derive(Default)]
struct RecordMap {
order: Vec<String>,
map: HashMap<String, TemporalRecord>,
}
impl RecordMap {
fn get(&self, id: &str) -> Option<&TemporalRecord> {
self.map.get(id)
}
fn set(&mut self, record: TemporalRecord) {
if !self.map.contains_key(&record.id) {
self.order.push(record.id.clone());
}
self.map.insert(record.id.clone(), record);
}
fn delete(&mut self, id: &str) {
if self.map.remove(id).is_some() {
if let Some(pos) = self.order.iter().position(|x| x == id) {
self.order.remove(pos);
}
}
}
fn values_in_order(&self) -> impl Iterator<Item = &TemporalRecord> {
self.order.iter().map(move |id| &self.map[id])
}
}
struct PendingResolve {
record: TemporalRecord,
old_existed: bool,
expanded: bool,
choose: bool,
olds_to_delete: Vec<String>,
rect: Rect,
write_blocks: Vec<(i32, i32)>,
frame: u32,
time: f64,
}
#[derive(Default)]
pub struct TemporalIndex {
records: RecordMap,
dirty: OrderedIds,
deleted: OrderedIds,
pending: Option<PendingResolve>,
}
pub struct CommitOut {
pub chosen: bool,
pub emit_incomplete: bool,
pub rect: Rect,
}
impl TemporalIndex {
pub fn new() -> Self {
Self::default()
}
pub fn load(&mut self, record: TemporalRecord) {
self.records.set(record);
}
#[allow(clippy::too_many_arguments)]
pub fn decide(
&mut self,
comp_bounds: Rect,
comp_blocks: &[(i32, i32)],
frame: u32,
time: f64,
visible: Rect,
latest_policy: bool,
mut next_seq: u64,
) -> (usize, u64) {
let mut olds: Vec<String> = Vec::new();
for rec in self.records.values_in_order() {
let expanded_rect = Rect {
x: rec.rect.x - 16.0,
y: rec.rect.y - 16.0,
width: rec.rect.width + 32.0,
height: rec.rect.height + 32.0,
};
let ix = rect_intersect(expanded_rect, comp_bounds);
if ix.width > 0.0 && ix.height > 0.0 {
olds.push(rec.id.clone());
}
}
let old_id = olds.first().cloned();
let mut rect = comp_bounds;
let mut previous_size: usize = 0;
let mut largest_id: Option<String> = None;
let mut largest_size: usize = 0;
for id in &olds {
let rec = self.records.get(id).unwrap();
rect = rect_union(rect, rec.rect);
previous_size += rec.keys.len();
if largest_id.is_none() || rec.keys.len() > largest_size {
largest_size = rec.keys.len();
largest_id = Some(id.clone());
}
}
let mut mask: HashSet<(i32, i32)> = largest_id
.as_ref()
.map(|id| self.records.get(id).unwrap().keys.clone())
.unwrap_or_default();
for id in &olds {
if Some(id) != largest_id.as_ref() {
for k in &self.records.get(id).unwrap().keys {
mask.insert(*k);
}
}
}
for k in comp_blocks {
mask.insert(*k);
}
let old_rec = old_id.as_ref().and_then(|id| self.records.get(id)).cloned();
let geometry_changed = match &old_rec {
None => true,
Some(o) => {
rect.x != o.rect.x
|| rect.y != o.rect.y
|| rect.width != o.rect.width
|| rect.height != o.rect.height
}
};
let membership_changed = match &old_rec {
None => true,
Some(_) => mask.len() != previous_size,
};
let expanded = geometry_changed || membership_changed;
let mut record = match &old_rec {
Some(o) => TemporalRecord {
id: o.id.clone(),
rect,
keys: mask.clone(),
chosen_frame: o.chosen_frame,
chosen_time: o.chosen_time,
complete: o.complete,
revisions: o.revisions,
},
None => {
let id = format!("{:010}", next_seq);
next_seq += 1;
TemporalRecord {
id,
rect,
keys: mask.clone(),
chosen_frame: frame,
chosen_time: time,
complete: false,
revisions: 0,
}
}
};
if expanded {
record.complete = false;
}
let iv = rect_intersect(rect, visible);
let complete_visible = iv.width == rect.width && iv.height == rect.height;
let choose = complete_visible
&& (old_rec.is_none()
|| !old_rec.as_ref().unwrap().complete
|| expanded
|| latest_policy);
let write_blocks = if choose {
persisted_blocks(&mask)
} else {
Vec::new()
};
let write_block_count = write_blocks.len();
let olds_to_delete: Vec<String> = olds.into_iter().filter(|id| *id != record.id).collect();
self.pending = Some(PendingResolve {
record,
old_existed: old_rec.is_some(),
expanded,
choose,
olds_to_delete,
rect,
write_blocks,
frame,
time,
});
(write_block_count, next_seq)
}
pub fn pending_write_blocks(&self) -> &[(i32, i32)] {
self.pending
.as_ref()
.map(|p| p.write_blocks.as_slice())
.unwrap_or(&[])
}
pub fn commit(&mut self, mask_complete_ok: bool) -> Option<CommitOut> {
let mut pending = self.pending.take()?;
let chosen = pending.choose && mask_complete_ok;
if chosen {
pending.record.chosen_frame = pending.frame;
pending.record.chosen_time = pending.time;
pending.record.complete = true;
pending.record.revisions += 1;
}
let emit_incomplete =
!pending.record.complete && (!pending.old_existed || pending.expanded);
let rect = pending.rect;
let record_id = pending.record.id.clone();
self.records.set(pending.record);
self.dirty.insert(&record_id);
self.deleted.remove(&record_id);
for id in &pending.olds_to_delete {
self.records.delete(id);
self.dirty.remove(id);
self.deleted.insert(id);
}
Some(CommitOut {
chosen,
emit_incomplete,
rect,
})
}
pub fn flush_sizes(&self) -> (usize, usize, usize) {
let dirty_block_total: usize = self
.dirty
.order
.iter()
.map(|id| self.records.get(id).map(|r| r.keys.len()).unwrap_or(0))
.sum();
(
self.deleted.order.len(),
self.dirty.order.len(),
dirty_block_total,
)
}
pub fn flush_take(&mut self) -> (Vec<String>, Vec<DirtyRow>) {
let deleted = self.deleted.take();
let dirty_ids = self.dirty.take();
let dirty = dirty_ids
.into_iter()
.map(|id| {
let rec = self.records.get(&id).unwrap().clone();
let blocks = persisted_blocks(&rec.keys);
(rec, blocks)
})
.collect();
(deleted, dirty)
}
}
pub type DirtyRow = (TemporalRecord, Vec<(i32, i32)>);
fn occluded(occlusions: &[Rect], sx: f64, sy: f64) -> bool {
occlusions
.iter()
.any(|o| sx >= o.x && sx < o.x + o.width && sy >= o.y && sy < o.y + o.height)
}
#[allow(clippy::too_many_arguments)]
pub fn mask_complete(
write_blocks: &[(i32, i32)],
labels: &[u8],
atlas_width: i64,
atlas_height: i64,
code: u8,
occlusions: &[Rect],
consistent: Option<&[u8]>,
ox: i64,
oy: i64,
) -> bool {
let b = QUALITY_BLOCK as i64;
for &(bx, by) in write_blocks {
let (bx, by) = (bx as i64, by as i64);
for y in by * b..by * b + b {
let sy = y - oy;
for x in bx * b..bx * b + b {
let sx = x - ox;
let contains = sx >= 0
&& sy >= 0
&& sx < atlas_width
&& sy < atlas_height
&& labels[(sy * atlas_width + sx) as usize] == code;
if !contains || occluded(occlusions, sx as f64, sy as f64) {
return false;
}
if let Some(c) = consistent {
if c[(sy * atlas_width + sx) as usize] == 0 {
return false;
}
}
}
}
}
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_block() {
let comps = components(&[(3, 4)], 16);
assert_eq!(comps.len(), 1);
assert_eq!(
(comps[0].x0, comps[0].y0, comps[0].width, comps[0].height),
(48, 64, 16, 16)
);
assert_eq!(comps[0].blocks, vec![(3, 4)]);
}
#[test]
fn l_shape_stays_concave() {
let comps = components(&[(0, 0), (1, 0), (0, 1)], 16);
assert_eq!(comps.len(), 1);
assert_eq!(comps[0].blocks.len(), 3);
assert_eq!((comps[0].width, comps[0].height), (32, 32));
}
#[test]
fn diagonal_touch_connects() {
let comps = components(&[(0, 0), (1, 1)], 16);
assert_eq!(comps.len(), 1);
}
#[test]
fn disjoint_cells_split_and_keep_seed_order() {
let comps = components(&[(0, 0), (10, 10), (0, 1)], 16);
assert_eq!(comps.len(), 2);
assert_eq!(comps[0].blocks.len(), 2);
assert_eq!(comps[1].blocks, vec![(10, 10)]);
}
#[test]
fn overwrite_writes_pixels_and_evidence() {
let size = 32usize;
let blocks = (size / QUALITY_BLOCK).pow(2);
let mut pixels = vec![0u8; size * size * 4];
let mut coverage = vec![0u8; size * size / 8];
let mut provisional = vec![0xffu8; size * size / 8]; let (mut quality, mut conflicts, mut owner, mut frozen) = (
vec![0u8; blocks],
vec![0u8; blocks],
vec![0u32; blocks],
vec![0u8; blocks],
);
let mut tile = OverwriteTile {
size,
pixels: &mut pixels,
coverage: &mut coverage,
provisional: &mut provisional,
quality: &mut quality,
conflicts: &mut conflicts,
owner: &mut owner,
frozen: &mut frozen,
};
let img_width = 32i64;
let rgba: Vec<u8> = (0..img_width * img_width)
.flat_map(|i| [i as u8, 1, 2, 255])
.collect();
let stats = overwrite_tile(
&mut tile,
&rgba,
img_width,
&[(0, 0)],
0,
0,
0,
0,
3,
0.7,
true,
);
assert_eq!(stats.added, 256);
assert_eq!(stats.conflict_pixels, 256);
assert_eq!(stats.provisional_delta, -256);
assert!(stats.changed);
assert_eq!(tile.owner[0], 4);
assert_eq!(tile.quality[0], 179); assert_eq!(tile.conflicts[0], 1);
assert_eq!(tile.frozen[0], 1);
assert_eq!(tile.pixels[0], 0);
assert_eq!(tile.pixels[4], 1);
}
#[test]
fn overwrite_respects_non_stable_policy() {
let size = 16usize;
let mut pixels = vec![0u8; size * size * 4];
let mut coverage = vec![0u8; size * size / 8];
let mut provisional = vec![0u8; size * size / 8];
let (mut quality, mut conflicts, mut owner, mut frozen) =
(vec![0u8; 1], vec![0u8; 1], vec![0u32; 1], vec![0u8; 1]);
let mut tile = OverwriteTile {
size,
pixels: &mut pixels,
coverage: &mut coverage,
provisional: &mut provisional,
quality: &mut quality,
conflicts: &mut conflicts,
owner: &mut owner,
frozen: &mut frozen,
};
let rgba = vec![9u8; 16 * 16 * 4];
overwrite_tile(&mut tile, &rgba, 16, &[(0, 0)], 0, 0, 0, 0, 0, 0.0, false);
assert_eq!(tile.frozen[0], 0);
assert_eq!(tile.quality[0], 0); }
fn block_rect(bx: i32, by: i32, w: i32, h: i32) -> Rect {
Rect {
x: (bx * 16) as f64,
y: (by * 16) as f64,
width: (w * 16) as f64,
height: (h * 16) as f64,
}
}
fn keys(blocks: &[(i32, i32)]) -> HashSet<(i32, i32)> {
blocks.iter().copied().collect()
}
fn resolve(
index: &mut TemporalIndex,
comp_blocks: &[(i32, i32)],
frame: u32,
visible: Rect,
seq: &mut u64,
) -> CommitOut {
let bounds = comp_blocks
.iter()
.fold(None::<Rect>, |acc, &(bx, by)| {
let r = block_rect(bx, by, 1, 1);
Some(acc.map_or(r, |a| rect_union(a, r)))
})
.unwrap();
let (_, next_seq) = index.decide(
bounds,
comp_blocks,
frame,
frame as f64,
visible,
false,
*seq,
);
*seq = next_seq;
index.commit(true).unwrap()
}
#[test]
fn flush_order_survives_a_delete_then_reinsert_shaped_sequence() {
let visible = block_rect(0, 0, 13, 1);
let mut index = TemporalIndex::new();
let mut seq = 0u64;
for &b in &[0, 4, 8, 12] {
resolve(&mut index, &[(b, 0)], b as u32, visible, &mut seq);
}
let ids = |n: u64| format!("{:010}", n);
assert_eq!(index.records.order, vec![ids(0), ids(1), ids(2), ids(3)]);
index.flush_take(); let out_bridge = resolve(
&mut index,
&[(4, 0), (5, 0), (6, 0), (7, 0), (8, 0)],
5,
visible,
&mut seq,
);
assert!(out_bridge.chosen);
index.decide(
block_rect(12, 0, 1, 1),
&[(12, 0)],
5,
5.0,
visible,
false,
seq,
);
assert_eq!(
index.pending_write_blocks().len(),
0,
"D neither expands nor re-chooses — decide() must not offer a mask to check"
);
let out_d = index.commit(false).unwrap();
assert!(!out_d.chosen);
assert_eq!(index.dirty.order, vec![ids(1), ids(3)]);
assert_eq!(index.deleted.order, vec![ids(2)]);
let all_blocks: Vec<(i32, i32)> = (0..=12).map(|bx| (bx, 0)).collect();
let out_mega = resolve(&mut index, &all_blocks, 6, visible, &mut seq);
assert!(out_mega.chosen);
assert_eq!(index.dirty.order, vec![ids(0)], "B and D — both still dirty and never flushed — are deleted before ever reaching a flush");
assert_eq!(index.deleted.order, vec![ids(2), ids(1), ids(3)]);
let (deleted, dirty) = index.flush_take();
assert_eq!(deleted, vec![ids(2), ids(1), ids(3)]);
assert_eq!(dirty.len(), 1);
assert_eq!(dirty[0].0.id, ids(0));
assert_eq!(dirty[0].1.len(), 13);
assert_eq!(index.dirty.order.len(), 0);
assert_eq!(index.deleted.order.len(), 0);
}
#[test]
fn fresh_id_colliding_with_a_live_record_overwrites_it_in_place() {
let visible = block_rect(0, 0, 6, 1);
let mut index = TemporalIndex::new();
index.load(TemporalRecord {
id: "0000000000".into(),
rect: block_rect(5, 0, 1, 1),
keys: keys(&[(5, 0)]),
chosen_frame: 0,
chosen_time: 0.0,
complete: true,
revisions: 1,
});
let mut seq = 0u64;
let out = resolve(&mut index, &[(0, 0)], 1, visible, &mut seq);
assert!(out.chosen);
assert_eq!(
index.records.order,
vec!["0000000000".to_string()],
"no second row — the id collision clobbers the loaded one in place"
);
assert_eq!(
index.records.get("0000000000").unwrap().rect,
block_rect(0, 0, 1, 1)
);
assert_eq!(index.deleted.order.len(), 0);
}
#[test]
fn fresh_id_colliding_with_a_deleted_id_is_appended_and_undeletes_it() {
let visible = block_rect(0, 0, 12, 1);
let mut index = TemporalIndex::new();
index.load(TemporalRecord {
id: "0000000000".into(),
rect: block_rect(0, 0, 1, 1),
keys: keys(&[(0, 0)]),
chosen_frame: 0,
chosen_time: 0.0,
complete: true,
revisions: 1,
});
index.load(TemporalRecord {
id: "0000000001".into(),
rect: block_rect(1, 0, 1, 1),
keys: keys(&[(1, 0)]),
chosen_frame: 0,
chosen_time: 0.0,
complete: true,
revisions: 1,
});
let mut seq = 0u64;
resolve(&mut index, &[(0, 0), (1, 0)], 1, visible, &mut seq);
assert_eq!(index.deleted.order, vec!["0000000001".to_string()]);
resolve(&mut index, &[(5, 0)], 2, visible, &mut seq);
resolve(&mut index, &[(9, 0)], 2, visible, &mut seq);
assert_eq!(
index.deleted.order.len(),
0,
"no delete() should ever be emitted for \"0000000001\" now"
);
assert_eq!(
index.records.order,
vec!["0000000000".to_string(), "0000000001".to_string()]
);
assert_eq!(
index.records.get("0000000001").unwrap().rect,
block_rect(9, 0, 1, 1)
);
let (deleted, dirty) = index.flush_take();
assert_eq!(deleted.len(), 0);
assert_eq!(
dirty.iter().map(|(r, _)| r.id.clone()).collect::<Vec<_>>(),
vec!["0000000000".to_string(), "0000000001".to_string()]
);
}
}