oxihuman_core/
cache_policy.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum PolicyKind {
11 Lru,
12 Fifo,
13 Lfu,
14}
15
16#[allow(dead_code)]
18#[derive(Debug, Clone)]
19pub struct PolicyEntry {
20 pub key: String,
21 pub insert_order: u64,
22 pub last_access: u64,
23 pub access_count: u64,
24}
25
26#[allow(dead_code)]
28#[derive(Debug, Clone)]
29pub struct CachePolicy {
30 kind: PolicyKind,
31 entries: Vec<PolicyEntry>,
32 counter: u64,
33}
34
35#[allow(dead_code)]
36impl CachePolicy {
37 pub fn new(kind: PolicyKind) -> Self {
38 Self {
39 kind,
40 entries: Vec::new(),
41 counter: 0,
42 }
43 }
44
45 pub fn insert(&mut self, key: &str) {
46 self.counter += 1;
47 if let Some(e) = self.entries.iter_mut().find(|e| e.key == key) {
48 e.last_access = self.counter;
49 e.access_count += 1;
50 } else {
51 self.entries.push(PolicyEntry {
52 key: key.to_string(),
53 insert_order: self.counter,
54 last_access: self.counter,
55 access_count: 1,
56 });
57 }
58 }
59
60 pub fn touch(&mut self, key: &str) {
61 self.counter += 1;
62 if let Some(e) = self.entries.iter_mut().find(|e| e.key == key) {
63 e.last_access = self.counter;
64 e.access_count += 1;
65 }
66 }
67
68 pub fn evict_candidate(&self) -> Option<&str> {
69 if self.entries.is_empty() {
70 return None;
71 }
72 let best = match self.kind {
73 PolicyKind::Lru => self.entries.iter().min_by_key(|e| e.last_access),
74 PolicyKind::Fifo => self.entries.iter().min_by_key(|e| e.insert_order),
75 PolicyKind::Lfu => self.entries.iter().min_by_key(|e| e.access_count),
76 };
77 best.map(|e| e.key.as_str())
78 }
79
80 pub fn remove(&mut self, key: &str) {
81 self.entries.retain(|e| e.key != key);
82 }
83
84 pub fn len(&self) -> usize {
85 self.entries.len()
86 }
87
88 pub fn is_empty(&self) -> bool {
89 self.entries.is_empty()
90 }
91
92 pub fn kind(&self) -> PolicyKind {
93 self.kind
94 }
95
96 pub fn contains(&self, key: &str) -> bool {
97 self.entries.iter().any(|e| e.key == key)
98 }
99
100 pub fn clear(&mut self) {
101 self.entries.clear();
102 self.counter = 0;
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn new_policy_empty() {
112 let p = CachePolicy::new(PolicyKind::Lru);
113 assert!(p.is_empty());
114 }
115
116 #[test]
117 fn insert_adds_entry() {
118 let mut p = CachePolicy::new(PolicyKind::Lru);
119 p.insert("a");
120 assert_eq!(p.len(), 1);
121 assert!(p.contains("a"));
122 }
123
124 #[test]
125 fn lru_evicts_least_recently_used() {
126 let mut p = CachePolicy::new(PolicyKind::Lru);
127 p.insert("a");
128 p.insert("b");
129 p.touch("a");
130 assert_eq!(p.evict_candidate(), Some("b"));
131 }
132
133 #[test]
134 fn fifo_evicts_oldest() {
135 let mut p = CachePolicy::new(PolicyKind::Fifo);
136 p.insert("first");
137 p.insert("second");
138 assert_eq!(p.evict_candidate(), Some("first"));
139 }
140
141 #[test]
142 fn lfu_evicts_least_frequent() {
143 let mut p = CachePolicy::new(PolicyKind::Lfu);
144 p.insert("a");
145 p.insert("b");
146 p.touch("a");
147 p.touch("a");
148 assert_eq!(p.evict_candidate(), Some("b"));
149 }
150
151 #[test]
152 fn remove_entry() {
153 let mut p = CachePolicy::new(PolicyKind::Lru);
154 p.insert("x");
155 p.remove("x");
156 assert!(p.is_empty());
157 }
158
159 #[test]
160 fn clear_resets() {
161 let mut p = CachePolicy::new(PolicyKind::Lru);
162 p.insert("a");
163 p.insert("b");
164 p.clear();
165 assert!(p.is_empty());
166 }
167
168 #[test]
169 fn evict_candidate_empty() {
170 let p = CachePolicy::new(PolicyKind::Lru);
171 assert!(p.evict_candidate().is_none());
172 }
173
174 #[test]
175 fn kind_returns_policy() {
176 let p = CachePolicy::new(PolicyKind::Fifo);
177 assert_eq!(p.kind(), PolicyKind::Fifo);
178 }
179
180 #[test]
181 fn duplicate_insert_updates() {
182 let mut p = CachePolicy::new(PolicyKind::Lfu);
183 p.insert("a");
184 p.insert("a");
185 assert_eq!(p.len(), 1);
186 }
187}