use std::iter;
use ahash::RandomState;
use crate::{
heap::{ContainsHeap, HeapReader},
resource::{ResourceError, ResourceTracker},
types::str::{allocate_string, allocate_string_no_interning},
value::Value,
};
const CAPACITY: usize = 16_384;
const MIN_LEN: usize = 2;
const MAX_LEN: usize = 64;
type CacheEntry = Option<(u64, Box<str>, Value)>;
#[derive(Default)]
pub(crate) struct JsonStringCache {
inner: Option<CacheInner>,
}
struct CacheInner {
entries: Box<[CacheEntry; CAPACITY]>,
hash_builder: RandomState,
}
impl JsonStringCache {
pub fn get_or_allocate(
&mut self,
s: String,
heap: &HeapReader<'_, impl ResourceTracker>,
) -> Result<Value, ResourceError> {
let len = s.len();
if !(MIN_LEN..=MAX_LEN).contains(&len) {
return allocate_string(s, heap.heap());
}
let inner = self.inner.get_or_insert_with(CacheInner::new);
inner.get_or_allocate(s, heap)
}
pub fn drop_all(&mut self, heap: &mut impl ContainsHeap) {
if let Some(inner) = &mut self.inner {
for entry in inner.entries.iter_mut() {
if let Some((_, _, value)) = entry.take() {
value.drop_with_heap(heap);
}
}
}
}
}
impl CacheInner {
fn new() -> Self {
Self {
entries: iter::repeat_with(|| None)
.take(CAPACITY)
.collect::<Vec<_>>()
.into_boxed_slice()
.try_into()
.expect("Vec length equals CAPACITY"),
hash_builder: RandomState::default(),
}
}
fn get_or_allocate(
&mut self,
s: String,
heap: &HeapReader<'_, impl ResourceTracker>,
) -> Result<Value, ResourceError> {
let hash = self.hash_builder.hash_one(s.as_str());
#[expect(clippy::cast_possible_truncation)]
let primary = hash as usize & (CAPACITY - 1);
for offset in 0..5 {
let index = (primary + offset) & (CAPACITY - 1);
let entry = &mut self.entries[index];
match entry {
Some((entry_hash, cached_str, cached_value)) => {
if *entry_hash == hash && **cached_str == *s {
return Ok(cached_value.clone_with_heap(heap));
}
}
None => {
return self.insert_at(index, hash, s, heap);
}
}
}
allocate_string_no_interning(s, heap.heap())
}
fn insert_at(
&mut self,
index: usize,
hash: u64,
s: String,
heap: &HeapReader<'_, impl ResourceTracker>,
) -> Result<Value, ResourceError> {
let key = s.clone().into_boxed_str();
let value = allocate_string_no_interning(s, heap.heap())?;
let cached = value.clone_with_heap(heap);
self.entries[index] = Some((hash, key, cached));
Ok(value)
}
}