use rustc_hash::FxHashMap;
const BLOCK: usize = 512;
#[derive(Clone, Debug)]
pub(crate) struct DynHeightIndex {
width_bucket: u32,
estimated_row_height: f32,
base: usize,
heights: Vec<f32>,
keys: Vec<String>,
block_sums: Vec<f32>,
live_total: f32,
base_prefix: f32,
key_to_phys: FxHashMap<String, usize>,
}
impl DynHeightIndex {
pub(crate) fn count(&self) -> usize {
self.heights.len() - self.base
}
pub(crate) fn height(&self, i: usize) -> f32 {
self.heights[self.base + i]
}
pub(crate) fn heights_sum(&self) -> f32 {
self.live_total
}
pub(crate) fn row_top(&self, i: usize, gap: f32) -> f32 {
self.prefix_live(i) + i as f32 * gap
}
pub(crate) fn index_for_key(&self, key: &str) -> Option<usize> {
self.key_to_phys.get(key).map(|phys| phys - self.base)
}
fn prefix_phys(&self, p: usize) -> f32 {
let full_blocks = p / BLOCK;
let mut sum = 0.0;
for b in 0..full_blocks {
sum += self.block_sums[b];
}
for h in &self.heights[full_blocks * BLOCK..p] {
sum += *h;
}
sum
}
fn prefix_live(&self, i: usize) -> f32 {
self.prefix_phys(self.base + i) - self.base_prefix
}
pub(crate) fn visible_range(
&self,
gap: f32,
offset: f32,
viewport_h: f32,
) -> (usize, f32, usize) {
let count = self.count();
let mut lo = 0;
let mut hi = count;
while lo < hi {
let mid = lo + (hi - lo) / 2;
let bottom = self.row_top(mid, gap) + self.height(mid);
if bottom <= offset {
lo = mid + 1;
} else {
hi = mid;
}
}
let start = lo;
if start >= count {
return (count, self.row_top(count, gap), count);
}
let start_y = self.row_top(start, gap);
let mut end = start;
let mut cursor = start_y;
let viewport_bottom = offset + viewport_h;
while end < count && cursor < viewport_bottom {
cursor += self.height(end) + gap;
end += 1;
}
(start, start_y, end)
}
pub(crate) fn build(
width_bucket: u32,
estimated_row_height: f32,
count: usize,
mut row: impl FnMut(usize) -> (String, f32),
) -> Self {
let mut heights = Vec::with_capacity(count);
let mut keys = Vec::with_capacity(count);
let mut key_to_phys = FxHashMap::with_capacity_and_hasher(count, Default::default());
let mut live_total = 0.0;
for i in 0..count {
let (key, h) = row(i);
key_to_phys.insert(key.clone(), i);
keys.push(key);
heights.push(h);
live_total += h;
}
let block_sums = Self::build_block_sums(&heights);
DynHeightIndex {
width_bucket,
estimated_row_height,
base: 0,
heights,
keys,
block_sums,
live_total,
base_prefix: 0.0,
key_to_phys,
}
}
fn build_block_sums(heights: &[f32]) -> Vec<f32> {
let n_blocks = heights.len().div_ceil(BLOCK).max(1);
let mut sums = vec![0.0; n_blocks];
for (i, h) in heights.iter().enumerate() {
sums[i / BLOCK] += *h;
}
sums
}
pub(crate) fn set_height(&mut self, i: usize, h: f32) {
let phys = self.base + i;
let delta = h - self.heights[phys];
if delta == 0.0 {
return;
}
self.heights[phys] = h;
self.block_sums[phys / BLOCK] += delta;
self.live_total += delta;
}
fn push(&mut self, key: String, h: f32) {
let phys = self.heights.len();
let block = phys / BLOCK;
if block == self.block_sums.len() {
self.block_sums.push(0.0);
}
self.block_sums[block] += h;
self.live_total += h;
self.key_to_phys.insert(key.clone(), phys);
self.keys.push(key);
self.heights.push(h);
}
fn trim_front(&mut self, t: usize, trimmed: &mut Vec<String>) {
for phys in self.base..self.base + t {
let key = std::mem::take(&mut self.keys[phys]);
self.key_to_phys.remove(&key);
trimmed.push(key);
self.base_prefix += self.heights[phys];
self.live_total -= self.heights[phys];
}
self.base += t;
}
fn maybe_compact(&mut self) {
if self.base <= self.heights.len() / 2 || self.base <= 2 * BLOCK {
return;
}
self.heights.drain(0..self.base);
self.keys.drain(0..self.base);
for phys in self.key_to_phys.values_mut() {
*phys -= self.base;
}
self.block_sums = Self::build_block_sums(&self.heights);
self.base_prefix = 0.0;
self.base = 0;
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn reconcile(
&mut self,
width_bucket: u32,
estimated_row_height: f32,
count: usize,
head_key: &str,
mut key_of: impl FnMut(usize) -> String,
mut height_of: impl FnMut(usize, &str) -> f32,
trimmed: &mut Vec<String>,
) -> bool {
if width_bucket != self.width_bucket
|| estimated_row_height != self.estimated_row_height
|| self.count() == 0
|| count == 0
{
return false;
}
let old_count = self.count();
let Some(&head_phys) = self.key_to_phys.get(head_key) else {
return false;
};
let t = head_phys - self.base;
let surviving = old_count - t;
if count < surviving {
debug_assert!(
false,
"append-only virtual list lost tail rows (count {count} < surviving \
prefix {surviving}); reconcile falling back to rebuild"
);
return false;
}
let appended = count - surviving;
#[cfg(debug_assertions)]
if surviving > 0 {
let mid = t + (surviving - 1) / 2;
debug_assert_eq!(
self.keys[self.base + mid],
key_of(mid - t),
"append-only virtual list reordered a surviving row; reconcile invalid"
);
debug_assert_eq!(
self.keys[self.base + old_count - 1],
key_of(surviving - 1),
"append-only virtual list mutated its tail before the append point"
);
}
self.trim_front(t, trimmed);
self.maybe_compact();
for j in 0..appended {
let logical = surviving + j;
let key = key_of(logical);
let h = height_of(logical, &key);
self.push(key, h);
}
true
}
}
#[cfg(test)]
mod tests {
use super::*;
struct Oracle {
rows: Vec<(String, f32)>,
}
impl Oracle {
fn row_top(&self, i: usize, gap: f32) -> f32 {
self.rows[..i].iter().map(|(_, h)| *h).sum::<f32>() + i as f32 * gap
}
fn heights_sum(&self) -> f32 {
self.rows.iter().map(|(_, h)| *h).sum()
}
fn visible_range(&self, gap: f32, offset: f32, vh: f32) -> (usize, f32, usize) {
let count = self.rows.len();
let mut start = 0;
let mut y = 0.0_f32;
while start < count {
let h = self.rows[start].1;
if y + h > offset {
break;
}
y += h + gap;
start += 1;
}
let mut end = start;
let mut cursor = y;
let bottom = offset + vh;
while end < count && cursor < bottom {
cursor += self.rows[end].1 + gap;
end += 1;
}
(start, y, end)
}
}
struct Lcg(u64);
impl Lcg {
fn next(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
self.0 >> 16
}
fn below(&mut self, n: u64) -> u64 {
self.next() % n
}
}
fn assert_matches(idx: &DynHeightIndex, oracle: &Oracle, gap: f32) {
assert_eq!(idx.count(), oracle.rows.len(), "count");
assert_eq!(idx.heights_sum(), oracle.heights_sum(), "heights_sum");
for i in 0..oracle.rows.len() {
assert_eq!(idx.height(i), oracle.rows[i].1, "height[{i}]");
assert_eq!(idx.row_top(i, gap), oracle.row_top(i, gap), "row_top[{i}]");
assert_eq!(
idx.index_for_key(&oracle.rows[i].0),
Some(i),
"index_for_key[{i}]"
);
}
let total = oracle.heights_sum() + gap * oracle.rows.len().saturating_sub(1) as f32;
for step in 0..20 {
let offset = total * step as f32 / 19.0 - 50.0;
assert_eq!(
idx.visible_range(gap, offset, 300.0),
oracle.visible_range(gap, offset, 300.0),
"visible_range at offset {offset}"
);
}
}
fn build_pair(count: usize, est: f32, gap: f32) -> (DynHeightIndex, Oracle) {
let mut rng = Lcg(0xC0FFEE);
let mut rows = Vec::new();
for i in 0..count {
let h = if rng.below(3) == 0 {
est
} else {
(10 + rng.below(90)) as f32
};
rows.push((format!("k{i}"), h));
}
let oracle = Oracle { rows: rows.clone() };
let idx = DynHeightIndex::build(7, est, count, |i| rows[i].clone());
let _ = gap;
(idx, oracle)
}
#[test]
fn cold_build_matches_oracle_across_blocks() {
let gap = 4.0;
let (idx, oracle) = build_pair(2000, 20.0, gap);
assert_matches(&idx, &oracle, gap);
}
#[test]
fn set_height_matches_oracle() {
let gap = 3.0;
let (mut idx, mut oracle) = build_pair(1500, 20.0, gap);
let mut rng = Lcg(42);
for _ in 0..400 {
let i = rng.below(oracle.rows.len() as u64) as usize;
let h = (10 + rng.below(120)) as f32;
idx.set_height(i, h);
oracle.rows[i].1 = h;
}
assert_matches(&idx, &oracle, gap);
}
#[test]
fn reconcile_trim_then_append_matches_oracle() {
let gap = 5.0;
let est = 20.0;
let (mut idx, mut oracle) = build_pair(3000, est, gap);
let mut rng = Lcg(0xABCD);
let mut next_key = 3000usize;
for round in 0..60 {
let trim = rng.below(40) as usize;
let trim = trim.min(oracle.rows.len().saturating_sub(1));
let append = rng.below(40) as usize;
let mut next: Vec<(String, f32)> = oracle.rows[trim..].to_vec();
for _ in 0..append {
let h = if rng.below(3) == 0 {
est
} else {
(10 + rng.below(90)) as f32
};
next.push((format!("k{next_key}"), h));
next_key += 1;
}
let count = next.len();
let head_key = next[0].0.clone();
let next_for_key = next.clone();
let next_for_h = next.clone();
let mut trimmed = Vec::new();
let ok = idx.reconcile(
7,
est,
count,
&head_key,
|i| next_for_key[i].0.clone(),
|i, _k| next_for_h[i].1,
&mut trimmed,
);
assert!(
ok,
"reconcile should succeed for trim-then-append (round {round})"
);
assert_eq!(
trimmed,
oracle.rows[..trim]
.iter()
.map(|(k, _)| k.clone())
.collect::<Vec<_>>(),
"trimmed keys (round {round})"
);
oracle.rows = next;
for _ in 0..rng.below(5) {
if oracle.rows.is_empty() {
break;
}
let i = rng.below(oracle.rows.len() as u64) as usize;
let h = (10 + rng.below(120)) as f32;
idx.set_height(i, h);
oracle.rows[i].1 = h;
}
assert_matches(&idx, &oracle, gap);
}
assert!(
idx.base <= idx.heights.len() / 2 || idx.base <= 2 * BLOCK,
"compaction kept the dead prefix bounded: base {} vs phys {}",
idx.base,
idx.heights.len()
);
}
#[test]
fn reconcile_trim_dominated_compacts_and_stays_correct() {
let gap = 4.0;
let est = 20.0;
let (mut idx, mut oracle) = build_pair(3000, est, gap);
let mut rng = Lcg(0x5EED);
let mut next_key = 3000usize;
let mut compacted = false;
for _ in 0..35 {
let trim = 60usize.min(oracle.rows.len().saturating_sub(1));
let append = 5usize;
let before_base = idx.base;
let mut next: Vec<(String, f32)> = oracle.rows[trim..].to_vec();
for _ in 0..append {
next.push((format!("k{next_key}"), (10 + rng.below(90)) as f32));
next_key += 1;
}
let head = next[0].0.clone();
let nfk = next.clone();
let nfh = next.clone();
assert!(idx.reconcile(
7,
est,
next.len(),
&head,
|i| nfk[i].0.clone(),
|i, _| nfh[i].1,
&mut Vec::new()
));
oracle.rows = next;
if idx.base < before_base {
compacted = true;
}
assert_matches(&idx, &oracle, gap);
}
assert!(compacted, "trim-dominated feed should trigger compaction");
}
#[test]
fn reconcile_rejects_width_change() {
let (mut idx, _oracle) = build_pair(100, 20.0, 4.0);
let ok = idx.reconcile(
99,
20.0,
100,
"k0",
|i| format!("k{i}"),
|_, _| 20.0,
&mut Vec::new(),
);
assert!(!ok, "width-bucket change must force a cold rebuild");
}
#[test]
fn reconcile_rejects_unknown_head() {
let (mut idx, _oracle) = build_pair(100, 20.0, 4.0);
let ok = idx.reconcile(
7,
20.0,
100,
"stranger",
|i| format!("z{i}"),
|_, _| 20.0,
&mut Vec::new(),
);
assert!(!ok, "unknown head key must force a cold rebuild");
}
#[test]
fn reconcile_pure_append_no_trim() {
let gap = 2.0;
let est = 15.0;
let (mut idx, mut oracle) = build_pair(50, est, gap);
let mut next = oracle.rows.clone();
for i in 50..70 {
next.push((format!("k{i}"), (i % 30 + 10) as f32));
}
let head = next[0].0.clone();
let nfk = next.clone();
let nfh = next.clone();
assert!(idx.reconcile(
7,
est,
next.len(),
&head,
|i| nfk[i].0.clone(),
|i, _| nfh[i].1,
&mut Vec::new()
));
oracle.rows = next;
assert_matches(&idx, &oracle, gap);
}
}