Skip to main content

termichart_data/
series.rs

1/// A fixed-capacity ring buffer that holds a sliding window of data items.
2///
3/// When the buffer is full, pushing a new item overwrites the oldest entry.
4/// Items are always presented in chronological order (oldest first).
5#[derive(Debug, Clone)]
6pub struct DataSeries<T: Clone> {
7    buf: Vec<T>,
8    capacity: usize,
9    head: usize,
10    len: usize,
11}
12
13impl<T: Clone> DataSeries<T> {
14    /// Creates a new empty `DataSeries` with the given capacity.
15    ///
16    /// # Panics
17    ///
18    /// Panics if `capacity` is zero.
19    pub fn new(capacity: usize) -> Self {
20        assert!(capacity > 0, "DataSeries capacity must be > 0");
21        Self {
22            buf: Vec::with_capacity(capacity),
23            capacity,
24            head: 0,
25            len: 0,
26        }
27    }
28
29    /// Pushes an item into the ring buffer in O(1) time.
30    ///
31    /// If the buffer is already at capacity the oldest item is overwritten.
32    pub fn push(&mut self, item: T) {
33        if self.buf.len() < self.capacity {
34            // Buffer not yet full -- just append.
35            self.buf.push(item);
36            self.len = self.buf.len();
37            // head stays at 0 while filling up.
38        } else {
39            // Buffer full -- overwrite at head position.
40            self.buf[self.head] = item;
41            self.head = (self.head + 1) % self.capacity;
42            // len stays at capacity.
43        }
44    }
45
46    /// Returns the number of items currently stored.
47    pub fn len(&self) -> usize {
48        self.len
49    }
50
51    /// Returns `true` if the buffer contains no items.
52    pub fn is_empty(&self) -> bool {
53        self.len == 0
54    }
55
56    /// Returns the maximum capacity of the buffer.
57    pub fn capacity(&self) -> usize {
58        self.capacity
59    }
60
61    /// Returns a reference to the item at `index` where 0 is the oldest item.
62    ///
63    /// Returns `None` if `index` is out of bounds.
64    pub fn get(&self, index: usize) -> Option<&T> {
65        if index >= self.len {
66            return None;
67        }
68        if self.len < self.capacity {
69            // Buffer hasn't wrapped yet.
70            Some(&self.buf[index])
71        } else {
72            // Buffer is full and has wrapped -- head points to the oldest slot.
73            let actual = (self.head + index) % self.capacity;
74            Some(&self.buf[actual])
75        }
76    }
77
78    /// Returns an iterator that yields references from oldest to newest.
79    pub fn iter(&self) -> impl Iterator<Item = &T> {
80        let len = self.len;
81        let cap = self.capacity;
82        let head = if self.len < self.capacity { 0 } else { self.head };
83        let buf = &self.buf;
84        (0..len).map(move |i| {
85            let actual = (head + i) % cap;
86            &buf[actual]
87        })
88    }
89
90    /// Returns all items as a `Vec<T>` ordered oldest-first.
91    pub fn as_slice(&self) -> Vec<T> {
92        self.iter().cloned().collect()
93    }
94
95    /// Returns a reference to the newest (most recently pushed) item.
96    pub fn last(&self) -> Option<&T> {
97        if self.len == 0 {
98            return None;
99        }
100        if self.len < self.capacity {
101            Some(&self.buf[self.len - 1])
102        } else {
103            // head points to the next write position, which is the oldest.
104            // newest is one step before head.
105            let idx = (self.head + self.capacity - 1) % self.capacity;
106            Some(&self.buf[idx])
107        }
108    }
109
110    /// Removes all items, resetting the buffer to empty.
111    pub fn clear(&mut self) {
112        self.buf.clear();
113        self.head = 0;
114        self.len = 0;
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn basic_push_and_get() {
124        let mut s = DataSeries::new(4);
125        s.push(10);
126        s.push(20);
127        s.push(30);
128        assert_eq!(s.len(), 3);
129        assert_eq!(s.get(0), Some(&10));
130        assert_eq!(s.get(1), Some(&20));
131        assert_eq!(s.get(2), Some(&30));
132        assert_eq!(s.get(3), None);
133    }
134
135    #[test]
136    fn wraps_around() {
137        let mut s = DataSeries::new(3);
138        s.push(1);
139        s.push(2);
140        s.push(3);
141        s.push(4); // overwrites 1
142        assert_eq!(s.len(), 3);
143        assert_eq!(s.get(0), Some(&2));
144        assert_eq!(s.get(1), Some(&3));
145        assert_eq!(s.get(2), Some(&4));
146    }
147
148    #[test]
149    fn iter_order() {
150        let mut s = DataSeries::new(3);
151        s.push(1);
152        s.push(2);
153        s.push(3);
154        s.push(4);
155        let v: Vec<_> = s.iter().copied().collect();
156        assert_eq!(v, vec![2, 3, 4]);
157    }
158
159    #[test]
160    fn as_slice_matches_iter() {
161        let mut s = DataSeries::new(3);
162        for i in 0..5 {
163            s.push(i);
164        }
165        let from_iter: Vec<_> = s.iter().copied().collect();
166        assert_eq!(s.as_slice(), from_iter);
167    }
168
169    #[test]
170    fn last_returns_newest() {
171        let mut s = DataSeries::new(3);
172        assert!(s.last().is_none());
173        s.push(1);
174        assert_eq!(s.last(), Some(&1));
175        s.push(2);
176        s.push(3);
177        s.push(4);
178        assert_eq!(s.last(), Some(&4));
179    }
180
181    #[test]
182    fn clear_resets() {
183        let mut s = DataSeries::new(3);
184        s.push(1);
185        s.push(2);
186        s.clear();
187        assert!(s.is_empty());
188        assert_eq!(s.len(), 0);
189        assert!(s.get(0).is_none());
190    }
191
192    #[test]
193    fn capacity_is_correct() {
194        let s: DataSeries<i32> = DataSeries::new(10);
195        assert_eq!(s.capacity(), 10);
196    }
197
198    #[test]
199    #[should_panic]
200    fn zero_capacity_panics() {
201        let _s: DataSeries<i32> = DataSeries::new(0);
202    }
203}