use crate::table::block::Header;
use crate::table::{Block, BlockOffset};
use crate::{GlobalTableId, UserValue};
use quick_cache::Weighter;
use quick_cache::sync::Cache as QuickCache;
const TAG_BLOCK: u8 = 0;
const TAG_BLOB: u8 = 1;
#[derive(Clone)]
enum Item {
Block(Block),
Blob(UserValue),
}
#[derive(Eq, std::hash::Hash, PartialEq)]
struct CacheKey(u8, u64, u64, u64);
impl From<(u8, u64, u64, u64)> for CacheKey {
fn from((tag, root_id, table_id, offset): (u8, u64, u64, u64)) -> Self {
Self(tag, root_id, table_id, offset)
}
}
#[derive(Clone)]
struct BlockWeighter;
impl Weighter<CacheKey, Item> for BlockWeighter {
fn weight(&self, _: &CacheKey, item: &Item) -> u64 {
use Item::{Blob, Block};
match item {
Block(b) => (Header::serialized_len() as u64) + u64::from(b.header.uncompressed_length),
Blob(b) => b.len() as u64,
}
}
}
pub struct Cache {
data: QuickCache<CacheKey, Item, BlockWeighter, rustc_hash::FxBuildHasher>,
capacity: u64,
}
impl Cache {
#[must_use]
pub fn with_capacity_bytes(bytes: u64) -> Self {
use quick_cache::sync::DefaultLifecycle;
#[expect(clippy::expect_used, reason = "nothing we can do if it fails")]
let opts = quick_cache::OptionsBuilder::new()
.weight_capacity(bytes)
.hot_allocation(0.8)
.estimated_items_capacity(10_000)
.build()
.expect("cache options should be valid");
let quick_cache = QuickCache::with_options(
opts,
BlockWeighter,
rustc_hash::FxBuildHasher,
DefaultLifecycle::default(),
);
Self {
data: quick_cache,
capacity: bytes,
}
}
#[must_use]
pub fn size(&self) -> u64 {
self.data.weight()
}
#[must_use]
pub fn capacity(&self) -> u64 {
self.capacity
}
#[doc(hidden)]
#[must_use]
pub fn get_block(&self, id: GlobalTableId, offset: BlockOffset) -> Option<Block> {
let key: CacheKey = (TAG_BLOCK, id.tree_id(), id.table_id(), *offset).into();
Some(match self.data.get(&key)? {
Item::Block(block) => block,
Item::Blob(_) => unreachable!("invalid cache item"),
})
}
#[doc(hidden)]
pub fn insert_block(&self, id: GlobalTableId, offset: BlockOffset, block: Block) {
self.data.insert(
(TAG_BLOCK, id.tree_id(), id.table_id(), *offset).into(),
Item::Block(block),
);
}
#[doc(hidden)]
pub fn insert_blob(
&self,
vlog_id: crate::TreeId,
vhandle: &crate::vlog::ValueHandle,
value: UserValue,
) {
self.data.insert(
(TAG_BLOB, vlog_id, vhandle.blob_file_id, vhandle.offset).into(),
Item::Blob(value),
);
}
#[doc(hidden)]
#[must_use]
pub fn get_blob(
&self,
vlog_id: crate::TreeId,
vhandle: &crate::vlog::ValueHandle,
) -> Option<UserValue> {
let key: CacheKey = (TAG_BLOB, vlog_id, vhandle.blob_file_id, vhandle.offset).into();
Some(match self.data.get(&key)? {
Item::Blob(blob) => blob,
Item::Block(_) => unreachable!("invalid cache item"),
})
}
}