use super::*;
pub(crate) fn lis_indices<T: Ord>(keys: &[T]) -> Vec<usize> {
let n: usize = keys.len();
if n == 0 {
return Vec::new();
}
let mut tails: Vec<usize> = Vec::with_capacity(n);
let mut tail_min: Vec<&T> = Vec::with_capacity(n);
let mut predecessors: Vec<usize> = vec![0_usize; n];
for (i, key) in keys.iter().enumerate() {
let pos: usize = match tail_min.binary_search(&key) {
Ok(idx) => idx,
Err(idx) => idx,
};
if pos == tails.len() {
tails.push(i);
tail_min.push(key);
} else {
tails[pos] = i;
tail_min[pos] = key;
}
predecessors[i] = if pos == 0 { usize::MAX } else { tails[pos - 1] };
}
let mut result: Vec<usize> = Vec::with_capacity(tails.len());
let mut k: usize = match tails.last() {
Some(last) => *last,
None => return result,
};
while k != usize::MAX {
result.push(k);
match predecessors.get(k) {
Some(&next) if next != usize::MAX => k = next,
_ => break,
}
}
result.reverse();
result
}
pub(crate) fn cached_document() -> Option<Document> {
DOCUMENT_CACHE.with(|cell: &UnsafeCell<Option<Document>>| {
let cached_ptr: *mut Option<Document> = cell.get();
unsafe {
if let Some(doc) = &*cached_ptr {
return Some(doc.clone());
}
}
let window_value: Window = window()?;
let document: Document = window_value.document()?;
DOCUMENT_CACHE.with(|cell: &UnsafeCell<Option<Document>>| unsafe {
*cell.get() = Some(document.clone());
});
Some(document)
})
}
pub(crate) fn append_nodes(parent: &Element, nodes: impl IntoIterator<Item = Node>) {
if !parent.is_connected() {
for node in nodes {
let _: Result<Node, JsValue> = parent.append_child(&node);
}
return;
}
let mut iter = nodes.into_iter();
let Some(first) = iter.next() else {
return;
};
let Some(second) = iter.next() else {
let _: Result<Node, JsValue> = parent.append_child(&first);
return;
};
let document: Document = match cached_document() {
Some(doc) => doc,
None => {
let _: Result<Node, JsValue> = parent.append_child(&first);
let _: Result<Node, JsValue> = parent.append_child(&second);
for node in iter {
let _: Result<Node, JsValue> = parent.append_child(&node);
}
return;
}
};
let fragment: DocumentFragment = document.create_document_fragment();
let _: Result<Node, JsValue> = fragment.append_child(&first);
let _: Result<Node, JsValue> = fragment.append_child(&second);
for node in iter {
let _: Result<Node, JsValue> = fragment.append_child(&node);
}
let fragment_node: Node = fragment.into();
let _: Result<Node, JsValue> = parent.append_child(&fragment_node);
}
pub(crate) fn compute_child_ops_plan<'a>(
old_keys: &[Option<&'a str>],
new_keys: &[Option<&'a str>],
) -> Vec<ChildOpPlan> {
let old_len: usize = old_keys.len();
let new_len: usize = new_keys.len();
let mut plan: Vec<ChildOpPlan> = Vec::with_capacity(old_len.saturating_add(new_len));
let mut old_key_to_pos: HashMap<&str, usize> = HashMap::with_capacity(old_len);
for (old_index, old_key_opt) in old_keys.iter().enumerate() {
if let Some(key) = old_key_opt.as_deref() {
old_key_to_pos.insert(key, old_index);
}
}
let mut new_key_set: HashSet<&str> = HashSet::with_capacity(new_len);
for new_key_opt in new_keys.iter() {
if let Some(key) = new_key_opt.as_deref() {
new_key_set.insert(key);
}
}
for (old_index, old_key_opt) in old_keys.iter().enumerate() {
match old_key_opt.as_deref() {
Some(key) if new_key_set.contains(key) => {
}
_ => {
plan.push(ChildOpPlan::Remove { old_index });
}
}
}
let mut kept_old_indices: Vec<usize> = Vec::with_capacity(new_len);
let mut kept_pos_for_new: Vec<Option<usize>> = Vec::with_capacity(new_len);
for new_key_opt in new_keys.iter() {
let Some(new_key) = new_key_opt.as_deref() else {
kept_pos_for_new.push(None);
continue;
};
match old_key_to_pos.get(new_key) {
Some(&old_idx) => {
let kept_pos: usize = kept_old_indices.len();
kept_old_indices.push(old_idx);
kept_pos_for_new.push(Some(kept_pos));
}
None => {
kept_pos_for_new.push(None);
}
}
}
let lis: Vec<usize> = lis_indices(&kept_old_indices);
let mut in_lis_at_kept_pos: Vec<bool> = vec![false; kept_old_indices.len()];
for &lis_pos in lis.iter() {
in_lis_at_kept_pos[lis_pos] = true;
}
let mut last_anchor: Option<usize> = None;
for (new_index, _) in new_keys.iter().enumerate() {
match kept_pos_for_new[new_index] {
Some(kept_pos) if in_lis_at_kept_pos[kept_pos] => {
plan.push(ChildOpPlan::Keep { new_index });
last_anchor = Some(new_index);
}
Some(_kept_pos) => {
plan.push(ChildOpPlan::MoveBefore {
new_index,
before: last_anchor.map(|a| a + 1),
});
last_anchor = Some(new_index);
}
None => {
plan.push(ChildOpPlan::InsertBefore {
new_index,
before: last_anchor.map(|a| a + 1),
});
last_anchor = Some(new_index);
}
}
}
plan
}
#[cfg(test)]
mod tests {
use super::*;
fn key(s: &'static str) -> Option<&'static str> {
Some(s)
}
type TestKeyList = Vec<Option<&'static str>>;
fn leak(s: String) -> &'static str {
Box::leak(s.into_boxed_str())
}
#[test]
fn lis_indices_empty() {
let keys: Vec<i32> = vec![];
assert!(lis_indices(&keys).is_empty());
}
#[test]
fn lis_indices_single() {
let keys: Vec<i32> = vec![42];
assert_eq!(lis_indices(&keys), vec![0]);
}
#[test]
fn lis_indices_strictly_increasing() {
let keys: Vec<i32> = vec![1, 2, 3, 4, 5];
assert_eq!(lis_indices(&keys), vec![0, 1, 2, 3, 4]);
}
#[test]
fn lis_indices_strictly_decreasing() {
let keys: Vec<i32> = vec![5, 4, 3, 2, 1];
assert_eq!(lis_indices(&keys), vec![4]);
}
#[test]
fn lis_indices_reorder_abc_to_cba() {
let keys: Vec<i32> = vec![2, 1, 0];
assert_eq!(lis_indices(&keys), vec![2]);
}
#[test]
fn lis_indices_with_duplicates_rightmost() {
let keys: Vec<i32> = vec![1, 1, 1];
assert_eq!(lis_indices(&keys), vec![2]);
}
#[test]
fn lis_indices_reorder_long_partial() {
let keys: Vec<i32> = vec![0, 10, 1, 11, 2, 12, 3, 13, 4, 14];
let lis = lis_indices(&keys);
for w in lis.windows(2) {
assert!(keys[w[0]] < keys[w[1]], "lis not increasing");
assert!(w[0] < w[1], "lis indices not ordered");
}
assert!(lis.len() >= 3, "expected a non-trivial LIS, got {:?}", lis);
}
#[test]
fn lis_indices_overlapping_reorder_debug() {
let keys: Vec<i32> = vec![1, 3, 0, 2];
let lis = lis_indices(&keys);
assert_eq!(lis.len(), 2, "expected LIS length 2, got {:?}", lis);
for w in lis.windows(2) {
assert!(keys[w[0]] < keys[w[1]]);
assert!(w[0] < w[1]);
}
}
#[test]
fn plan_no_change_all_keep() {
let old = vec![key("a"), key("b"), key("c")];
let new = vec![key("a"), key("b"), key("c")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::Keep { new_index: 0 },
ChildOpPlan::Keep { new_index: 1 },
ChildOpPlan::Keep { new_index: 2 },
]
);
}
#[test]
fn plan_insert_at_tail() {
let old = vec![key("a"), key("b")];
let new = vec![key("a"), key("b"), key("c")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::Keep { new_index: 0 },
ChildOpPlan::Keep { new_index: 1 },
ChildOpPlan::InsertBefore {
new_index: 2,
before: Some(2)
},
]
);
}
#[test]
fn plan_insert_at_head() {
let old = vec![key("a"), key("b")];
let new = vec![key("z"), key("a"), key("b")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::InsertBefore {
new_index: 0,
before: None
},
ChildOpPlan::Keep { new_index: 1 },
ChildOpPlan::Keep { new_index: 2 },
]
);
}
#[test]
fn plan_remove_from_tail() {
let old = vec![key("a"), key("b")];
let new = vec![key("a")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::Remove { old_index: 1 },
ChildOpPlan::Keep { new_index: 0 },
]
);
}
#[test]
fn plan_remove_from_head() {
let old = vec![key("a"), key("b")];
let new = vec![key("b")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::Remove { old_index: 0 },
ChildOpPlan::Keep { new_index: 0 },
]
);
}
#[test]
fn plan_swap_two_all_keep() {
let old = vec![key("a"), key("b")];
let new = vec![key("b"), key("a")];
let plan = compute_child_ops_plan(&old, &new);
let moves = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::MoveBefore { .. }))
.count();
let keeps = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::Keep { .. }))
.count();
assert_eq!(moves, 1);
assert_eq!(keeps, 1);
let has_move: bool = plan
.iter()
.any(|op| matches!(op, ChildOpPlan::MoveBefore { .. }));
let has_keep: bool = plan.iter().any(|op| matches!(op, ChildOpPlan::Keep { .. }));
assert!(has_move, "fixture has exactly one MoveBefore");
assert!(has_keep, "fixture has exactly one Keep");
let move_count: usize = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::MoveBefore { .. }))
.count();
let keep_count: usize = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::Keep { .. }))
.count();
assert_eq!(move_count, 1);
assert_eq!(keep_count, 1);
}
#[test]
fn plan_full_disjoint_replace() {
let old: Vec<Option<&'static str>> = (0..26).map(|i| Some(leak(format!("k{i}")))).collect();
let new: Vec<Option<&'static str>> =
(40..66).map(|i| Some(leak(format!("k{i}")))).collect();
let plan = compute_child_ops_plan(&old, &new);
let removes: Vec<ChildOpPlan> = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::Remove { .. }))
.cloned()
.collect();
assert_eq!(removes.len(), 26);
let inserts: Vec<ChildOpPlan> = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::InsertBefore { .. }))
.cloned()
.collect();
assert_eq!(inserts.len(), 26);
let mut saw_first = false;
for op in &inserts {
if let ChildOpPlan::InsertBefore { new_index, before } = op {
if !saw_first {
assert_eq!(*new_index, 0);
assert_eq!(*before, None, "first insert must anchor against None");
saw_first = true;
} else {
assert!(before.is_some(), "later inserts must have a live anchor");
}
}
}
assert!(saw_first);
let total_inserts: usize = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::InsertBefore { .. }))
.count();
let total_removes: usize = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::Remove { .. }))
.count();
assert!(
total_inserts >= 1,
"fixture must have at least one InsertBefore",
);
assert!(total_removes >= 1, "fixture must have at least one Remove",);
let inserts_before_first_remove: usize = plan
.iter()
.take_while(|op| !matches!(op, ChildOpPlan::Remove { .. }))
.filter(|op| matches!(op, ChildOpPlan::InsertBefore { .. }))
.count();
assert_eq!(
inserts_before_first_remove, 0,
"no InsertBefore may precede any Remove",
);
let removes_after_first_insert: usize = plan
.iter()
.rev()
.take_while(|op| !matches!(op, ChildOpPlan::InsertBefore { .. }))
.filter(|op| matches!(op, ChildOpPlan::Remove { .. }))
.count();
assert_eq!(
removes_after_first_insert, 0,
"no Remove may follow any InsertBefore",
);
}
#[test]
fn plan_overlapping_reorder() {
let old = vec![key("a"), key("b"), key("c"), key("d")];
let new = vec![key("b"), key("d"), key("a"), key("c")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::MoveBefore {
new_index: 0,
before: None
},
ChildOpPlan::MoveBefore {
new_index: 1,
before: Some(1)
},
ChildOpPlan::Keep { new_index: 2 },
ChildOpPlan::Keep { new_index: 3 },
]
);
}
#[test]
fn plan_insert_and_remove() {
let old = vec![key("a"), key("b")];
let new = vec![key("a"), key("c")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::Remove { old_index: 1 },
ChildOpPlan::Keep { new_index: 0 },
ChildOpPlan::InsertBefore {
new_index: 1,
before: Some(1)
},
]
);
}
#[test]
fn plan_empty_to_non_empty() {
let old: Vec<Option<&'static str>> = vec![];
let new = vec![key("a"), key("b")];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::InsertBefore {
new_index: 0,
before: None
},
ChildOpPlan::InsertBefore {
new_index: 1,
before: Some(1)
},
]
);
}
#[test]
fn plan_non_empty_to_empty() {
let old = vec![key("a"), key("b")];
let new: Vec<Option<&'static str>> = vec![];
let plan = compute_child_ops_plan(&old, &new);
assert_eq!(
plan,
vec![
ChildOpPlan::Remove { old_index: 0 },
ChildOpPlan::Remove { old_index: 1 },
]
);
}
#[test]
fn plan_reverse_yields_minimal_moves() {
let old: Vec<Option<&'static str>> = (0..10).map(|i| Some(leak(format!("k{i}")))).collect();
let new: Vec<Option<&'static str>> =
(0..10).rev().map(|i| Some(leak(format!("k{i}")))).collect();
let plan = compute_child_ops_plan(&old, &new);
let moves = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::MoveBefore { .. }))
.count();
let inserts = plan
.iter()
.filter(|op| matches!(op, ChildOpPlan::InsertBefore { .. }))
.count();
assert_eq!(moves, 9, "reverse of 10 keys should need 9 moves");
assert_eq!(inserts, 0, "reverse reuses every key");
}
#[test]
fn plan_invariant_no_lost_keys() {
let cases: Vec<(TestKeyList, TestKeyList)> = vec![
(vec![], vec![]),
(vec![key("a")], vec![key("a")]),
(
vec![key("a"), key("b"), key("c")],
vec![key("c"), key("a"), key("b")],
),
(
vec![key("k0"), key("k1"), key("k2"), key("k3"), key("k4")],
vec![key("k5"), key("k6"), key("k7"), key("k8"), key("k9")],
),
(
vec![key("x"), key("y"), key("z")],
vec![key("a"), key("x"), key("b"), key("y"), key("z"), key("c")],
),
];
for (old, new) in cases {
let plan = compute_child_ops_plan(&old, &new);
let removed: Vec<bool> = old
.iter()
.enumerate()
.map(|(old_index, _)| {
plan.iter().any(
|op| matches!(op, ChildOpPlan::Remove { old_index: i } if *i == old_index),
)
})
.collect();
let mut live: TestKeyList = Vec::new();
for new_k in &new {
let _ = old
.iter()
.position(|ok| ok == new_k)
.filter(|&i| !removed[i]);
live.push(*new_k);
}
assert_eq!(
live, new,
"plan did not preserve new_keys order for old={:?} new={:?}",
old, new
);
}
}
#[test]
fn plan_scales_linearly_virtual_list_scroll() {
let old: Vec<Option<&'static str>> = (0..26).map(|i| Some(leak(format!("k{i}")))).collect();
let new: Vec<Option<&'static str>> =
(40..66).map(|i| Some(leak(format!("k{i}")))).collect();
for _ in 0..4 {
let _ = compute_child_ops_plan(&old, &new);
}
let iterations: usize = 5_000;
let start: std::time::Instant = std::time::Instant::now();
for _ in 0..iterations {
let _ = compute_child_ops_plan(&old, &new);
}
let elapsed: std::time::Duration = start.elapsed();
let per_call_ns: f64 = (elapsed.as_nanos() as f64) / (iterations as f64);
eprintln!("perf_virtual_list_scroll = {:.0} ns/call", per_call_ns);
assert!(
per_call_ns < 5_000.0,
"compute_child_ops_plan perf regression: \
{per_call_ns:.1} ns/call on N=26 all-disjoint \
(was <500ns before)",
);
}
#[test]
fn plan_scales_linearly_full_reorder() {
let n: usize = 100;
let old: Vec<Option<&'static str>> = (0..n).map(|i| Some(leak(format!("k{i}")))).collect();
let new: Vec<Option<&'static str>> =
(0..n).rev().map(|i| Some(leak(format!("k{i}")))).collect();
for _ in 0..4 {
let _ = compute_child_ops_plan(&old, &new);
}
let iterations: usize = 1_000;
let start: std::time::Instant = std::time::Instant::now();
for _ in 0..iterations {
let _ = compute_child_ops_plan(&old, &new);
}
let elapsed: std::time::Duration = start.elapsed();
let per_call_ns: f64 = (elapsed.as_nanos() as f64) / (iterations as f64);
eprintln!("perf_full_reorder = {:.0} ns/call", per_call_ns);
assert!(
per_call_ns < 25_000.0,
"compute_child_ops_plan perf regression: \
{per_call_ns:.1} ns/call on N=100 full reverse \
(was <5µs before)",
);
}
}