Skip to main content

skippy_cache/resident/
activation.rs

1use 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}