1use moka::sync::Cache;
7use std::sync::Arc;
8
9use super::types::ReflexStatus;
10use crate::hashing::hash_prompt;
11use crate::storage::mmap::MmapFileHandle;
12
13#[derive(Debug, Clone)]
15pub struct L1LookupResult {
16 handle: MmapFileHandle,
17 hash: [u8; 32],
18}
19
20impl L1LookupResult {
21 #[inline]
23 pub fn status(&self) -> ReflexStatus {
24 ReflexStatus::HitL1Exact
25 }
26
27 #[inline]
29 pub fn handle(&self) -> &MmapFileHandle {
30 &self.handle
31 }
32
33 #[inline]
35 pub fn into_handle(self) -> MmapFileHandle {
36 self.handle
37 }
38
39 #[inline]
41 pub fn hash(&self) -> &[u8; 32] {
42 &self.hash
43 }
44
45 #[inline]
47 pub fn as_slice(&self) -> &[u8] {
48 self.handle.as_slice()
49 }
50}
51
52pub struct L1Cache {
54 entries: Cache<[u8; 32], MmapFileHandle>,
55}
56
57impl L1Cache {
58 const DEFAULT_CAPACITY: u64 = 10_000;
59
60 #[inline]
62 pub fn new() -> Self {
63 Self::with_capacity(Self::DEFAULT_CAPACITY)
64 }
65
66 #[inline]
68 pub fn with_capacity(capacity: u64) -> Self {
69 Self {
70 entries: Cache::builder().max_capacity(capacity).build(),
71 }
72 }
73
74 #[inline]
76 pub fn lookup(&self, prompt: &str) -> Option<L1LookupResult> {
77 let hash = hash_prompt(prompt);
78 self.lookup_by_hash(&hash)
79 }
80
81 #[inline]
83 pub fn lookup_by_hash(&self, hash: &[u8; 32]) -> Option<L1LookupResult> {
84 self.entries.get(hash).map(|handle| L1LookupResult {
85 handle,
86 hash: *hash,
87 })
88 }
89
90 #[inline]
92 pub fn insert(&self, prompt: &str, handle: MmapFileHandle) -> [u8; 32] {
93 let hash = hash_prompt(prompt);
94 self.entries.insert(hash, handle);
95 hash
96 }
97
98 #[inline]
100 pub fn insert_by_hash(&self, hash: [u8; 32], handle: MmapFileHandle) {
101 self.entries.insert(hash, handle);
102 }
103
104 #[inline]
106 pub fn remove(&self, hash: &[u8; 32]) -> Option<MmapFileHandle> {
107 self.entries.remove(hash)
108 }
109
110 #[inline]
112 pub fn remove_prompt(&self, prompt: &str) -> Option<MmapFileHandle> {
113 let hash = hash_prompt(prompt);
114 self.remove(&hash)
115 }
116
117 #[inline]
119 pub fn len(&self) -> u64 {
120 self.entries.entry_count()
121 }
122
123 #[inline]
125 pub fn is_empty(&self) -> bool {
126 self.entries.entry_count() == 0
127 }
128
129 #[inline]
131 pub fn clear(&self) {
132 self.entries.invalidate_all();
133 }
134
135 #[inline]
137 pub fn contains_hash(&self, hash: &[u8; 32]) -> bool {
138 self.entries.contains_key(hash)
139 }
140
141 #[inline]
143 pub fn contains_prompt(&self, prompt: &str) -> bool {
144 let hash = hash_prompt(prompt);
145 self.contains_hash(&hash)
146 }
147
148 #[inline]
150 pub fn run_pending_tasks(&self) {
151 self.entries.run_pending_tasks();
152 }
153
154 pub fn hashes(&self) -> impl Iterator<Item = [u8; 32]> {
156 self.entries.iter().map(|(k, _)| *k)
157 }
158}
159
160impl Default for L1Cache {
161 fn default() -> Self {
162 Self::new()
163 }
164}
165
166impl std::fmt::Debug for L1Cache {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.debug_struct("L1Cache")
169 .field("entries", &self.entries.entry_count())
170 .finish()
171 }
172}
173
174#[derive(Clone)]
175pub struct L1CacheHandle {
177 inner: Arc<L1Cache>,
178}
179
180impl L1CacheHandle {
181 #[inline]
183 pub fn new() -> Self {
184 Self {
185 inner: Arc::new(L1Cache::new()),
186 }
187 }
188
189 #[inline]
191 pub fn with_capacity(capacity: usize) -> Self {
192 Self {
193 inner: Arc::new(L1Cache::with_capacity(capacity as u64)),
194 }
195 }
196
197 #[inline]
199 pub fn lookup(&self, prompt: &str) -> Option<L1LookupResult> {
200 self.inner.lookup(prompt)
201 }
202
203 #[inline]
205 pub fn lookup_by_hash(&self, hash: &[u8; 32]) -> Option<L1LookupResult> {
206 self.inner.lookup_by_hash(hash)
207 }
208
209 #[inline]
211 pub fn insert(&self, prompt: &str, handle: MmapFileHandle) -> [u8; 32] {
212 self.inner.insert(prompt, handle)
213 }
214
215 #[inline]
217 pub fn insert_by_hash(&self, hash: [u8; 32], handle: MmapFileHandle) {
218 self.inner.insert_by_hash(hash, handle)
219 }
220
221 #[inline]
223 pub fn remove(&self, hash: &[u8; 32]) -> Option<MmapFileHandle> {
224 self.inner.remove(hash)
225 }
226
227 #[inline]
229 pub fn remove_prompt(&self, prompt: &str) -> Option<MmapFileHandle> {
230 self.inner.remove_prompt(prompt)
231 }
232
233 #[inline]
235 pub fn len(&self) -> usize {
236 self.inner.len() as usize
237 }
238
239 #[inline]
241 pub fn is_empty(&self) -> bool {
242 self.inner.is_empty()
243 }
244
245 #[inline]
247 pub fn clear(&self) {
248 self.inner.clear();
249 }
250
251 #[inline]
253 pub fn contains_hash(&self, hash: &[u8; 32]) -> bool {
254 self.inner.contains_hash(hash)
255 }
256
257 #[inline]
259 pub fn contains_prompt(&self, prompt: &str) -> bool {
260 self.inner.contains_prompt(prompt)
261 }
262
263 #[inline]
265 pub fn run_pending_tasks(&self) {
266 self.inner.run_pending_tasks();
267 }
268
269 #[inline]
271 pub fn strong_count(&self) -> usize {
272 Arc::strong_count(&self.inner)
273 }
274}
275
276impl Default for L1CacheHandle {
277 fn default() -> Self {
278 Self::new()
279 }
280}
281
282impl std::fmt::Debug for L1CacheHandle {
283 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
284 f.debug_struct("L1CacheHandle")
285 .field("strong_count", &self.strong_count())
286 .finish()
287 }
288}