use std::cell::RefCell;
use std::sync::atomic::{AtomicU64, Ordering};
pub(crate) const KEY_MAX: usize = 96;
const TOKENS_MAX: usize = 48;
const SLOTS: usize = 4096;
static NEXT_CACHE_ID: AtomicU64 = AtomicU64::new(1);
pub(crate) fn new_cache_id() -> u64 {
NEXT_CACHE_ID.fetch_add(1, Ordering::Relaxed)
}
#[derive(Clone, Copy)]
struct Slot {
owner: u64, len: u8,
n_tokens: u8,
key: [u8; KEY_MAX],
tokens: [u32; TOKENS_MAX],
}
const EMPTY: Slot = Slot {
owner: 0,
len: 0,
n_tokens: 0,
key: [0; KEY_MAX],
tokens: [0; TOKENS_MAX],
};
thread_local! {
static CACHE: RefCell<Box<[Slot; SLOTS]>> = RefCell::new(Box::new([EMPTY; SLOTS]));
}
#[inline]
fn slot_index(owner: u64, piece: &[u8]) -> usize {
use std::hash::{Hash, Hasher};
let mut h = rustc_hash::FxHasher::default();
owner.hash(&mut h);
piece.hash(&mut h);
let x = h.finish();
((x ^ (x >> 32)) as usize) & (SLOTS - 1)
}
#[inline]
pub(crate) fn count_piece(
owner: u64,
piece: &[u8],
compute: impl FnOnce(&mut [u32; KEY_MAX]) -> usize,
) -> usize {
debug_assert!(piece.len() <= KEY_MAX);
CACHE.with(|c| {
let mut cache = c.borrow_mut();
let slot = &mut cache[slot_index(owner, piece)];
if slot.owner == owner
&& slot.len as usize == piece.len()
&& &slot.key[..piece.len()] == piece
{
return slot.n_tokens as usize;
}
let mut buf = [0u32; KEY_MAX];
let n = compute(&mut buf);
if n <= TOKENS_MAX {
slot.owner = owner;
slot.len = piece.len() as u8;
slot.n_tokens = n as u8;
slot.key[..piece.len()].copy_from_slice(piece);
slot.tokens[..n].copy_from_slice(&buf[..n]);
}
n
})
}
#[inline]
pub(crate) fn encode_piece(
owner: u64,
piece: &[u8],
out: &mut Vec<u32>,
compute: impl FnOnce(&mut [u32; KEY_MAX]) -> usize,
) {
debug_assert!(piece.len() <= KEY_MAX);
CACHE.with(|c| {
let mut cache = c.borrow_mut();
let slot = &mut cache[slot_index(owner, piece)];
if slot.owner == owner
&& slot.len as usize == piece.len()
&& &slot.key[..piece.len()] == piece
{
out.extend_from_slice(&slot.tokens[..slot.n_tokens as usize]);
return;
}
let mut buf = [0u32; KEY_MAX];
let n = compute(&mut buf);
out.extend_from_slice(&buf[..n]);
if n <= TOKENS_MAX {
slot.owner = owner;
slot.len = piece.len() as u8;
slot.n_tokens = n as u8;
slot.key[..piece.len()].copy_from_slice(piece);
slot.tokens[..n].copy_from_slice(&buf[..n]);
}
});
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hit_returns_stored_tokens_per_owner() {
let a = new_cache_id();
let b = new_cache_id();
let mut calls = 0;
let n = count_piece(a, b"xyz", |buf| {
calls += 1;
buf[0] = 7;
buf[1] = 8;
2
});
assert_eq!((n, calls), (2, 1));
let n = count_piece(a, b"xyz", |_| unreachable!("must hit"));
assert_eq!(n, 2);
let mut out = vec![1];
encode_piece(a, b"xyz", &mut out, |_| unreachable!("must hit"));
assert_eq!(out, vec![1, 7, 8]);
let n = count_piece(b, b"xyz", |buf| {
buf[0] = 9;
1
});
assert_eq!(n, 1);
let n = count_piece(a, b"xyz", |buf| {
buf[0] = 7;
buf[1] = 8;
2
});
assert_eq!(n, 2);
}
#[test]
fn oversized_results_are_not_stored() {
let a = new_cache_id();
let n = count_piece(a, b"big", |buf| {
for (i, slot) in buf.iter_mut().enumerate() {
*slot = i as u32;
}
KEY_MAX });
assert_eq!(n, KEY_MAX);
let mut called = false;
let n = count_piece(a, b"big", |buf| {
called = true;
buf[0] = 1;
KEY_MAX
});
assert_eq!((n, called), (KEY_MAX, true));
}
}