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