use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
use std::sync::{Arc, LazyLock, Mutex};
use crate::Avatar;
pub const CACHE_CAPACITY: usize = 128;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct CacheKey {
input: String,
sans_env: bool,
part: Option<String>,
theme: Option<String>,
svg_width: i32,
svg_height: i32,
optimize_svg: bool,
}
impl CacheKey {
pub fn new(
input: &str,
sans_env: bool,
part: Option<&str>,
theme: Option<&str>,
svg_width: i32,
svg_height: i32,
optimize_svg: bool,
) -> CacheKey {
CacheKey {
input: input.to_string(),
sans_env,
part: part.map(str::to_string),
theme: theme.map(str::to_string),
svg_width,
svg_height,
optimize_svg,
}
}
}
struct LruCache<K, V> {
map: HashMap<K, V>,
order: VecDeque<K>,
cap: usize,
}
impl<K: Clone + Eq + Hash, V> LruCache<K, V> {
fn new(cap: usize) -> Self {
LruCache {
map: HashMap::with_capacity(cap),
order: VecDeque::new(),
cap,
}
}
fn get(&mut self, key: &K) -> Option<&V> {
if self.map.contains_key(key) {
self.touch(key);
self.map.get(key)
} else {
None
}
}
fn put(&mut self, key: K, value: V) {
if self.map.contains_key(&key) {
self.map.insert(key.clone(), value);
self.touch(&key);
return;
}
if self.map.len() == self.cap {
while let Some(old) = self.order.pop_front() {
if self.map.remove(&old).is_some() {
break;
}
}
}
self.map.insert(key.clone(), value);
self.order.push_back(key);
}
fn len(&self) -> usize {
self.map.len()
}
fn clear(&mut self) {
self.map.clear();
self.order.clear();
}
fn touch(&mut self, key: &K) {
if let Some(pos) = self.order.iter().position(|k| k == key) {
let k = self.order.remove(pos).expect("位置已存在");
self.order.push_back(k);
}
}
}
pub struct AvatarCache;
static CACHE: LazyLock<Mutex<LruCache<CacheKey, Arc<Avatar>>>> = LazyLock::new(|| {
Mutex::new(LruCache::new(CACHE_CAPACITY))
});
impl AvatarCache {
pub fn get(key: &CacheKey) -> Option<Arc<Avatar>> {
CACHE
.lock()
.expect("avatar cache poisoned")
.get(key)
.cloned()
}
pub fn put(key: CacheKey, avatar: Arc<Avatar>) {
CACHE.lock().expect("avatar cache poisoned").put(key, avatar);
}
pub fn size() -> usize {
CACHE.lock().expect("avatar cache poisoned").len()
}
pub fn clear() {
CACHE.lock().expect("avatar cache poisoned").clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lru_basic_hit_miss() {
let mut c: LruCache<i32, &str> = LruCache::new(3);
assert_eq!(c.get(&1), None);
c.put(1, "a");
c.put(2, "b");
c.put(3, "c");
assert_eq!(c.get(&2), Some(&"b"));
assert_eq!(c.len(), 3);
}
#[test]
fn lru_evicts_least_recently_used() {
let mut c: LruCache<i32, &str> = LruCache::new(2);
c.put(1, "a");
c.put(2, "b");
c.get(&1); c.put(3, "c"); assert_eq!(c.get(&2), None);
assert_eq!(c.get(&1), Some(&"a"));
assert_eq!(c.get(&3), Some(&"c"));
assert_eq!(c.len(), 2);
}
#[test]
fn lru_update_and_clear() {
let mut c: LruCache<i32, &str> = LruCache::new(2);
c.put(1, "a");
c.put(1, "b"); assert_eq!(c.len(), 1);
assert_eq!(c.get(&1), Some(&"b"));
c.clear();
assert_eq!(c.len(), 0);
assert_eq!(c.get(&1), None);
}
}