oxihuman_core/
key_cache.rs1#![allow(dead_code)]
4
5use std::collections::HashMap;
8
9#[derive(Debug, Clone)]
11#[allow(dead_code)]
12pub struct KeyCacheEntry<V> {
13 pub value: V,
14 pub expiry: u64,
15 pub hits: u32,
16}
17
18#[derive(Debug)]
20#[allow(dead_code)]
21pub struct KeyCache<V> {
22 entries: HashMap<String, KeyCacheEntry<V>>,
23 frame: u64,
24 capacity: usize,
25}
26
27#[allow(dead_code)]
29pub fn new_key_cache<V>(capacity: usize) -> KeyCache<V> {
30 KeyCache {
31 entries: HashMap::new(),
32 frame: 0,
33 capacity,
34 }
35}
36
37#[allow(dead_code)]
39pub fn kc_insert<V>(cache: &mut KeyCache<V>, key: &str, value: V, ttl: u64) {
40 if cache.entries.len() >= cache.capacity && !cache.entries.contains_key(key) {
41 if let Some(oldest) = cache
43 .entries
44 .iter()
45 .min_by_key(|(_, e)| e.expiry)
46 .map(|(k, _)| k.clone())
47 {
48 cache.entries.remove(&oldest);
49 }
50 }
51 cache.entries.insert(
52 key.to_string(),
53 KeyCacheEntry {
54 value,
55 expiry: cache.frame + ttl,
56 hits: 0,
57 },
58 );
59}
60
61#[allow(dead_code)]
63pub fn kc_get<'a, V>(cache: &'a mut KeyCache<V>, key: &str) -> Option<&'a V> {
64 let frame = cache.frame;
65 if let Some(e) = cache.entries.get_mut(key) {
66 if e.expiry >= frame {
67 e.hits += 1;
68 return Some(&e.value);
69 }
70 }
71 None
72}
73
74#[allow(dead_code)]
76pub fn kc_advance(cache: &mut KeyCache<impl std::fmt::Debug>, frames: u64) {
77 cache.frame += frames;
78 let frame = cache.frame;
79 cache.entries.retain(|_, e| e.expiry >= frame);
80}
81
82#[allow(dead_code)]
84pub fn kc_remove<V>(cache: &mut KeyCache<V>, key: &str) -> bool {
85 cache.entries.remove(key).is_some()
86}
87
88#[allow(dead_code)]
90pub fn kc_contains<V>(cache: &mut KeyCache<V>, key: &str) -> bool {
91 kc_get(cache, key).is_some()
92}
93
94#[allow(dead_code)]
96pub fn kc_len<V>(cache: &KeyCache<V>) -> usize {
97 cache.entries.len()
98}
99
100#[allow(dead_code)]
102pub fn kc_frame<V>(cache: &KeyCache<V>) -> u64 {
103 cache.frame
104}
105
106#[allow(dead_code)]
108pub fn kc_hits<V>(cache: &KeyCache<V>, key: &str) -> u32 {
109 cache.entries.get(key).map(|e| e.hits).unwrap_or(0)
110}
111
112#[allow(dead_code)]
114pub fn kc_clear<V>(cache: &mut KeyCache<V>) {
115 cache.entries.clear();
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn test_insert_get() {
124 let mut c: KeyCache<u32> = new_key_cache(10);
125 kc_insert(&mut c, "a", 42, 5);
126 assert_eq!(kc_get(&mut c, "a"), Some(&42));
127 }
128
129 #[test]
130 fn test_expired() {
131 let mut c: KeyCache<u32> = new_key_cache(10);
132 kc_insert(&mut c, "x", 1, 2);
133 kc_advance(&mut c, 3);
134 assert_eq!(kc_get(&mut c, "x"), None);
135 }
136
137 #[test]
138 fn test_remove() {
139 let mut c: KeyCache<u32> = new_key_cache(10);
140 kc_insert(&mut c, "k", 5, 100);
141 assert!(kc_remove(&mut c, "k"));
142 assert_eq!(kc_get(&mut c, "k"), None);
143 }
144
145 #[test]
146 fn test_contains() {
147 let mut c: KeyCache<i32> = new_key_cache(10);
148 kc_insert(&mut c, "z", -1, 10);
149 assert!(kc_contains(&mut c, "z"));
150 }
151
152 #[test]
153 fn test_capacity_eviction() {
154 let mut c: KeyCache<u32> = new_key_cache(2);
155 kc_insert(&mut c, "a", 1, 100);
156 kc_insert(&mut c, "b", 2, 50);
157 kc_insert(&mut c, "c", 3, 200);
158 assert_eq!(kc_len(&c), 2);
159 }
160
161 #[test]
162 fn test_advance_evicts() {
163 let mut c: KeyCache<u32> = new_key_cache(10);
164 kc_insert(&mut c, "a", 1, 1);
165 kc_advance(&mut c, 2);
166 assert_eq!(kc_len(&c), 0);
167 }
168
169 #[test]
170 fn test_hits() {
171 let mut c: KeyCache<u32> = new_key_cache(10);
172 kc_insert(&mut c, "h", 7, 10);
173 kc_get(&mut c, "h");
174 kc_get(&mut c, "h");
175 assert_eq!(kc_hits(&c, "h"), 2);
176 }
177
178 #[test]
179 fn test_clear() {
180 let mut c: KeyCache<u32> = new_key_cache(10);
181 kc_insert(&mut c, "a", 1, 10);
182 kc_clear(&mut c);
183 assert_eq!(kc_len(&c), 0);
184 }
185
186 #[test]
187 fn test_frame_advances() {
188 let mut c: KeyCache<u32> = new_key_cache(10);
189 kc_advance(&mut c, 5);
190 assert_eq!(kc_frame(&c), 5);
191 }
192}