use crate::dep_graph::{CycleError, DepGraph, InputKind, NodeId};
use ftui_core::geometry::Rect;
use rustc_hash::FxHashMap;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug)]
struct CachedNodeLayout {
area: Rect,
rects: crate::Rects,
result_hash: u64,
}
fn hash_rects(rects: &[Rect]) -> u64 {
let mut h = rustc_hash::FxHasher::default();
for r in rects {
r.x.hash(&mut h);
r.y.hash(&mut h);
r.width.hash(&mut h);
r.height.hash(&mut h);
}
h.finish()
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct IncrementalStats {
pub recomputed: usize,
pub cached: usize,
pub total: usize,
pub cache_entries: usize,
}
impl IncrementalStats {
pub fn hit_rate(&self) -> f64 {
if self.total == 0 {
0.0
} else {
self.cached as f64 / self.total as f64
}
}
}
pub struct IncrementalLayout {
graph: DepGraph,
cache: FxHashMap<NodeId, CachedNodeLayout>,
stats: IncrementalStats,
force_full: bool,
}
impl IncrementalLayout {
#[must_use]
pub fn new() -> Self {
Self {
graph: DepGraph::new(),
cache: FxHashMap::default(),
stats: IncrementalStats::default(),
force_full: false,
}
}
#[must_use]
pub fn with_capacity(node_cap: usize) -> Self {
Self {
graph: DepGraph::with_capacity(node_cap, node_cap),
cache: FxHashMap::with_capacity_and_hasher(node_cap, Default::default()),
stats: IncrementalStats::default(),
force_full: false,
}
}
#[must_use]
pub fn from_env() -> Self {
let force = std::env::var("FRANKENTUI_FULL_LAYOUT")
.map(|v| matches!(v.to_ascii_lowercase().as_str(), "1" | "true" | "yes"))
.unwrap_or(false);
Self {
graph: DepGraph::new(),
cache: FxHashMap::default(),
stats: IncrementalStats::default(),
force_full: force,
}
}
pub fn add_node(&mut self, parent: Option<NodeId>) -> NodeId {
let id = self.graph.add_node();
if let Some(p) = parent {
self.graph.set_parent(id, p);
let _ = self.graph.add_edge(id, p);
}
self.graph.mark_dirty(id);
id
}
pub fn remove_node(&mut self, id: NodeId) {
self.cache.remove(&id);
if let Some(parent) = self.graph.parent(id) {
self.mark_dirty_with_ancestors(parent);
}
self.graph.remove_node(id);
}
pub fn add_dependency(&mut self, from: NodeId, to: NodeId) -> Result<(), CycleError> {
self.graph.add_edge(from, to)
}
pub fn mark_changed(&mut self, id: NodeId, kind: InputKind, new_hash: u64) {
self.graph.mark_changed(id, kind, new_hash);
}
pub fn mark_dirty(&mut self, id: NodeId) {
self.graph.mark_dirty(id);
}
pub fn mark_dirty_with_ancestors(&mut self, id: NodeId) {
self.graph.mark_dirty(id);
let mut current = id;
while let Some(parent) = self.graph.parent(current) {
self.graph.mark_dirty(parent);
current = parent;
}
}
pub fn propagate(&mut self) -> Vec<NodeId> {
self.graph.propagate()
}
#[must_use]
pub fn is_dirty(&self, id: NodeId) -> bool {
self.graph.is_dirty(id)
}
pub fn get_or_compute<F>(&mut self, id: NodeId, area: Rect, compute: F) -> crate::Rects
where
F: FnOnce(Rect) -> crate::Rects,
{
self.stats.total += 1;
if !self.force_full
&& !self.graph.is_dirty(id)
&& let Some(cached) = self.cache.get(&id)
&& cached.area == area
{
self.stats.cached += 1;
return cached.rects.clone();
}
let rects = compute(area);
let result_hash = hash_rects(&rects);
self.cache.insert(
id,
CachedNodeLayout {
area,
rects: rects.clone(),
result_hash,
},
);
self.graph.clean(id);
self.stats.recomputed += 1;
rects
}
#[must_use]
pub fn cached_rects(&self, id: NodeId) -> Option<&[Rect]> {
self.cache.get(&id).map(|c| c.rects.as_slice())
}
#[must_use]
pub fn result_changed(&self, id: NodeId) -> bool {
!self.cache.contains_key(&id)
}
#[must_use]
pub fn result_hash(&self, id: NodeId) -> Option<u64> {
self.cache.get(&id).map(|c| c.result_hash)
}
pub fn set_force_full(&mut self, force: bool) {
self.force_full = force;
}
#[must_use]
pub fn force_full(&self) -> bool {
self.force_full
}
#[must_use]
pub fn stats(&self) -> IncrementalStats {
IncrementalStats {
cache_entries: self.cache.len(),
..self.stats.clone()
}
}
pub fn reset_stats(&mut self) {
self.stats = IncrementalStats::default();
}
pub fn clean_all(&mut self) {
self.graph.clean_all();
}
pub fn invalidate_all(&mut self) {
self.graph.invalidate_all();
}
pub fn clear_cache(&mut self) {
self.cache.clear();
}
#[must_use]
pub fn graph(&self) -> &DepGraph {
&self.graph
}
pub fn graph_mut(&mut self) -> &mut DepGraph {
&mut self.graph
}
#[must_use]
pub fn cache_len(&self) -> usize {
self.cache.len()
}
#[must_use]
pub fn node_count(&self) -> usize {
self.graph.node_count()
}
}
impl Default for IncrementalLayout {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for IncrementalLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IncrementalLayout")
.field("nodes", &self.graph.node_count())
.field("cache_entries", &self.cache.len())
.field("force_full", &self.force_full)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn binary_tree(depth: u32) -> (IncrementalLayout, NodeId, Vec<NodeId>) {
let node_count = (1u32 << (depth + 1)) - 1;
let mut inc = IncrementalLayout::with_capacity(node_count as usize);
let root = inc.add_node(None);
let mut current_level = vec![root];
let mut leaves = Vec::new();
for _ in 0..depth {
let mut next_level = Vec::new();
for &parent in ¤t_level {
let left = inc.add_node(Some(parent));
let right = inc.add_node(Some(parent));
next_level.push(left);
next_level.push(right);
}
current_level = next_level;
}
leaves.extend(current_level);
(inc, root, leaves)
}
fn flat_tree(n: usize) -> (IncrementalLayout, NodeId, Vec<NodeId>) {
let mut inc = IncrementalLayout::with_capacity(n + 1);
let root = inc.add_node(None);
let children: Vec<_> = (0..n).map(|_| inc.add_node(Some(root))).collect();
(inc, root, children)
}
fn area(w: u16, h: u16) -> Rect {
Rect::new(0, 0, w, h)
}
fn split_equal(a: Rect, n: usize) -> crate::Rects {
if n == 0 {
return crate::Rects::new();
}
let w = a.width / n as u16;
(0..n)
.map(|i| Rect::new(a.x + (i as u16) * w, a.y, w, a.height))
.collect()
}
#[test]
fn new_node_is_dirty() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
assert!(inc.is_dirty(n));
}
#[test]
fn get_or_compute_caches() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = area(80, 24);
let mut calls = 0u32;
let r1 = inc.get_or_compute(n, a, |a| {
calls += 1;
split_equal(a, 2)
});
let r2 = inc.get_or_compute(n, a, |a| {
calls += 1;
split_equal(a, 2)
});
assert_eq!(r1, r2);
assert_eq!(calls, 1);
}
#[test]
fn dirty_node_recomputes() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(n, a, |a| split_equal(a, 2));
inc.mark_dirty(n);
inc.propagate();
let mut calls = 0u32;
inc.get_or_compute(n, a, |a| {
calls += 1;
split_equal(a, 2)
});
assert_eq!(calls, 1);
}
#[test]
fn area_change_triggers_recompute() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
inc.get_or_compute(n, area(80, 24), |a| split_equal(a, 2));
let mut calls = 0u32;
inc.get_or_compute(n, area(120, 40), |a| {
calls += 1;
split_equal(a, 2)
});
assert_eq!(calls, 1);
}
#[test]
fn same_area_same_node_cached() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(n, a, |a| split_equal(a, 2));
let mut calls = 0u32;
inc.get_or_compute(n, a, |_a| {
calls += 1;
crate::Rects::new()
});
assert_eq!(calls, 0);
}
#[test]
fn dirty_parent_dirties_child() {
let mut inc = IncrementalLayout::new();
let root = inc.add_node(None);
let child = inc.add_node(Some(root));
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(root, a, |a| split_equal(a, 1));
inc.get_or_compute(child, a, |a| split_equal(a, 2));
inc.mark_dirty(root);
inc.propagate();
assert!(inc.is_dirty(root));
assert!(inc.is_dirty(child));
}
#[test]
fn clean_sibling_not_affected_by_dirty_sibling() {
let (mut inc, root, children) = flat_tree(3);
inc.propagate();
let a = area(120, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
inc.mark_dirty(children[1]);
inc.propagate();
assert!(!inc.is_dirty(root));
assert!(!inc.is_dirty(children[0]));
assert!(inc.is_dirty(children[1]));
assert!(!inc.is_dirty(children[2]));
}
#[test]
fn flex_siblings_dirty_via_parent() {
let (mut inc, root, children) = flat_tree(3);
inc.propagate();
let a = area(120, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
inc.mark_dirty(children[1]);
inc.mark_dirty(root); inc.propagate();
assert!(inc.is_dirty(root));
assert!(inc.is_dirty(children[0]));
assert!(inc.is_dirty(children[1]));
assert!(inc.is_dirty(children[2]));
}
#[test]
fn stats_track_hits_and_misses() {
let (mut inc, root, children) = flat_tree(3);
inc.propagate();
let a = area(120, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
let s = inc.stats();
assert_eq!(s.recomputed, 4);
assert_eq!(s.cached, 0);
assert_eq!(s.total, 4);
inc.reset_stats();
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
let s = inc.stats();
assert_eq!(s.recomputed, 0);
assert_eq!(s.cached, 4);
assert_eq!(s.total, 4);
assert!((s.hit_rate() - 1.0).abs() < 0.001);
}
#[test]
fn stats_partial_dirty() {
let (mut inc, root, children) = flat_tree(4);
inc.propagate();
let a = area(160, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 4));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
inc.reset_stats();
inc.mark_dirty(children[2]);
inc.propagate();
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 4));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
let s = inc.stats();
assert_eq!(s.recomputed, 1);
assert_eq!(s.cached, 4); assert_eq!(s.total, 5);
}
#[test]
fn force_full_bypasses_cache() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(n, a, |a| split_equal(a, 2));
inc.set_force_full(true);
assert!(inc.force_full());
let mut calls = 0u32;
inc.get_or_compute(n, a, |a| {
calls += 1;
split_equal(a, 2)
});
assert_eq!(calls, 1);
}
#[test]
fn force_full_produces_identical_results() {
let (mut inc, root, children) = flat_tree(3);
inc.propagate();
let a = area(120, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
let mut child_rects_inc = Vec::new();
for (i, &c) in children.iter().enumerate() {
child_rects_inc.push(inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 2)));
}
inc.set_force_full(true);
inc.reset_stats();
let root_rects_full = inc.get_or_compute(root, a, |a| split_equal(a, 3));
let mut child_rects_full = Vec::new();
for (i, &c) in children.iter().enumerate() {
child_rects_full.push(inc.get_or_compute(c, root_rects_full[i], |a| split_equal(a, 2)));
}
assert_eq!(root_rects, root_rects_full);
assert_eq!(child_rects_inc, child_rects_full);
}
#[test]
fn remove_node_evicts_cache() {
let mut inc = IncrementalLayout::new();
let root = inc.add_node(None);
let child = inc.add_node(Some(root));
inc.propagate();
inc.get_or_compute(child, area(40, 24), |a| split_equal(a, 1));
assert!(inc.cached_rects(child).is_some());
inc.remove_node(child);
assert!(inc.cached_rects(child).is_none());
}
#[test]
fn remove_node_dirties_parent() {
let mut inc = IncrementalLayout::new();
let root = inc.add_node(None);
let child = inc.add_node(Some(root));
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(root, a, |a| split_equal(a, 1));
inc.get_or_compute(child, a, |a| split_equal(a, 1));
inc.remove_node(child);
assert!(inc.is_dirty(root));
}
#[test]
fn invalidate_all_forces_recompute() {
let (mut inc, root, children) = flat_tree(3);
inc.propagate();
let a = area(120, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
inc.invalidate_all();
inc.propagate();
assert!(inc.is_dirty(root));
for &c in &children {
assert!(inc.is_dirty(c));
}
}
#[test]
fn clean_all_resets_dirty() {
let (mut inc, root, children) = flat_tree(2);
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(root, a, |a| split_equal(a, 2));
for (i, &c) in children.iter().enumerate() {
let child_area = Rect::new(i as u16 * 40, 0, 40, 24);
inc.get_or_compute(c, child_area, |a| split_equal(a, 1));
}
inc.mark_dirty(root);
inc.propagate();
assert!(inc.is_dirty(root));
inc.clean_all();
assert!(!inc.is_dirty(root));
}
#[test]
fn clear_cache_frees_memory() {
let (mut inc, root, _children) = flat_tree(5);
inc.propagate();
let a = area(200, 24);
inc.get_or_compute(root, a, |a| split_equal(a, 5));
assert!(inc.cache_len() > 0);
inc.clear_cache();
assert_eq!(inc.cache_len(), 0);
}
#[test]
fn mark_dirty_with_ancestors_propagates_to_siblings() {
let (mut inc, root, children) = flat_tree(3);
inc.propagate();
let a = area(120, 24);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 3));
for (i, &c) in children.iter().enumerate() {
inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 1));
}
inc.mark_dirty_with_ancestors(children[1]);
inc.propagate();
assert!(inc.is_dirty(root));
assert!(inc.is_dirty(children[0]));
assert!(inc.is_dirty(children[1]));
assert!(inc.is_dirty(children[2]));
}
#[test]
fn mark_dirty_with_ancestors_deep_chain() {
let mut inc = IncrementalLayout::new();
let root = inc.add_node(None);
let a = inc.add_node(Some(root));
let b = inc.add_node(Some(a));
let c = inc.add_node(Some(b));
inc.propagate();
let area_ = area(80, 24);
inc.get_or_compute(root, area_, |a| split_equal(a, 1));
inc.get_or_compute(a, area_, |a| split_equal(a, 1));
inc.get_or_compute(b, area_, |a| split_equal(a, 1));
inc.get_or_compute(c, area_, |a| split_equal(a, 1));
inc.mark_dirty_with_ancestors(c);
inc.propagate();
assert!(inc.is_dirty(root));
assert!(inc.is_dirty(a));
assert!(inc.is_dirty(b));
assert!(inc.is_dirty(c));
}
#[test]
fn mark_changed_deduplicates() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(n, a, |a| split_equal(a, 2));
inc.mark_changed(n, InputKind::Constraint, 42);
inc.propagate();
assert!(inc.is_dirty(n));
inc.get_or_compute(n, a, |a| split_equal(a, 2));
inc.mark_changed(n, InputKind::Constraint, 42);
inc.propagate();
assert!(!inc.is_dirty(n));
}
#[test]
fn deep_tree_partial_dirty() {
let (mut inc, root, leaves) = binary_tree(4);
inc.propagate();
fn walk(inc: &mut IncrementalLayout, id: NodeId, a: Rect) {
let rects = inc.get_or_compute(id, a, |a| split_equal(a, 2));
let deps: Vec<_> = inc.graph().dependents(id).to_vec();
for (i, child) in deps.iter().enumerate() {
if i < rects.len() {
walk(inc, *child, rects[i]);
}
}
}
walk(&mut inc, root, area(160, 24));
inc.reset_stats();
inc.mark_dirty(leaves[7]);
inc.propagate();
walk(&mut inc, root, area(160, 24));
let s = inc.stats();
assert_eq!(s.recomputed, 1);
assert!(s.cached > 0);
}
#[test]
fn incremental_equals_full_layout() {
let a = area(200, 60);
let mut inc = IncrementalLayout::new();
let root = inc.add_node(None);
let mut children = Vec::new();
let mut grandchildren = Vec::new();
for _ in 0..5 {
let child = inc.add_node(Some(root));
children.push(child);
for _ in 0..3 {
let gc = inc.add_node(Some(child));
grandchildren.push(gc);
}
}
inc.propagate();
let compute = |a: Rect, n: usize| -> crate::Rects { split_equal(a, n) };
let root_rects = inc.get_or_compute(root, a, |a| compute(a, 5));
let mut child_rects = Vec::new();
let mut gc_rects = Vec::new();
for (i, &c) in children.iter().enumerate() {
let cr = inc.get_or_compute(c, root_rects[i], |a| compute(a, 3));
let deps: Vec<_> = inc.graph().dependents(c).to_vec();
for (j, &gc) in deps.iter().enumerate() {
if j < cr.len() {
gc_rects.push(inc.get_or_compute(gc, cr[j], |a| compute(a, 1)));
}
}
child_rects.push(cr);
}
inc.set_force_full(true);
inc.reset_stats();
let root_rects2 = inc.get_or_compute(root, a, |a| compute(a, 5));
let mut child_rects2 = Vec::new();
let mut gc_rects2 = Vec::new();
for (i, &c) in children.iter().enumerate() {
let cr = inc.get_or_compute(c, root_rects2[i], |a| compute(a, 3));
let deps: Vec<_> = inc.graph().dependents(c).to_vec();
for (j, &gc) in deps.iter().enumerate() {
if j < cr.len() {
gc_rects2.push(inc.get_or_compute(gc, cr[j], |a| compute(a, 1)));
}
}
child_rects2.push(cr);
}
assert_eq!(root_rects, root_rects2);
assert_eq!(child_rects, child_rects2);
assert_eq!(gc_rects, gc_rects2);
}
#[test]
fn empty_graph() {
let inc = IncrementalLayout::new();
assert_eq!(inc.node_count(), 0);
assert_eq!(inc.cache_len(), 0);
}
#[test]
fn single_node_graph() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let r = inc.get_or_compute(n, area(80, 24), |_a| crate::Rects::new());
assert!(r.is_empty());
let s = inc.stats();
assert_eq!(s.total, 1);
assert_eq!(s.recomputed, 1);
}
#[test]
fn zero_area_still_caches() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = Rect::default(); inc.get_or_compute(n, a, |_| crate::Rects::new());
let mut calls = 0u32;
inc.get_or_compute(n, a, |_| {
calls += 1;
crate::Rects::new()
});
assert_eq!(calls, 0);
}
#[test]
fn result_hash_consistent() {
let mut inc = IncrementalLayout::new();
let n = inc.add_node(None);
inc.propagate();
let a = area(80, 24);
inc.get_or_compute(n, a, |a| split_equal(a, 2));
let h1 = inc.result_hash(n).unwrap();
inc.mark_dirty(n);
inc.propagate();
inc.get_or_compute(n, a, |a| split_equal(a, 2));
let h2 = inc.result_hash(n).unwrap();
assert_eq!(h1, h2);
}
#[test]
fn debug_format() {
let inc = IncrementalLayout::new();
let dbg = format!("{:?}", inc);
assert!(dbg.contains("IncrementalLayout"));
assert!(dbg.contains("nodes"));
}
#[test]
fn default_impl() {
let inc = IncrementalLayout::default();
assert_eq!(inc.node_count(), 0);
assert!(!inc.force_full());
}
#[test]
fn from_env_default_is_not_force_full() {
let inc = IncrementalLayout::from_env();
assert_eq!(inc.node_count(), 0);
}
#[test]
fn parse_env_values() {
let parse =
|s: &str| -> bool { matches!(s.to_ascii_lowercase().as_str(), "1" | "true" | "yes") };
assert!(parse("1"));
assert!(parse("true"));
assert!(parse("TRUE"));
assert!(parse("yes"));
assert!(parse("YES"));
assert!(!parse("0"));
assert!(!parse("false"));
assert!(!parse("no"));
assert!(!parse(""));
}
#[test]
fn thousand_node_tree_partial_dirty() {
let mut inc = IncrementalLayout::with_capacity(111);
let root = inc.add_node(None);
let mut children = Vec::new();
let mut grandchildren = Vec::new();
for _ in 0..10 {
let child = inc.add_node(Some(root));
children.push(child);
for _ in 0..10 {
let gc = inc.add_node(Some(child));
grandchildren.push(gc);
}
}
inc.propagate();
let a = area(200, 60);
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 10));
for (i, &c) in children.iter().enumerate() {
let cr = inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 10));
let deps: Vec<_> = inc.graph().dependents(c).to_vec();
for (j, &gc) in deps.iter().enumerate() {
if j < cr.len() {
inc.get_or_compute(gc, cr[j], |a| split_equal(a, 1));
}
}
}
let s = inc.stats();
assert_eq!(s.recomputed, 111);
assert_eq!(s.cached, 0);
inc.reset_stats();
inc.mark_dirty(grandchildren[17]);
inc.mark_dirty(grandchildren[83]);
inc.propagate();
let root_rects = inc.get_or_compute(root, a, |a| split_equal(a, 10));
for (i, &c) in children.iter().enumerate() {
let cr = inc.get_or_compute(c, root_rects[i], |a| split_equal(a, 10));
let deps: Vec<_> = inc.graph().dependents(c).to_vec();
for (j, &gc) in deps.iter().enumerate() {
if j < cr.len() {
inc.get_or_compute(gc, cr[j], |a| split_equal(a, 1));
}
}
}
let s = inc.stats();
assert_eq!(s.recomputed, 2);
assert_eq!(s.cached, 109);
assert!(s.hit_rate() > 0.95);
}
}