lasercube_core/
buffer.rs

1//! Buffer management for LaserCube devices.
2
3/// Default buffer size from observed devices.
4pub const DEFAULT_SIZE: u16 = 6_000;
5/// Recommended buffer threshold for maintaining stability vs latency
6pub const DEFAULT_THRESHOLD: u16 = 5_000;
7
8/// Tracks the state of the LaserCube's buffer.
9#[derive(Debug, Clone, Copy)]
10pub struct BufferState {
11    /// Total buffer size.
12    pub total_size: u16,
13    /// Current free space in the buffer.
14    pub free_space: u16,
15    /// Threshold for deciding when to send more data.
16    pub threshold: u16,
17    /// Last time we received a buffer update (in milliseconds since start).
18    pub last_update_time: u64,
19}
20
21impl BufferState {
22    pub const DEFAULT: Self = Self {
23        total_size: DEFAULT_SIZE,
24        free_space: DEFAULT_SIZE,
25        threshold: DEFAULT_THRESHOLD,
26        last_update_time: 0,
27    };
28
29    /// Create a new `BufferState` with default values.
30    pub fn new() -> Self {
31        Self::DEFAULT
32    }
33
34    /// Update buffer free space from device response.
35    pub fn update_free_space(&mut self, free_space: u16, current_time: u64) {
36        self.free_space = free_space;
37        self.last_update_time = current_time;
38    }
39
40    /// Update total buffer size from device response.
41    pub fn update_total_size(&mut self, total_size: u16) {
42        self.total_size = total_size;
43
44        // Adjust threshold to be a percentage of total size
45        // Maintain latency vs stability tradeoff
46        if total_size > 1000 {
47            self.threshold = total_size - 1000;
48        } else {
49            // Fallback for very small buffers
50            self.threshold = total_size / 6 * 5;
51        }
52    }
53
54    /// Check if we should send more data based on buffer free space.
55    pub fn should_send(&self) -> bool {
56        self.free_space >= self.threshold
57    }
58
59    /// Estimate current free space based on time elapsed and DAC rate.
60    pub fn estimate_current_free_space(&self, current_time: u64, dac_rate: u32) -> u16 {
61        if dac_rate == 0 || self.last_update_time == 0 {
62            return self.free_space;
63        }
64
65        // Calculate time delta in milliseconds
66        let delta_ms = if current_time > self.last_update_time {
67            current_time - self.last_update_time
68        } else {
69            // Handle possible timer wraparound
70            0
71        };
72
73        // Convert from DAC rate (points per second) to points per millisecond
74        let points_per_ms = dac_rate as f32 / 1000.0;
75
76        // Calculate estimated points consumed
77        let points_consumed = (delta_ms as f32 * points_per_ms) as u16;
78
79        // Add to free space, but don't exceed total buffer size
80        let estimated_free = self
81            .free_space
82            .saturating_add(points_consumed)
83            .min(self.total_size);
84
85        estimated_free
86    }
87
88    /// Update the buffer when points are sent.
89    pub fn consume(&mut self, points_sent: u16) {
90        self.free_space = self.free_space.saturating_sub(points_sent);
91    }
92}
93
94impl Default for BufferState {
95    fn default() -> Self {
96        Self::DEFAULT
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_buffer_defaults() {
106        let buffer = BufferState::new();
107
108        assert_eq!(buffer.total_size, DEFAULT_SIZE);
109        assert_eq!(buffer.free_space, DEFAULT_SIZE);
110        assert_eq!(buffer.threshold, DEFAULT_THRESHOLD);
111        assert_eq!(buffer.last_update_time, 0);
112    }
113
114    #[test]
115    fn test_update_free_space() {
116        let mut buffer = BufferState::new();
117
118        // Test updating free space
119        buffer.update_free_space(3000, 100);
120        assert_eq!(buffer.free_space, 3000);
121        assert_eq!(buffer.last_update_time, 100);
122
123        // Test updating free space again
124        buffer.update_free_space(4000, 200);
125        assert_eq!(buffer.free_space, 4000);
126        assert_eq!(buffer.last_update_time, 200);
127    }
128
129    #[test]
130    fn test_update_total_size() {
131        let mut buffer = BufferState::new();
132
133        // Test with normal buffer size
134        buffer.update_total_size(8000);
135        assert_eq!(buffer.total_size, 8000);
136        assert_eq!(buffer.threshold, 7000); // 8000 - 1000
137
138        // Test with small buffer size
139        buffer.update_total_size(600);
140        assert_eq!(buffer.total_size, 600);
141        assert_eq!(buffer.threshold, 500); // 600 / 6 * 5
142    }
143
144    #[test]
145    fn test_should_send() {
146        let mut buffer = BufferState::new();
147        buffer.threshold = 4000;
148
149        // Test when free space is below threshold
150        buffer.free_space = 3999;
151        assert!(!buffer.should_send());
152
153        // Test when free space is at threshold
154        buffer.free_space = 4000;
155        assert!(buffer.should_send());
156
157        // Test when free space is above threshold
158        buffer.free_space = 4001;
159        assert!(buffer.should_send());
160    }
161
162    #[test]
163    fn test_estimate_current_free_space() {
164        let mut buffer = BufferState::new();
165        buffer.total_size = 6000;
166        buffer.free_space = 3000;
167        buffer.last_update_time = 1000;
168
169        // Test with zero DAC rate
170        let estimate = buffer.estimate_current_free_space(2000, 0);
171        assert_eq!(estimate, 3000); // Should remain unchanged
172
173        // Test with non-zero DAC rate (1000 points per second)
174        // 1000 ms elapsed, 1000 points per second = 1000 points
175        let estimate = buffer.estimate_current_free_space(2000, 1000);
176        assert_eq!(estimate, 4000); // 3000 + 1000
177
178        // Test that estimate doesn't exceed total size
179        buffer.free_space = 5500;
180        let estimate = buffer.estimate_current_free_space(2000, 1000);
181        assert_eq!(estimate, 6000); // Capped at total_size
182
183        // Test with time wraparound (current time < last update time)
184        buffer.free_space = 3000;
185        buffer.last_update_time = 2000;
186        let estimate = buffer.estimate_current_free_space(1000, 1000);
187        assert_eq!(estimate, 3000); // Should remain unchanged
188    }
189
190    #[test]
191    fn test_consume() {
192        let mut buffer = BufferState::new();
193        buffer.free_space = 5000;
194
195        // Test normal consumption
196        buffer.consume(1000);
197        assert_eq!(buffer.free_space, 4000);
198
199        // Test consumption that would go below zero
200        buffer.consume(5000);
201        assert_eq!(buffer.free_space, 0); // Should saturate at 0
202    }
203
204    #[test]
205    fn test_integrated_buffer_scenario() {
206        // Simulating a realistic usage scenario
207        let mut buffer = BufferState::new();
208
209        // Initialize with device info
210        buffer.update_total_size(6000);
211        buffer.update_free_space(6000, 100);
212
213        // Send some points
214        buffer.consume(1000);
215        assert_eq!(buffer.free_space, 5000);
216
217        // Device renders some points over time
218        // 500ms passes, DAC rate is 1000 points/sec
219        let estimate = buffer.estimate_current_free_space(600, 1000);
220        assert_eq!(estimate, 5500); // 5000 + (500 * 1000 / 1000)
221
222        // Update with actual device reported free space
223        buffer.update_free_space(5400, 600); // Maybe some overhead in actual device
224
225        // Send more points
226        buffer.consume(2000);
227        assert_eq!(buffer.free_space, 3400);
228
229        // Check if we should send more
230        assert!(!buffer.should_send()); // 3400 < 5000 threshold
231    }
232}