#[derive(Debug, Clone)]
pub struct CutRowMap {
slot_to_row: Vec<Option<usize>>,
row_to_slot: Vec<Option<usize>>,
is_active: Vec<bool>,
base_row_offset: usize,
next_row: usize,
active_count: usize,
}
impl CutRowMap {
#[must_use]
pub fn new(pool_capacity: usize, base_row_offset: usize) -> Self {
Self {
slot_to_row: vec![None; pool_capacity],
row_to_slot: Vec::new(),
is_active: vec![false; pool_capacity],
base_row_offset,
next_row: base_row_offset,
active_count: 0,
}
}
pub fn insert(&mut self, slot: usize) -> usize {
debug_assert!(
slot < self.slot_to_row.len(),
"CutRowMap::insert: slot {slot} >= pool_capacity {}",
self.slot_to_row.len()
);
debug_assert!(
self.slot_to_row[slot].is_none(),
"CutRowMap::insert: slot {slot} is already mapped to LP row {:?}",
self.slot_to_row[slot]
);
let lp_row = self.next_row;
self.slot_to_row[slot] = Some(lp_row);
self.is_active[slot] = true;
let relative = lp_row - self.base_row_offset;
if relative >= self.row_to_slot.len() {
self.row_to_slot.resize(relative + 1, None);
}
self.row_to_slot[relative] = Some(slot);
self.next_row += 1;
self.active_count += 1;
lp_row
}
pub fn deactivate(&mut self, slot: usize) {
debug_assert!(
slot < self.slot_to_row.len() && self.slot_to_row[slot].is_some(),
"CutRowMap::deactivate: slot {slot} is not mapped"
);
if self.is_active[slot] {
self.is_active[slot] = false;
self.active_count -= 1;
}
}
#[must_use]
#[inline]
pub fn lp_row_for_slot(&self, slot: usize) -> Option<usize> {
self.slot_to_row.get(slot).copied().flatten()
}
#[must_use]
#[inline]
pub fn slot_for_lp_row(&self, lp_row: usize) -> Option<usize> {
if lp_row < self.base_row_offset {
return None;
}
let relative = lp_row - self.base_row_offset;
self.row_to_slot.get(relative).copied().flatten()
}
#[must_use]
#[inline]
pub fn total_cut_rows(&self) -> usize {
self.next_row - self.base_row_offset
}
#[must_use]
#[inline]
pub fn active_count(&self) -> usize {
self.active_count
}
#[must_use]
#[inline]
pub fn base_row_offset(&self) -> usize {
self.base_row_offset
}
#[must_use]
#[inline]
pub fn next_row(&self) -> usize {
self.next_row
}
#[must_use]
#[inline]
pub fn is_slot_active(&self, slot: usize) -> bool {
slot < self.is_active.len() && self.is_active[slot]
}
pub fn reset(&mut self, new_base_row_offset: usize) {
self.slot_to_row.fill(None);
self.row_to_slot.clear();
self.is_active.fill(false);
self.base_row_offset = new_base_row_offset;
self.next_row = new_base_row_offset;
self.active_count = 0;
}
}
#[cfg(test)]
mod tests {
use super::CutRowMap;
#[test]
fn new_creates_empty_map() {
let map = CutRowMap::new(100, 50);
assert_eq!(map.base_row_offset(), 50);
assert_eq!(map.next_row(), 50);
assert_eq!(map.active_count(), 0);
assert_eq!(map.total_cut_rows(), 0);
}
#[test]
fn new_zero_capacity_is_valid() {
let map = CutRowMap::new(0, 10);
assert_eq!(map.total_cut_rows(), 0);
assert_eq!(map.active_count(), 0);
}
#[test]
fn insert_assigns_sequential_rows_from_base_offset() {
let mut map = CutRowMap::new(100, 50);
let r0 = map.insert(7);
assert_eq!(r0, 50);
let r1 = map.insert(3);
assert_eq!(r1, 51);
let r2 = map.insert(42);
assert_eq!(r2, 52);
assert_eq!(map.next_row(), 53);
assert_eq!(map.total_cut_rows(), 3);
assert_eq!(map.active_count(), 3);
}
#[test]
fn insert_updates_both_mappings() {
let mut map = CutRowMap::new(100, 50);
map.insert(7);
assert_eq!(map.lp_row_for_slot(7), Some(50));
assert_eq!(map.slot_for_lp_row(50), Some(7));
}
#[test]
fn lp_row_for_slot_returns_none_for_unmapped_slot() {
let map = CutRowMap::new(100, 50);
assert_eq!(map.lp_row_for_slot(7), None);
}
#[test]
fn lp_row_for_slot_returns_none_for_out_of_range() {
let map = CutRowMap::new(10, 50);
assert_eq!(map.lp_row_for_slot(999), None);
}
#[test]
fn slot_for_lp_row_returns_none_for_template_rows() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
assert_eq!(map.slot_for_lp_row(0), None);
assert_eq!(map.slot_for_lp_row(49), None);
}
#[test]
fn slot_for_lp_row_returns_none_for_unmapped_cut_row() {
let mut map = CutRowMap::new(100, 50);
map.insert(0); assert_eq!(map.slot_for_lp_row(51), None);
}
#[test]
fn deactivate_decrements_active_count() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
map.insert(1);
assert_eq!(map.active_count(), 2);
map.deactivate(0);
assert_eq!(map.active_count(), 1);
assert_eq!(map.total_cut_rows(), 2); }
#[test]
fn deactivate_preserves_row_mapping() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
let row = map.lp_row_for_slot(0);
map.deactivate(0);
assert_eq!(map.lp_row_for_slot(0), row);
assert_eq!(map.slot_for_lp_row(row.expect("must be Some")), Some(0));
}
#[test]
fn deactivate_is_idempotent() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
map.deactivate(0);
assert_eq!(map.active_count(), 0);
map.deactivate(0);
assert_eq!(map.active_count(), 0);
}
#[test]
fn is_slot_active_reflects_state() {
let mut map = CutRowMap::new(100, 50);
assert!(!map.is_slot_active(0));
map.insert(0);
assert!(map.is_slot_active(0));
map.deactivate(0);
assert!(!map.is_slot_active(0));
}
#[test]
fn is_slot_active_returns_false_for_out_of_range() {
let map = CutRowMap::new(10, 50);
assert!(!map.is_slot_active(999));
}
#[test]
fn reset_clears_all_state() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
map.insert(1);
map.insert(2);
map.deactivate(1);
map.reset(60);
assert_eq!(map.total_cut_rows(), 0);
assert_eq!(map.active_count(), 0);
assert_eq!(map.next_row(), 60);
assert_eq!(map.base_row_offset(), 60);
assert_eq!(map.lp_row_for_slot(0), None);
assert_eq!(map.lp_row_for_slot(1), None);
assert_eq!(map.lp_row_for_slot(2), None);
assert!(!map.is_slot_active(0));
}
#[test]
fn reset_allows_reinsertion() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
assert_eq!(map.lp_row_for_slot(0), Some(50));
map.reset(60);
let row = map.insert(0);
assert_eq!(row, 60);
assert_eq!(map.lp_row_for_slot(0), Some(60));
}
#[test]
fn multiple_inserts_and_deactivations() {
let mut map = CutRowMap::new(100, 10);
map.insert(5);
map.insert(10);
map.insert(15);
assert_eq!(map.active_count(), 3);
assert_eq!(map.total_cut_rows(), 3);
map.deactivate(10);
assert_eq!(map.active_count(), 2);
assert_eq!(map.total_cut_rows(), 3);
let row = map.insert(20);
assert_eq!(row, 13);
assert_eq!(map.active_count(), 3);
assert_eq!(map.total_cut_rows(), 4);
assert_eq!(map.lp_row_for_slot(5), Some(10));
assert_eq!(map.lp_row_for_slot(10), Some(11)); assert_eq!(map.lp_row_for_slot(15), Some(12));
assert_eq!(map.lp_row_for_slot(20), Some(13));
assert_eq!(map.slot_for_lp_row(10), Some(5));
assert_eq!(map.slot_for_lp_row(11), Some(10));
assert_eq!(map.slot_for_lp_row(12), Some(15));
assert_eq!(map.slot_for_lp_row(13), Some(20));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "already mapped")]
fn insert_same_slot_twice_panics_in_debug() {
let mut map = CutRowMap::new(100, 50);
map.insert(0);
map.insert(0); }
#[test]
fn cut_row_map_derives_debug_and_clone() {
let mut map = CutRowMap::new(10, 5);
map.insert(0);
map.insert(3);
let cloned = map.clone();
assert_eq!(cloned.active_count(), 2);
assert_eq!(cloned.lp_row_for_slot(0), Some(5));
assert_eq!(cloned.lp_row_for_slot(3), Some(6));
let debug_str = format!("{map:?}");
assert!(!debug_str.is_empty());
}
#[test]
fn base_row_offset_zero_works() {
let mut map = CutRowMap::new(10, 0);
let row = map.insert(0);
assert_eq!(row, 0);
assert_eq!(map.slot_for_lp_row(0), Some(0));
}
}