use std::cell::RefCell;
use tf_tree_core::{FrameId, LookupError, Plan};
use crate::tree::Tree;
const SLOTS: usize = 16;
#[derive(Clone, Copy)]
struct Entry {
key: (u32, u32, u64),
plan: Plan,
}
thread_local! {
static CACHE: RefCell<[Option<Entry>; SLOTS]> = const { RefCell::new([None; SLOTS]) };
}
fn index(key: (u32, u32, u64)) -> usize {
let (a, b, g) = key;
let mut h = u64::from(a);
h = h.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ u64::from(b);
h = h.wrapping_mul(0x9E37_79B9_7F4A_7C15) ^ g;
(h as usize) & (SLOTS - 1)
}
pub(crate) fn get_or_compile(
tree: &Tree,
target: FrameId,
source: FrameId,
generation: u64,
) -> Result<(Plan, bool), LookupError> {
let key = (target.get(), source.get(), generation);
let idx = index(key);
CACHE.with(|c| {
if let Some(entry) = c.borrow()[idx] {
if entry.key == key {
return Ok((entry.plan, true));
}
}
let plan = tree.plan(target, source)?;
c.borrow_mut()[idx] = Some(Entry { key, plan });
Ok((plan, false))
})
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use crate::{Iso3, TreeBuilder};
#[test]
fn cache_hits_and_invalidates_on_generation() {
let tree = TreeBuilder::new()
.static_edge("a", "b", &Iso3::IDENTITY)
.static_edge("b", "c", &Iso3::IDENTITY)
.build()
.unwrap();
let a = tree.frame("a").unwrap();
let b = tree.frame("b").unwrap();
let c = tree.frame("c").unwrap();
let gen1 = tree.guard().generation();
let (p1, hit1) = super::get_or_compile(&tree, b, a, gen1).unwrap();
assert!(!hit1, "first compile is a miss");
assert_eq!(p1.generation(), gen1);
let (_p2, hit2) = super::get_or_compile(&tree, b, a, gen1).unwrap();
assert!(hit2, "repeat lookup hits the cache");
tree.reparent(c, a).unwrap();
let gen2 = tree.guard().generation();
assert_ne!(gen1, gen2, "re-parent must change the generation");
let (p3, _hit3) = super::get_or_compile(&tree, b, a, gen2).unwrap();
assert_eq!(
p3.generation(),
gen2,
"post-change plan is stamped with the new generation"
);
}
}