Skip to main content

oxihuman_core/
checkpoint_store.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// Stores named checkpoints of serialised state for save/restore workflows.
6#[allow(dead_code)]
7#[derive(Debug, Clone)]
8pub struct Checkpoint {
9    pub name: String,
10    pub data: Vec<u8>,
11    pub timestamp: u64,
12}
13
14#[allow(dead_code)]
15#[derive(Debug, Clone)]
16pub struct CheckpointStore {
17    checkpoints: Vec<Checkpoint>,
18    max_checkpoints: usize,
19    clock: u64,
20}
21
22#[allow(dead_code)]
23impl CheckpointStore {
24    pub fn new(max_checkpoints: usize) -> Self {
25        Self {
26            checkpoints: Vec::new(),
27            max_checkpoints: max_checkpoints.max(1),
28            clock: 0,
29        }
30    }
31
32    pub fn advance_clock(&mut self, dt: u64) {
33        self.clock += dt;
34    }
35
36    pub fn save(&mut self, name: &str, data: Vec<u8>) {
37        if let Some(cp) = self.checkpoints.iter_mut().find(|c| c.name == name) {
38            cp.data = data;
39            cp.timestamp = self.clock;
40            return;
41        }
42        if self.checkpoints.len() >= self.max_checkpoints {
43            self.checkpoints.remove(0);
44        }
45        self.checkpoints.push(Checkpoint {
46            name: name.to_string(),
47            data,
48            timestamp: self.clock,
49        });
50    }
51
52    pub fn restore(&self, name: &str) -> Option<&[u8]> {
53        self.checkpoints
54            .iter()
55            .find(|c| c.name == name)
56            .map(|c| c.data.as_slice())
57    }
58
59    pub fn remove(&mut self, name: &str) -> bool {
60        let before = self.checkpoints.len();
61        self.checkpoints.retain(|c| c.name != name);
62        self.checkpoints.len() < before
63    }
64
65    pub fn count(&self) -> usize {
66        self.checkpoints.len()
67    }
68
69    pub fn names(&self) -> Vec<&str> {
70        self.checkpoints.iter().map(|c| c.name.as_str()).collect()
71    }
72
73    pub fn latest(&self) -> Option<&Checkpoint> {
74        self.checkpoints.iter().max_by_key(|c| c.timestamp)
75    }
76
77    pub fn oldest(&self) -> Option<&Checkpoint> {
78        self.checkpoints.iter().min_by_key(|c| c.timestamp)
79    }
80
81    pub fn total_bytes(&self) -> usize {
82        self.checkpoints.iter().map(|c| c.data.len()).sum()
83    }
84
85    pub fn contains(&self, name: &str) -> bool {
86        self.checkpoints.iter().any(|c| c.name == name)
87    }
88
89    pub fn clear(&mut self) {
90        self.checkpoints.clear();
91    }
92
93    pub fn max_checkpoints(&self) -> usize {
94        self.max_checkpoints
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn test_new() {
104        let cs = CheckpointStore::new(5);
105        assert_eq!(cs.count(), 0);
106        assert_eq!(cs.max_checkpoints(), 5);
107    }
108
109    #[test]
110    fn test_save_and_restore() {
111        let mut cs = CheckpointStore::new(5);
112        cs.save("cp1", vec![1, 2, 3]);
113        assert_eq!(cs.restore("cp1"), Some([1u8, 2, 3].as_slice()));
114    }
115
116    #[test]
117    fn test_overwrite() {
118        let mut cs = CheckpointStore::new(5);
119        cs.save("cp1", vec![1]);
120        cs.save("cp1", vec![2]);
121        assert_eq!(cs.restore("cp1"), Some([2u8].as_slice()));
122        assert_eq!(cs.count(), 1);
123    }
124
125    #[test]
126    fn test_max_eviction() {
127        let mut cs = CheckpointStore::new(2);
128        cs.save("a", vec![1]);
129        cs.save("b", vec![2]);
130        cs.save("c", vec![3]);
131        assert_eq!(cs.count(), 2);
132        assert!(!cs.contains("a"));
133        assert!(cs.contains("c"));
134    }
135
136    #[test]
137    fn test_remove() {
138        let mut cs = CheckpointStore::new(5);
139        cs.save("a", vec![1]);
140        assert!(cs.remove("a"));
141        assert!(!cs.remove("a"));
142    }
143
144    #[test]
145    fn test_latest() {
146        let mut cs = CheckpointStore::new(5);
147        cs.save("a", vec![1]);
148        cs.advance_clock(10);
149        cs.save("b", vec![2]);
150        assert_eq!(cs.latest().expect("should succeed").name, "b");
151    }
152
153    #[test]
154    fn test_oldest() {
155        let mut cs = CheckpointStore::new(5);
156        cs.save("a", vec![1]);
157        cs.advance_clock(10);
158        cs.save("b", vec![2]);
159        assert_eq!(cs.oldest().expect("should succeed").name, "a");
160    }
161
162    #[test]
163    fn test_total_bytes() {
164        let mut cs = CheckpointStore::new(5);
165        cs.save("a", vec![1, 2]);
166        cs.save("b", vec![3, 4, 5]);
167        assert_eq!(cs.total_bytes(), 5);
168    }
169
170    #[test]
171    fn test_names() {
172        let mut cs = CheckpointStore::new(5);
173        cs.save("x", vec![1]);
174        cs.save("y", vec![2]);
175        assert_eq!(cs.names().len(), 2);
176    }
177
178    #[test]
179    fn test_clear() {
180        let mut cs = CheckpointStore::new(5);
181        cs.save("a", vec![1]);
182        cs.clear();
183        assert_eq!(cs.count(), 0);
184    }
185}