use std::collections::HashMap;
use std::sync::Arc;
use crate::blocks::{CcBlock, SiBlock};
use crate::error::Result;
use crate::io::ByteSource;
use crate::parser;
const MAX_ENTRIES_PER_KIND: usize = 100_000;
#[derive(Debug, Default)]
pub struct BlockCache {
cc_cache: HashMap<u64, Arc<CcBlock>>,
text_cache: HashMap<u64, Arc<str>>,
si_cache: HashMap<u64, Arc<SiBlock>>,
stats: CacheStats,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct CacheStats {
pub cc_hits: u64,
pub cc_misses: u64,
pub text_hits: u64,
pub text_misses: u64,
pub si_hits: u64,
pub si_misses: u64,
}
impl CacheStats {
pub fn total_hits(&self) -> u64 {
self.cc_hits + self.text_hits + self.si_hits
}
pub fn total_misses(&self) -> u64 {
self.cc_misses + self.text_misses + self.si_misses
}
pub fn hit_ratio(&self) -> f64 {
let total = self.total_hits() + self.total_misses();
if total == 0 {
0.0
} else {
self.total_hits() as f64 / total as f64
}
}
}
impl BlockCache {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(cc_count: usize, text_count: usize, si_count: usize) -> Self {
BlockCache {
cc_cache: HashMap::with_capacity(cc_count),
text_cache: HashMap::with_capacity(text_count),
si_cache: HashMap::with_capacity(si_count),
stats: CacheStats::default(),
}
}
pub fn get_or_parse_cc<S: ByteSource>(
&mut self,
source: &S,
offset: u64,
) -> Result<Option<Arc<CcBlock>>> {
if offset == 0 {
return Ok(None);
}
if let Some(cached) = self.cc_cache.get(&offset) {
self.stats.cc_hits += 1;
return Ok(Some(Arc::clone(cached)));
}
self.stats.cc_misses += 1;
let cc_block = parser::parse_cc_block(source, offset)?;
let arc = Arc::new(cc_block);
if self.cc_cache.len() < MAX_ENTRIES_PER_KIND {
self.cc_cache.insert(offset, Arc::clone(&arc));
}
Ok(Some(arc))
}
pub fn get_or_parse_text<S: ByteSource>(
&mut self,
source: &S,
offset: u64,
) -> Result<Arc<str>> {
if offset == 0 {
return Ok(Arc::from(""));
}
if let Some(cached) = self.text_cache.get(&offset) {
self.stats.text_hits += 1;
return Ok(Arc::clone(cached));
}
self.stats.text_misses += 1;
let text = parser::read_text(source, offset)?;
let arc: Arc<str> = Arc::from(text.as_str());
if self.text_cache.len() < MAX_ENTRIES_PER_KIND {
self.text_cache.insert(offset, Arc::clone(&arc));
}
Ok(arc)
}
pub fn get_or_parse_si<S: ByteSource>(
&mut self,
source: &S,
offset: u64,
) -> Result<Option<Arc<SiBlock>>> {
if offset == 0 {
return Ok(None);
}
if let Some(cached) = self.si_cache.get(&offset) {
self.stats.si_hits += 1;
return Ok(Some(Arc::clone(cached)));
}
self.stats.si_misses += 1;
let si_block = parser::parse_si_block(source, offset)?;
let arc = Arc::new(si_block);
if self.si_cache.len() < MAX_ENTRIES_PER_KIND {
self.si_cache.insert(offset, Arc::clone(&arc));
}
Ok(Some(arc))
}
pub fn stats(&self) -> &CacheStats {
&self.stats
}
pub fn cc_count(&self) -> usize {
self.cc_cache.len()
}
pub fn text_count(&self) -> usize {
self.text_cache.len()
}
pub fn si_count(&self) -> usize {
self.si_cache.len()
}
pub fn clear(&mut self) {
self.cc_cache.clear();
self.text_cache.clear();
self.si_cache.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_stats_hit_ratio() {
let stats = CacheStats {
cc_hits: 8,
cc_misses: 2,
text_hits: 0,
text_misses: 0,
si_hits: 0,
si_misses: 0,
};
assert!((stats.hit_ratio() - 0.8).abs() < 0.001);
}
#[test]
fn test_cache_stats_empty() {
let stats = CacheStats::default();
assert_eq!(stats.total_hits(), 0);
assert_eq!(stats.total_misses(), 0);
assert_eq!(stats.hit_ratio(), 0.0);
}
}