use std::convert::TryInto;
use crate::allocator::{Allocator, NodePtr, SExp};
type CachedFunction<T> = fn(&mut ObjectCache<T>, &Allocator, NodePtr) -> Option<T>;
use super::bytes32::{hash_blobs, Bytes32};
pub struct ObjectCache<'a, T> {
cache: Vec<Option<T>>,
allocator: &'a Allocator,
f: CachedFunction<T>,
}
fn node_to_index(node: &NodePtr) -> usize {
let node = *node;
if node < 0 {
(-node - node - 1) as usize
} else {
(node + node) as usize
}
}
impl<'a, T: Clone> ObjectCache<'a, T> {
pub fn new(allocator: &'a Allocator, f: CachedFunction<T>) -> Self {
let cache = vec![];
Self {
cache,
allocator,
f,
}
}
pub fn get_or_calculate(&mut self, node: &NodePtr) -> Option<&T> {
self.calculate(node);
self.get_from_cache(node)
}
fn get_from_cache(&self, node: &NodePtr) -> Option<&T> {
let index = node_to_index(node);
if index < self.cache.len() {
self.cache[index].as_ref()
} else {
None
}
}
fn set(&mut self, node: &NodePtr, v: T) {
let index = node_to_index(node);
if index >= self.cache.len() {
self.cache.resize(index + 1, None);
}
self.cache[index] = Some(v)
}
fn calculate(&mut self, root_node: &NodePtr) {
let mut obj_list = vec![*root_node];
loop {
match obj_list.pop() {
None => {
return;
}
Some(node) => {
let v = self.get_from_cache(&node);
match v {
Some(_) => {}
None => match (self.f)(self, self.allocator, node) {
None => match self.allocator.sexp(node) {
SExp::Pair(left, right) => {
obj_list.push(node);
obj_list.push(left);
obj_list.push(right);
}
_ => panic!("f returned `None` for atom"),
},
Some(v) => {
self.set(&node, v);
}
},
}
}
}
}
}
}
pub fn treehash(
cache: &mut ObjectCache<Bytes32>,
allocator: &Allocator,
node: NodePtr,
) -> Option<Bytes32> {
match allocator.sexp(node) {
SExp::Pair(left, right) => match cache.get_from_cache(&left) {
None => None,
Some(left_value) => cache
.get_from_cache(&right)
.map(|right_value| hash_blobs(&[&[2], left_value, right_value])),
},
SExp::Atom() => Some(hash_blobs(&[&[1], allocator.atom(node)])),
}
}
pub fn serialized_length(
cache: &mut ObjectCache<u64>,
allocator: &Allocator,
node: NodePtr,
) -> Option<u64> {
match allocator.sexp(node) {
SExp::Pair(left, right) => match cache.get_from_cache(&left) {
None => None,
Some(left_value) => cache.get_from_cache(&right).map(|right_value| {
1_u64
.saturating_add(*left_value)
.saturating_add(*right_value)
}),
},
SExp::Atom() => {
let buf = allocator.atom(node);
let lb: u64 = buf.len().try_into().unwrap_or(u64::MAX);
Some(if lb == 0 || (lb == 1 && buf[0] < 128) {
1
} else if lb < 0x40 {
1 + lb
} else if lb < 0x2000 {
2 + lb
} else if lb < 0x100000 {
3 + lb
} else if lb < 0x8000000 {
4 + lb
} else {
5 + lb
})
}
}
}
#[cfg(test)]
use std::cmp::max;
#[cfg(test)]
use std::fmt::Debug;
#[cfg(test)]
use std::io::Cursor;
#[cfg(test)]
use hex::FromHex;
#[cfg(test)]
use crate::serde::de::node_from_stream;
#[cfg(test)]
fn calculate_depth_simple(
cache: &mut ObjectCache<usize>,
allocator: &Allocator,
node: NodePtr,
) -> Option<usize> {
match allocator.sexp(node) {
SExp::Pair(left, right) => match cache.get_from_cache(&left) {
None => None,
Some(left_value) => cache
.get_from_cache(&right)
.map(|right_value| 1 + max(*left_value, *right_value)),
},
SExp::Atom() => Some(0),
}
}
#[cfg(test)]
fn check_cached_function<T>(obj_as_hex: &str, expected_value: T, f: CachedFunction<T>)
where
T: Clone + Eq + Debug,
{
let mut allocator = Allocator::new();
let blob: Vec<u8> = Vec::from_hex(obj_as_hex).unwrap();
let mut cursor: Cursor<&[u8]> = Cursor::new(&blob);
let obj = node_from_stream(&mut allocator, &mut cursor).unwrap();
let mut oc = ObjectCache::new(&allocator, f);
assert_eq!(oc.get_from_cache(&obj), None);
oc.calculate(&obj);
assert_eq!(oc.get_from_cache(&obj), Some(&expected_value));
assert_eq!(oc.get_or_calculate(&obj).unwrap().clone(), expected_value);
assert_eq!(oc.get_from_cache(&obj), Some(&expected_value));
let mut oc = ObjectCache::new(&allocator, f);
assert_eq!(oc.get_or_calculate(&obj).unwrap().clone(), expected_value);
}
#[test]
fn test_depths_cache() {
let check = |a, b| check_cached_function(a, b, calculate_depth_simple);
check("01", 0); check("ff83666f6f83626172", 1); check("ff83666f6fff8362617280", 2); check("ffff0102ff0304", 2); check("ff01ff02ff03ff04ff05ff0680", 6); }
#[test]
fn test_treehash() {
let check = |a, b| check_cached_function(a, Bytes32::from_hex(b).unwrap(), treehash);
check(
"ff83666f6f83626172",
"c518e45ae6a7b4146017b7a1d81639051b132f1f5572ce3088a3898a9ed1280b",
); check(
"ff83666f6fff8362617280",
"c97d97cc81100a4980080ba81ff1ba3985f7cff1db9d41d904b9d512bb875144",
); check(
"ffff0102ff0304",
"2824018d148bc6aed0847e2c86aaa8a5407b916169f15b12cea31fa932fc4c8d",
); check(
"ff01ff02ff03ff04ff05ff0680",
"65de5098d18bebd62aee37de32f0b62d1803d9c7c48f10dca25501243d7a0392",
); }
#[test]
fn test_serialized_length() {
let check = |a, b| check_cached_function(a, b, serialized_length);
check("ff83666f6f83626172", 9); check("ff83666f6fff8362617280", 11); check("ffff0102ff0304", 7); check("ff01ff02ff03ff04ff05ff0680", 13); }
#[test]
fn test_node_to_index() {
assert_eq!(node_to_index(&0), 0);
assert_eq!(node_to_index(&1), 2);
assert_eq!(node_to_index(&2), 4);
assert_eq!(node_to_index(&-1), 1);
assert_eq!(node_to_index(&-2), 3);
}
#[cfg(not(debug_assertions))]
#[test]
fn test_very_long_list() {
const LIST_SIZE: u64 = 20_000_000;
let mut allocator = Allocator::new();
let mut top = allocator.null();
for _ in 0..LIST_SIZE {
let atom = allocator.one();
top = allocator.new_pair(atom, top).unwrap();
}
let expected_value = LIST_SIZE * 2 + 1;
let mut oc = ObjectCache::new(&allocator, serialized_length);
assert_eq!(oc.get_or_calculate(&top).unwrap().clone(), expected_value);
let expected_value =
<[u8; 32]>::from_hex("a168fce695099a30c0745075e6db3722ed7f059e0d7cc4d7e7504e215db5017b")
.unwrap();
let mut oc = ObjectCache::new(&allocator, treehash);
assert_eq!(oc.get_or_calculate(&top).unwrap().clone(), expected_value);
}