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