Skip to main content

i_slint_core/items/flickable/
data_ringbuffer.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4//! This module contains a simple ringbuffer to store time and location tuples. It is used in the flickable to
5//! determine the initial velocity of the animation
6
7use crate::Coord;
8use crate::animations::Instant;
9use crate::lengths::LogicalPoint;
10use crate::lengths::LogicalPx;
11use core::time::Duration;
12use euclid::Vector2D;
13
14/// Simple ringbuffer storing time and position tuples
15#[derive(Debug)]
16pub(crate) struct PositionTimeRingBuffer<const N: usize> {
17    /// Pointing to the next free element
18    curr_index: usize,
19    /// Indicates if the buffer is full
20    full: bool,
21    values: [(Instant, LogicalPoint); N],
22}
23
24impl<const N: usize> Default for PositionTimeRingBuffer<N> {
25    fn default() -> Self {
26        Self { curr_index: 0, full: false, values: [(Instant::now(), LogicalPoint::default()); N] }
27    }
28}
29
30impl<const N: usize> PositionTimeRingBuffer<N> {
31    /// Indicates if the buffer is empty
32    pub fn empty(&self) -> bool {
33        !(self.full || self.curr_index > 0)
34    }
35
36    /// Add a new element to the ringbuffer
37    pub fn push(&mut self, time: Instant, value: LogicalPoint) {
38        if self.curr_index < self.values.len() {
39            self.values[self.curr_index] = (time, value);
40        }
41        self.curr_index += 1;
42        if self.curr_index >= N {
43            self.full = true;
44            self.curr_index = 0;
45        }
46    }
47
48    /// Index of the most recent added value
49    fn latest_index(&self) -> usize {
50        if self.curr_index > 0 { self.curr_index - 1 } else { N - 1 }
51    }
52
53    /// Returns the last time value added to the buffer if not empty otherwise None
54    pub fn last_time(&self) -> Option<Instant> {
55        if !self.empty() { Some(self.values[self.latest_index()].0) } else { None }
56    }
57
58    /// Returns the difference between the oldest and the newest point
59    pub fn diff(&self) -> (Duration, Vector2D<Coord, LogicalPx>) {
60        if self.full {
61            let oldest = self.values[self.curr_index];
62            let newest = if self.curr_index > 0 {
63                self.values[self.curr_index - 1]
64            } else {
65                self.values[self.values.len() - 1]
66            };
67            (newest.0.duration_since(oldest.0), newest.1 - oldest.1)
68        } else {
69            let oldest = self.values[0];
70            let newest = self.values[usize::max(0, self.curr_index - 1)];
71            (newest.0.duration_since(oldest.0), newest.1 - oldest.1)
72        }
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::animations::Instant;
80    use crate::lengths::LogicalPoint;
81    use core::time::Duration;
82
83    #[test]
84    fn test_empty_buffer() {
85        let buffer: PositionTimeRingBuffer<5> = PositionTimeRingBuffer::default();
86        assert!(buffer.empty());
87        assert_eq!(buffer.curr_index, 0);
88        assert!(!buffer.full);
89        assert_eq!(buffer.last_time(), None);
90    }
91
92    #[test]
93    fn test_push_single_element() {
94        let mut buffer: PositionTimeRingBuffer<5> = PositionTimeRingBuffer::default();
95        let time = Instant::now();
96        let point = LogicalPoint::new(10.0, 20.0);
97
98        buffer.push(time, point);
99
100        assert!(!buffer.empty());
101        assert_eq!(buffer.curr_index, 1);
102        assert!(!buffer.full);
103        assert_eq!(buffer.latest_index(), 0);
104        assert_eq!(buffer.last_time(), Some(time));
105
106        assert_eq!(buffer.diff(), (Duration::from_millis(0), Vector2D::new(0., 0.)));
107    }
108
109    /// Buffer not complete full
110    #[test]
111    fn test_push_two_elements() {
112        let mut buffer: PositionTimeRingBuffer<5> = PositionTimeRingBuffer::default();
113        let time = Instant::now();
114
115        buffer.push(time, LogicalPoint::new(10.0, 20.0));
116        buffer.push(time + Duration::from_millis(13), LogicalPoint::new(13.0, -5.0));
117
118        assert!(!buffer.empty());
119        assert_eq!(buffer.curr_index, 2);
120        assert!(!buffer.full);
121        assert_eq!(buffer.latest_index(), 1);
122        assert_eq!(buffer.last_time(), Some(time + Duration::from_millis(13)));
123
124        assert_eq!(buffer.diff(), (Duration::from_millis(13), Vector2D::new(3., -25.)));
125    }
126
127    #[test]
128    fn test_push_until_full() {
129        let mut buffer: PositionTimeRingBuffer<5> = PositionTimeRingBuffer::default();
130        let base_time = Instant::now();
131
132        // Push elements to fill the buffer
133        for i in 0..5 {
134            let time = base_time + Duration::from_millis(i * 3 as u64);
135            let point = LogicalPoint::new(i as f32, -2. * i as f32);
136            buffer.push(time, point);
137        }
138
139        assert!(!buffer.empty());
140        assert_eq!(buffer.curr_index, 0);
141        assert!(buffer.full);
142        assert_eq!(buffer.last_time(), Some(base_time + Duration::from_millis(4 * 3)));
143        assert_eq!(buffer.latest_index(), 4);
144
145        assert_eq!(buffer.diff(), (Duration::from_millis(12), Vector2D::new(4., -8.)));
146    }
147
148    #[test]
149    fn test_push_beyond_capacity() {
150        const CAP: usize = 5;
151        let mut buffer: PositionTimeRingBuffer<CAP> = PositionTimeRingBuffer::default();
152        let base_time = Instant::now();
153
154        // Push more than capacity
155        for i in 0..(CAP + 2) {
156            let time = base_time + Duration::from_millis(i as u64);
157            let point = LogicalPoint::new(i as f32, i as f32 * 2. + 100.);
158            buffer.push(time, point);
159        }
160
161        assert!(!buffer.empty());
162        assert!(buffer.full);
163        assert_eq!(buffer.curr_index, 2);
164        assert_eq!(buffer.latest_index(), 1);
165        assert_eq!(buffer.last_time(), Some(base_time + Duration::from_millis(6)));
166
167        assert_eq!(buffer.diff(), (Duration::from_millis(4), Vector2D::new(4., 4. * 2.)));
168    }
169
170    #[test]
171    fn test_push_beyond_capacity_wrap_back() {
172        const CAP: usize = 5;
173        let mut buffer: PositionTimeRingBuffer<CAP> = PositionTimeRingBuffer::default();
174        let base_time = Instant::now();
175
176        // Push more than capacity
177        for i in 0..CAP {
178            let time = base_time + Duration::from_millis(i as u64);
179            let point = LogicalPoint::new(i as f32 * 3., i as f32 * -2. + 100.);
180            buffer.push(time, point);
181        }
182
183        assert!(!buffer.empty());
184        assert!(buffer.full);
185        assert_eq!(buffer.curr_index, 0);
186        assert_eq!(buffer.latest_index(), CAP - 1);
187        assert_eq!(buffer.last_time(), Some(base_time + Duration::from_millis(4)));
188
189        // Wrapping back must be done
190        assert_eq!(buffer.diff(), (Duration::from_millis(4), Vector2D::new(4. * 3., 4. * -2.)));
191    }
192}