use crate::cache::cache_storage_with_get_size::CacheStorageWithGetSize;
use crate::cache::CacheStorage;
use crate::template::Template;
use std::collections::{HashMap, VecDeque};
use std::rc::Rc;
enum Entry {
Strong(Rc<Template>),
Soft(std::rc::Weak<Template>),
}
pub struct MruCacheStorage {
strong_size_limit: usize,
soft_size_limit: usize,
map: HashMap<String, Entry>,
strong_order: VecDeque<String>,
soft_order: VecDeque<String>,
}
impl MruCacheStorage {
pub fn new(strong_size_limit: usize, soft_size_limit: usize) -> Self {
MruCacheStorage {
strong_size_limit,
soft_size_limit,
map: HashMap::new(),
strong_order: VecDeque::new(),
soft_order: VecDeque::new(),
}
}
}
impl Default for MruCacheStorage {
fn default() -> Self {
MruCacheStorage::new(50, 50)
}
}
impl CacheStorage for MruCacheStorage {
fn get(&mut self, key: &str) -> Option<Rc<Template>> {
let value = match self.map.get(key) {
Some(Entry::Strong(t)) => Some(t.clone()),
Some(Entry::Soft(w)) => w.upgrade(),
None => None,
}?;
match self.map.get(key) {
Some(Entry::Strong(_)) => {
self.relink_strong(key);
}
Some(Entry::Soft(_)) => {
remove_from(&mut self.soft_order, key);
if self.strong_size_limit > 0 && self.strong_order.len() >= self.strong_size_limit {
if let Some(oldest) = self.strong_order.pop_front() {
self.demote(&oldest);
}
}
self.strong_order.push_back(key.to_string());
}
None => {}
}
Some(value)
}
fn put(&mut self, key: &str, value: Rc<Template>) {
if self.map.contains_key(key) {
self.map.insert(key.to_string(), Entry::Strong(value));
remove_from(&mut self.soft_order, key);
self.relink_strong(key);
return;
}
self.map.insert(key.to_string(), Entry::Strong(value));
self.strong_order.push_back(key.to_string());
if self.strong_size_limit > 0 && self.strong_order.len() > self.strong_size_limit {
if let Some(oldest) = self.strong_order.pop_front() {
self.demote(&oldest);
}
}
}
fn remove(&mut self, key: &str) {
self.map.remove(key);
remove_from(&mut self.strong_order, key);
remove_from(&mut self.soft_order, key);
}
fn clear(&mut self) {
self.map.clear();
self.strong_order.clear();
self.soft_order.clear();
}
}
impl MruCacheStorage {
fn demote(&mut self, key: &str) {
let strong: Option<Rc<Template>> = match self.map.get(key) {
Some(Entry::Strong(t)) => Some(t.clone()),
_ => None,
};
let Some(t) = strong else { return };
if self.soft_size_limit > 0 && self.soft_order.len() >= self.soft_size_limit {
if let Some(oldest) = self.soft_order.pop_front() {
self.map.remove(&oldest);
}
}
self.map
.insert(key.to_string(), Entry::Soft(Rc::downgrade(&t)));
self.soft_order.push_back(key.to_string());
}
fn relink_strong(&mut self, key: &str) {
remove_from(&mut self.strong_order, key);
self.strong_order.push_back(key.to_string());
}
}
fn remove_from(order: &mut VecDeque<String>, key: &str) {
if let Some(pos) = order.iter().position(|k| k == key) {
order.remove(pos);
}
}
impl CacheStorageWithGetSize for MruCacheStorage {
fn get_size(&mut self) -> usize {
self.map.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::parser;
use crate::template::Configuration;
fn tmpl(name: &str) -> Rc<Template> {
let cfg = Rc::new(Configuration::default());
Rc::new(parser::parse(&cfg, name, "x").unwrap())
}
#[test]
fn mru_evicts_strong_then_soft() {
let mut s = MruCacheStorage::new(2, 1);
let (ta, tb, tc, td) = (tmpl("a"), tmpl("b"), tmpl("c"), tmpl("d"));
s.put("a.ftl", ta.clone());
s.put("b.ftl", tb.clone());
s.put("c.ftl", tc.clone());
assert!(s.get("a.ftl").is_some());
assert!(s.get("a.ftl").is_some());
s.put("d.ftl", td.clone());
assert_eq!(s.get_size(), 3, "总容量 = strong 2 + soft 1");
assert!(s.get("a.ftl").is_some());
assert!(s.get("c.ftl").is_some());
assert!(s.get("d.ftl").is_some());
}
#[test]
fn mru_unlimited() {
let mut s = MruCacheStorage::new(0, 0);
for i in 0..100 {
s.put(&format!("t{i}.ftl"), tmpl(&format!("t{i}")));
}
assert_eq!(s.get_size(), 100);
assert!(s.get("t0.ftl").is_some());
}
}