libspot_rs/
ubend.rs

1//! Circular buffer implementation (Ubend)
2//!
3//! This module implements a circular buffer that matches the C implementation exactly.
4//! The Ubend structure is a kind of circular vector that starts empty, fills up to capacity,
5//! and then overwrites older data with newer data.
6
7use crate::error::{SpotError, SpotResult};
8
9/// Circular buffer implementation that matches the C Ubend structure
10///
11/// # Serialization
12///
13/// When the `serde` feature is enabled, this struct can be serialized and deserialized.
14/// This is useful for saving the state of a SPOT detector and restoring it later.
15#[derive(Debug, Clone)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub struct Ubend {
18    /// Current position inside the container
19    cursor: usize,
20    /// Maximum storage capacity
21    capacity: usize,
22    /// Last erased value (i.e., replaced by a new one)
23    #[cfg_attr(feature = "serde", serde(with = "crate::ser::nan_safe_f64"))]
24    last_erased_data: f64,
25    /// Container fill status (true = filled, false = not filled)
26    filled: bool,
27    /// Data container
28    data: Vec<f64>,
29}
30
31impl Ubend {
32    /// Initialize a new Ubend with the given capacity
33    pub fn new(capacity: usize) -> SpotResult<Self> {
34        if capacity == 0 {
35            return Err(SpotError::MemoryAllocationFailed);
36        }
37
38        Ok(Self {
39            cursor: 0,
40            filled: false,
41            capacity,
42            last_erased_data: f64::NAN,
43            data: vec![0.0; capacity],
44        })
45    }
46
47    /// Get the current size of the container
48    /// Returns capacity if filled, otherwise returns cursor position
49    pub fn size(&self) -> usize {
50        if self.filled {
51            self.capacity
52        } else {
53            self.cursor
54        }
55    }
56
57    /// Push a new value into the container
58    /// Returns the value that was erased (if any), otherwise NaN
59    pub fn push(&mut self, x: f64) -> f64 {
60        // If the container has already been filled, we must keep in memory
61        // the data we will erase
62        if self.filled {
63            self.last_erased_data = self.data[self.cursor];
64        }
65
66        // Assign value at cursor
67        self.data[self.cursor] = x;
68
69        // Increment cursor
70        if self.cursor == self.capacity - 1 {
71            self.cursor = 0;
72            self.filled = true;
73        } else {
74            self.cursor += 1;
75        }
76
77        self.last_erased_data
78    }
79
80    /// Get iterator over the data in insertion order
81    pub fn iter(&self) -> UbendIterator<'_> {
82        UbendIterator {
83            ubend: self,
84            index: 0,
85        }
86    }
87
88    /// Get the data at a specific index in insertion order
89    pub fn get(&self, index: usize) -> Option<f64> {
90        let size = self.size();
91        if index >= size {
92            return None;
93        }
94
95        if !self.filled {
96            // Simple case: data is contiguous from 0 to cursor-1
97            Some(self.data[index])
98        } else {
99            // Complex case: data wraps around
100            let real_index = (self.cursor + index) % self.capacity;
101            Some(self.data[real_index])
102        }
103    }
104
105    /// Access to raw data (for compatibility with C implementation)
106    pub fn raw_data(&self) -> &[f64] {
107        &self.data
108    }
109
110    /// Get capacity
111    pub fn capacity(&self) -> usize {
112        self.capacity
113    }
114
115    /// Check if the buffer is filled
116    pub fn is_filled(&self) -> bool {
117        self.filled
118    }
119
120    /// Get current cursor position
121    pub fn cursor(&self) -> usize {
122        self.cursor
123    }
124
125    /// Get last erased data
126    pub fn last_erased_data(&self) -> f64 {
127        self.last_erased_data
128    }
129
130    /// Get all data in insertion order as a vector
131    pub fn data(&self) -> Vec<f64> {
132        self.iter().collect()
133    }
134}
135
136/// Iterator over Ubend data in insertion order
137pub struct UbendIterator<'a> {
138    ubend: &'a Ubend,
139    index: usize,
140}
141
142impl<'a> Iterator for UbendIterator<'a> {
143    type Item = f64;
144
145    fn next(&mut self) -> Option<Self::Item> {
146        let result = self.ubend.get(self.index);
147        self.index += 1;
148        result
149    }
150}
151
152impl<'a> ExactSizeIterator for UbendIterator<'a> {
153    fn len(&self) -> usize {
154        self.ubend.size()
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use approx::assert_relative_eq;
162
163    #[test]
164    fn test_ubend_creation() {
165        let ubend = Ubend::new(5).unwrap();
166        assert_eq!(ubend.capacity(), 5);
167        assert_eq!(ubend.size(), 0);
168        assert!(!ubend.is_filled());
169        assert_eq!(ubend.cursor(), 0);
170        assert!(ubend.last_erased_data().is_nan());
171    }
172
173    #[test]
174    fn test_ubend_zero_capacity() {
175        let result = Ubend::new(0);
176        assert!(result.is_err());
177        assert_eq!(result.unwrap_err(), SpotError::MemoryAllocationFailed);
178    }
179
180    #[test]
181    fn test_ubend_push_before_full() {
182        let mut ubend = Ubend::new(3).unwrap();
183
184        // Push first element
185        let erased = ubend.push(1.0);
186        assert!(erased.is_nan());
187        assert_eq!(ubend.size(), 1);
188        assert!(!ubend.is_filled());
189        assert_eq!(ubend.cursor(), 1);
190
191        // Push second element
192        let erased = ubend.push(2.0);
193        assert!(erased.is_nan());
194        assert_eq!(ubend.size(), 2);
195        assert!(!ubend.is_filled());
196        assert_eq!(ubend.cursor(), 2);
197
198        // Push third element
199        let erased = ubend.push(3.0);
200        assert!(erased.is_nan());
201        assert_eq!(ubend.size(), 3);
202        assert!(ubend.is_filled());
203        assert_eq!(ubend.cursor(), 0);
204    }
205
206    #[test]
207    fn test_ubend_push_after_full() {
208        let mut ubend = Ubend::new(3).unwrap();
209
210        // Fill the buffer
211        ubend.push(1.0);
212        ubend.push(2.0);
213        ubend.push(3.0);
214
215        // Now it should start overwriting
216        let erased = ubend.push(4.0);
217        assert_relative_eq!(erased, 1.0);
218        assert_eq!(ubend.size(), 3);
219        assert!(ubend.is_filled());
220        assert_eq!(ubend.cursor(), 1);
221
222        let erased = ubend.push(5.0);
223        assert_relative_eq!(erased, 2.0);
224        assert_eq!(ubend.size(), 3);
225        assert!(ubend.is_filled());
226        assert_eq!(ubend.cursor(), 2);
227    }
228
229    #[test]
230    fn test_ubend_get() {
231        let mut ubend = Ubend::new(3).unwrap();
232
233        // Test empty buffer
234        assert!(ubend.get(0).is_none());
235
236        // Add some data
237        ubend.push(10.0);
238        ubend.push(20.0);
239
240        assert_relative_eq!(ubend.get(0).unwrap(), 10.0);
241        assert_relative_eq!(ubend.get(1).unwrap(), 20.0);
242        assert!(ubend.get(2).is_none());
243
244        // Fill buffer and test wraparound
245        ubend.push(30.0);
246        ubend.push(40.0); // This should overwrite 10.0
247
248        assert_relative_eq!(ubend.get(0).unwrap(), 20.0);
249        assert_relative_eq!(ubend.get(1).unwrap(), 30.0);
250        assert_relative_eq!(ubend.get(2).unwrap(), 40.0);
251    }
252
253    #[test]
254    fn test_ubend_iterator() {
255        let mut ubend = Ubend::new(3).unwrap();
256
257        ubend.push(1.0);
258        ubend.push(2.0);
259        ubend.push(3.0);
260
261        let values: Vec<f64> = ubend.iter().collect();
262        assert_eq!(values, vec![1.0, 2.0, 3.0]);
263
264        // Test after wraparound
265        ubend.push(4.0);
266        let values: Vec<f64> = ubend.iter().collect();
267        assert_eq!(values, vec![2.0, 3.0, 4.0]);
268    }
269
270    #[test]
271    fn test_ubend_exact_size_iterator() {
272        let mut ubend = Ubend::new(3).unwrap();
273
274        assert_eq!(ubend.iter().len(), 0);
275
276        ubend.push(1.0);
277        assert_eq!(ubend.iter().len(), 1);
278
279        ubend.push(2.0);
280        ubend.push(3.0);
281        assert_eq!(ubend.iter().len(), 3);
282
283        ubend.push(4.0);
284        assert_eq!(ubend.iter().len(), 3);
285    }
286}