embedded_charts/
memory.rs

1//! Memory management utilities for embedded environments.
2
3use crate::data::DataPoint;
4use crate::error::{DataError, DataResult};
5use heapless::Vec;
6
7/// Fixed-capacity collections wrapper for chart data
8pub struct FixedCapacityCollections;
9
10impl FixedCapacityCollections {
11    /// Create a fixed-capacity vector for data points
12    pub fn data_vec<T: DataPoint, const N: usize>() -> Vec<T, N> {
13        Vec::new()
14    }
15
16    /// Create a fixed-capacity vector for strings (labels, etc.)
17    pub fn string_vec<const N: usize, const S: usize>() -> Vec<heapless::String<S>, N> {
18        Vec::new()
19    }
20
21    /// Create a fixed-capacity vector for colors
22    pub fn color_vec<C: Copy, const N: usize>() -> Vec<C, N> {
23        Vec::new()
24    }
25}
26
27/// Memory usage statistics for monitoring
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct MemoryStats {
30    /// Total memory allocated for chart data
31    pub total_allocated: usize,
32    /// Memory currently in use
33    pub used: usize,
34    /// Available memory
35    pub available: usize,
36    /// Peak memory usage
37    pub peak_usage: usize,
38}
39
40impl MemoryStats {
41    /// Create new memory statistics
42    pub const fn new(total: usize) -> Self {
43        Self {
44            total_allocated: total,
45            used: 0,
46            available: total,
47            peak_usage: 0,
48        }
49    }
50
51    /// Update memory usage
52    pub fn update_usage(&mut self, used: usize) {
53        self.used = used;
54        self.available = self.total_allocated.saturating_sub(used);
55        if used > self.peak_usage {
56            self.peak_usage = used;
57        }
58    }
59
60    /// Get memory utilization as a percentage
61    pub fn utilization_percent(&self) -> f32 {
62        if self.total_allocated == 0 {
63            0.0
64        } else {
65            (self.used as f32 / self.total_allocated as f32) * 100.0
66        }
67    }
68
69    /// Check if memory usage is above a threshold
70    pub fn is_above_threshold(&self, threshold_percent: f32) -> bool {
71        self.utilization_percent() > threshold_percent
72    }
73}
74
75/// Memory manager for chart operations
76pub struct ChartMemoryManager<const POOL_SIZE: usize> {
77    stats: MemoryStats,
78    high_water_mark: usize,
79}
80
81impl<const POOL_SIZE: usize> ChartMemoryManager<POOL_SIZE> {
82    /// Create a new memory manager
83    pub fn new() -> Self {
84        Self {
85            stats: MemoryStats::new(POOL_SIZE),
86            high_water_mark: 0,
87        }
88    }
89}
90
91impl<const POOL_SIZE: usize> Default for ChartMemoryManager<POOL_SIZE> {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl<const POOL_SIZE: usize> ChartMemoryManager<POOL_SIZE> {
98    /// Get current memory statistics
99    pub fn stats(&self) -> &MemoryStats {
100        &self.stats
101    }
102
103    /// Update memory usage statistics
104    pub fn update_usage(&mut self, used: usize) {
105        self.stats.update_usage(used);
106        if used > self.high_water_mark {
107            self.high_water_mark = used;
108        }
109    }
110
111    /// Get the high water mark (peak usage)
112    pub fn high_water_mark(&self) -> usize {
113        self.high_water_mark
114    }
115
116    /// Reset statistics
117    pub fn reset_stats(&mut self) {
118        self.stats = MemoryStats::new(POOL_SIZE);
119        self.high_water_mark = 0;
120    }
121
122    /// Check if memory is critically low
123    pub fn is_memory_critical(&self, threshold: f32) -> bool {
124        self.stats.is_above_threshold(threshold)
125    }
126}
127
128/// Sliding window buffer for real-time data with memory management
129#[derive(Debug, Clone)]
130pub struct ManagedSlidingWindow<T: Copy, const N: usize> {
131    buffer: [Option<T>; N],
132    head: usize,
133    count: usize,
134    full: bool,
135    memory_stats: MemoryStats,
136}
137
138impl<T: Copy, const N: usize> ManagedSlidingWindow<T, N> {
139    /// Create a new managed sliding window
140    pub fn new() -> Self {
141        Self {
142            buffer: [None; N],
143            head: 0,
144            count: 0,
145            full: false,
146            memory_stats: MemoryStats::new(N * core::mem::size_of::<T>()),
147        }
148    }
149
150    /// Push a new item into the window
151    pub fn push(&mut self, item: T) {
152        self.buffer[self.head] = Some(item);
153        self.head = (self.head + 1) % N;
154
155        if self.full {
156            // Overwriting old data, memory usage stays the same
157        } else {
158            self.count += 1;
159            if self.count == N {
160                self.full = true;
161            }
162            self.update_memory_stats();
163        }
164    }
165
166    /// Get the current number of items
167    pub fn len(&self) -> usize {
168        self.count
169    }
170
171    /// Check if the window is empty
172    pub fn is_empty(&self) -> bool {
173        self.count == 0
174    }
175
176    /// Check if the window is full
177    pub fn is_full(&self) -> bool {
178        self.full
179    }
180
181    /// Get memory statistics
182    pub fn memory_stats(&self) -> &MemoryStats {
183        &self.memory_stats
184    }
185
186    /// Clear all data
187    pub fn clear(&mut self) {
188        self.buffer = [None; N];
189        self.head = 0;
190        self.count = 0;
191        self.full = false;
192        self.update_memory_stats();
193    }
194
195    /// Get an iterator over the current items
196    pub fn iter(&self) -> impl Iterator<Item = T> + '_ {
197        let start_idx = if self.full { self.head } else { 0 };
198        let len = if self.full { N } else { self.count };
199
200        (0..len).filter_map(move |i| {
201            let idx = (start_idx + i) % N;
202            self.buffer[idx]
203        })
204    }
205
206    /// Update memory statistics
207    fn update_memory_stats(&mut self) {
208        let used = self.count * core::mem::size_of::<T>();
209        self.memory_stats.update_usage(used);
210    }
211}
212
213impl<T: Copy, const N: usize> Default for ManagedSlidingWindow<T, N> {
214    fn default() -> Self {
215        Self::new()
216    }
217}
218
219/// Memory-efficient string storage for chart labels
220#[derive(Debug, Clone)]
221pub struct LabelStorage<const MAX_LABELS: usize, const MAX_LENGTH: usize> {
222    labels: Vec<heapless::String<MAX_LENGTH>, MAX_LABELS>,
223    memory_stats: MemoryStats,
224}
225
226impl<const MAX_LABELS: usize, const MAX_LENGTH: usize> LabelStorage<MAX_LABELS, MAX_LENGTH> {
227    /// Create new label storage
228    pub fn new() -> Self {
229        Self {
230            labels: Vec::new(),
231            memory_stats: MemoryStats::new(MAX_LABELS * MAX_LENGTH),
232        }
233    }
234
235    /// Add a label to storage
236    pub fn add_label(&mut self, label: &str) -> DataResult<usize> {
237        let mut string = heapless::String::new();
238        if string.push_str(label).is_err() {
239            return Err(DataError::INVALID_DATA_POINT);
240        }
241        let index = self.labels.len();
242        self.labels
243            .push(string)
244            .map_err(|_| DataError::BUFFER_FULL)?;
245        self.update_memory_stats();
246        Ok(index)
247    }
248
249    /// Get a label by index
250    pub fn get_label(&self, index: usize) -> Option<&str> {
251        self.labels.get(index).map(|s| s.as_str())
252    }
253
254    /// Get the number of labels
255    pub fn len(&self) -> usize {
256        self.labels.len()
257    }
258
259    /// Check if storage is empty
260    pub fn is_empty(&self) -> bool {
261        self.labels.is_empty()
262    }
263
264    /// Clear all labels
265    pub fn clear(&mut self) {
266        self.labels.clear();
267        self.update_memory_stats();
268    }
269
270    /// Get memory statistics
271    pub fn memory_stats(&self) -> &MemoryStats {
272        &self.memory_stats
273    }
274
275    /// Update memory statistics
276    fn update_memory_stats(&mut self) {
277        let used = self.labels.iter().map(|s| s.len()).sum::<usize>();
278        self.memory_stats.update_usage(used);
279    }
280}
281
282impl<const MAX_LABELS: usize, const MAX_LENGTH: usize> Default
283    for LabelStorage<MAX_LABELS, MAX_LENGTH>
284{
285    fn default() -> Self {
286        Self::new()
287    }
288}
289
290#[cfg(test)]
291mod tests {
292    use super::*;
293
294    #[test]
295    fn test_memory_stats() {
296        let mut stats = MemoryStats::new(1000);
297        assert_eq!(stats.total_allocated, 1000);
298        assert_eq!(stats.used, 0);
299        assert_eq!(stats.available, 1000);
300
301        stats.update_usage(300);
302        assert_eq!(stats.used, 300);
303        assert_eq!(stats.available, 700);
304        assert!((stats.utilization_percent() - 30.0).abs() < 0.001);
305    }
306
307    #[test]
308    fn test_managed_sliding_window() {
309        let mut window: ManagedSlidingWindow<i32, 3> = ManagedSlidingWindow::new();
310        assert!(window.is_empty());
311        assert!(!window.is_full());
312
313        window.push(1);
314        window.push(2);
315        window.push(3);
316
317        assert_eq!(window.len(), 3);
318        assert!(window.is_full());
319
320        // Test overwriting
321        window.push(4);
322        assert_eq!(window.len(), 3);
323
324        let values: Vec<i32, 3> = window.iter().collect();
325        assert_eq!(values.as_slice(), &[2, 3, 4]);
326    }
327
328    #[test]
329    fn test_label_storage() {
330        let mut storage: LabelStorage<5, 20> = LabelStorage::new();
331        assert!(storage.is_empty());
332
333        let index1 = storage.add_label("Label 1").unwrap();
334        let index2 = storage.add_label("Label 2").unwrap();
335
336        assert_eq!(storage.len(), 2);
337        assert_eq!(storage.get_label(index1), Some("Label 1"));
338        assert_eq!(storage.get_label(index2), Some("Label 2"));
339    }
340
341    #[test]
342    fn test_memory_manager() {
343        let mut manager: ChartMemoryManager<1000> = ChartMemoryManager::new();
344        assert_eq!(manager.stats().total_allocated, 1000);
345
346        manager.update_usage(500);
347        assert_eq!(manager.high_water_mark(), 500);
348        assert!(!manager.is_memory_critical(60.0));
349        assert!(manager.is_memory_critical(40.0));
350    }
351}