use crate::shape::Shape;
use alloc::rc::Rc;
#[derive(Default)]
pub struct PropertyCache {
shape: Option<Rc<Shape>>,
slot: u32,
hits: u32,
misses: u32,
}
impl PropertyCache {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn lookup(&mut self, object_shape: &Rc<Shape>, key: &str) -> Option<u32> {
if let Some(cached) = &self.shape
&& Rc::ptr_eq(cached, object_shape)
{
self.hits += 1;
return Some(self.slot);
}
self.misses += 1;
let slot = object_shape.lookup(key)?;
self.shape = Some(Rc::clone(object_shape));
self.slot = slot;
Some(slot)
}
#[must_use]
pub fn is_warm(&self) -> bool {
self.shape.is_some()
}
#[must_use]
pub fn hits(&self) -> u32 {
self.hits
}
#[must_use]
pub fn misses(&self) -> u32 {
self.misses
}
pub fn clear(&mut self) {
self.shape = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::object::Object;
#[test]
fn first_lookup_misses_then_repeats_hit() {
let root = Shape::root();
let mut obj = Object::new(Rc::clone(&root));
obj.set("x", crate::nanbox::NanBox::number(1.0));
obj.set("y", crate::nanbox::NanBox::number(2.0));
let mut ic = PropertyCache::new();
assert!(!ic.is_warm());
assert_eq!(ic.lookup(obj.shape(), "y"), Some(1));
assert!(ic.is_warm());
assert_eq!((ic.hits(), ic.misses()), (0, 1));
assert_eq!(ic.lookup(obj.shape(), "y"), Some(1));
assert_eq!(ic.lookup(obj.shape(), "y"), Some(1));
assert_eq!((ic.hits(), ic.misses()), (2, 1));
}
#[test]
fn shape_change_re_arms_the_cache() {
let root = Shape::root();
let mut a = Object::new(Rc::clone(&root));
a.set("p", crate::nanbox::NanBox::number(1.0));
let mut b = Object::new(Rc::clone(&root));
b.set("p", crate::nanbox::NanBox::number(1.0));
b.set("q", crate::nanbox::NanBox::number(2.0));
let mut ic = PropertyCache::new();
assert_eq!(ic.lookup(a.shape(), "p"), Some(0)); assert_eq!(ic.lookup(a.shape(), "p"), Some(0)); assert_eq!(ic.lookup(b.shape(), "p"), Some(0)); assert_eq!(ic.lookup(b.shape(), "p"), Some(0)); assert_eq!((ic.hits(), ic.misses()), (2, 2));
}
#[test]
fn two_objects_one_shape_share_the_fast_path() {
let root = Shape::root();
let mut a = Object::new(Rc::clone(&root));
a.set("k", crate::nanbox::NanBox::number(1.0));
let mut b = Object::new(Rc::clone(&root));
b.set("k", crate::nanbox::NanBox::number(9.0));
assert!(Rc::ptr_eq(a.shape(), b.shape()));
let mut ic = PropertyCache::new();
assert_eq!(ic.lookup(a.shape(), "k"), Some(0)); assert_eq!(ic.lookup(b.shape(), "k"), Some(0)); assert_eq!((ic.hits(), ic.misses()), (1, 1));
}
#[test]
fn absent_property_is_a_miss() {
let root = Shape::root();
let mut obj = Object::new(root);
obj.set("x", crate::nanbox::NanBox::number(1.0));
let mut ic = PropertyCache::new();
assert_eq!(ic.lookup(obj.shape(), "nope"), None);
assert_eq!((ic.hits(), ic.misses()), (0, 1));
assert!(!ic.is_warm());
}
#[test]
fn clear_cools_the_cache() {
let root = Shape::root();
let mut obj = Object::new(root);
obj.set("x", crate::nanbox::NanBox::number(1.0));
let mut ic = PropertyCache::new();
ic.lookup(obj.shape(), "x");
assert!(ic.is_warm());
ic.clear();
assert!(!ic.is_warm());
}
}