skippy_cache/resident/
activation.rs1use std::collections::HashMap;
2
3use crate::ResidentCacheConfig;
4
5#[derive(Debug)]
6pub struct ResidentActivationCache<F> {
7 max_entries: usize,
8 max_bytes: u64,
9 min_tokens: u64,
10 clock: u64,
11 resident_bytes: u64,
12 entries: HashMap<String, ResidentActivationEntry<F>>,
13}
14
15#[derive(Debug, Clone)]
16struct ResidentActivationEntry<F> {
17 token_count: u64,
18 byte_size: u64,
19 last_used: u64,
20 frame: F,
21}
22
23#[derive(Debug, Clone)]
24pub struct ResidentActivationLookup<F> {
25 pub token_count: u64,
26 pub byte_size: u64,
27 pub entries: usize,
28 pub frame: F,
29}
30
31#[derive(Debug, Clone)]
32pub struct ResidentActivationRecordOutcome {
33 pub evicted_entries: usize,
34 pub evicted_bytes: u64,
35}
36
37#[derive(Debug, Clone, Copy, Default)]
38pub struct ResidentActivationStats {
39 pub entries: usize,
40 pub resident_bytes: u64,
41}
42
43impl<F: Clone> ResidentActivationCache<F> {
44 pub fn new(config: ResidentCacheConfig) -> Self {
45 Self {
46 max_entries: config.max_entries,
47 max_bytes: config.max_bytes,
48 min_tokens: config.min_tokens,
49 clock: 0,
50 resident_bytes: 0,
51 entries: HashMap::new(),
52 }
53 }
54
55 pub fn lookup(&mut self, page_id: &str) -> Option<ResidentActivationLookup<F>> {
56 self.clock = self.clock.saturating_add(1);
57 let entries = self.entries.len();
58 let entry = self.entries.get_mut(page_id)?;
59 entry.last_used = self.clock;
60 Some(ResidentActivationLookup {
61 token_count: entry.token_count,
62 byte_size: entry.byte_size,
63 entries,
64 frame: entry.frame.clone(),
65 })
66 }
67
68 pub fn record(
69 &mut self,
70 page_id: String,
71 token_count: u64,
72 byte_size: u64,
73 frame: F,
74 ) -> ResidentActivationRecordOutcome {
75 if token_count < self.min_tokens || byte_size == 0 {
76 return ResidentActivationRecordOutcome {
77 evicted_entries: 0,
78 evicted_bytes: 0,
79 };
80 }
81 self.clock = self.clock.saturating_add(1);
82 if let Some(previous) = self.entries.remove(&page_id) {
83 self.resident_bytes = self.resident_bytes.saturating_sub(previous.byte_size);
84 }
85 let (evicted_entries, evicted_bytes) = self.evict_until_room_for(byte_size);
86 self.resident_bytes = self.resident_bytes.saturating_add(byte_size);
87 self.entries.insert(
88 page_id,
89 ResidentActivationEntry {
90 token_count,
91 byte_size,
92 last_used: self.clock,
93 frame,
94 },
95 );
96 ResidentActivationRecordOutcome {
97 evicted_entries,
98 evicted_bytes,
99 }
100 }
101
102 pub fn stats(&self) -> ResidentActivationStats {
103 ResidentActivationStats {
104 entries: self.entries.len(),
105 resident_bytes: self.resident_bytes,
106 }
107 }
108
109 fn evict_until_room_for(&mut self, byte_size: u64) -> (usize, u64) {
110 let mut evicted_entries = 0usize;
111 let mut evicted_bytes = 0u64;
112 loop {
113 let over_entries = self.entries.len().saturating_add(1) > self.max_entries;
114 let over_bytes = self.max_bytes > 0
115 && self.resident_bytes.saturating_add(byte_size) > self.max_bytes;
116 if !over_entries && !over_bytes {
117 break;
118 }
119 let Some(victim) = self
120 .entries
121 .iter()
122 .min_by_key(|(_, entry)| entry.last_used)
123 .map(|(key, _)| key.clone())
124 else {
125 break;
126 };
127 if let Some(entry) = self.entries.remove(&victim) {
128 self.resident_bytes = self.resident_bytes.saturating_sub(entry.byte_size);
129 evicted_entries = evicted_entries.saturating_add(1);
130 evicted_bytes = evicted_bytes.saturating_add(entry.byte_size);
131 }
132 }
133 (evicted_entries, evicted_bytes)
134 }
135}