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        // Placeholder timestamps; `curr_index`/`full` track which entries are real.
26        Self { curr_index: 0, full: false, values: [(Instant::default(), Vector2D::default()); N] }
27    }
28}
29
30impl<const N: usize> VelocityRingBuffer<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: LogicalVector) {
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    fn len(&self) -> usize {
54        if self.full { N } else { self.curr_index }
55    }
56
57    /// Returns the last time value added to the buffer if not empty otherwise None
58    pub fn last_time(&self) -> Option<Instant> {
59        if !self.empty() { Some(self.values[self.latest_index()].0) } else { None }
60    }
61
62    pub fn mean_velocity(&self) -> LogicalVector {
63        let len = self.len();
64        if len < 2 {
65            return Default::default();
66        }
67
68        let oldest_index = if self.full { self.curr_index } else { 0 };
69        let newest_index = self.latest_index();
70        let duration = self.values[newest_index].0.duration_since(self.values[oldest_index].0);
71        if duration == Duration::ZERO {
72            return Default::default();
73        }
74
75        // The oldest recorded delta happened before the oldest timestamp in the covered time span,
76        // so it does not belong to the average velocity between oldest and newest.
77        let mut total_delta = LogicalVector::default();
78        let mut index = (oldest_index + 1) % N;
79        for _ in 1..len {
80            total_delta += self.values[index].1;
81            index = (index + 1) % N;
82        }
83
84        (total_delta.cast::<f32>() / duration.as_secs_f32()).cast::<Coord>()
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use crate::animations::Instant;
92    use core::time::Duration;
93
94    #[test]
95    fn test_empty_buffer() {
96        let buffer: VelocityRingBuffer<5> = VelocityRingBuffer::default();
97        assert!(buffer.empty());
98        assert_eq!(buffer.curr_index, 0);
99        assert!(!buffer.full);
100        assert_eq!(buffer.last_time(), None);
101        assert_eq!(buffer.mean_velocity(), Vector2D::default());
102    }
103
104    #[test]
105    fn test_push_single_element() {
106        let mut buffer: VelocityRingBuffer<5> = VelocityRingBuffer::default();
107        let time = Instant::default();
108        let delta = Vector2D::new(10.0, 20.0);
109
110        buffer.push(time, delta);
111
112        assert!(!buffer.empty());
113        assert_eq!(buffer.curr_index, 1);
114        assert!(!buffer.full);
115        assert_eq!(buffer.latest_index(), 0);
116        assert_eq!(buffer.last_time(), Some(time));
117        assert_eq!(buffer.mean_velocity(), Vector2D::default());
118    }
119
120    /// Buffer not complete full
121    #[test]
122    fn test_push_two_elements() {
123        let mut buffer: VelocityRingBuffer<5> = VelocityRingBuffer::default();
124        let time = Instant::default();
125
126        buffer.push(time, Vector2D::new(10.0, 20.0));
127        buffer.push(time + Duration::from_millis(100), Vector2D::new(13.0, -5.0));
128
129        assert!(!buffer.empty());
130        assert_eq!(buffer.curr_index, 2);
131        assert!(!buffer.full);
132        assert_eq!(buffer.latest_index(), 1);
133        assert_eq!(buffer.last_time(), Some(time + Duration::from_millis(100)));
134
135        assert_eq!(buffer.mean_velocity(), Vector2D::new(130.0, -50.0));
136    }
137
138    #[test]
139    fn test_push_until_full() {
140        let mut buffer: VelocityRingBuffer<5> = VelocityRingBuffer::default();
141        let base_time = Instant::default();
142
143        // Push elements to fill the buffer
144        for i in 0..5 {
145            let time = base_time + Duration::from_millis(i * 100);
146            buffer.push(time, Vector2D::new(1.0, -2.0));
147        }
148
149        assert!(!buffer.empty());
150        assert_eq!(buffer.curr_index, 0);
151        assert!(buffer.full);
152        assert_eq!(buffer.last_time(), Some(base_time + Duration::from_millis(400)));
153        assert_eq!(buffer.latest_index(), 4);
154
155        assert_eq!(buffer.mean_velocity(), Vector2D::new(10.0, -20.0));
156    }
157
158    #[test]
159    fn test_push_beyond_capacity() {
160        const CAP: usize = 5;
161        let mut buffer: VelocityRingBuffer<CAP> = VelocityRingBuffer::default();
162        let base_time = Instant::default();
163
164        // Push more than capacity
165        for i in 0..(CAP + 2) {
166            let time = base_time + Duration::from_millis(i as u64 * 100);
167            buffer.push(time, Vector2D::new(1.0, 2.0));
168        }
169
170        assert!(!buffer.empty());
171        assert!(buffer.full);
172        assert_eq!(buffer.curr_index, 2);
173        assert_eq!(buffer.latest_index(), 1);
174        assert_eq!(buffer.last_time(), Some(base_time + Duration::from_millis(600)));
175
176        assert_eq!(buffer.mean_velocity(), Vector2D::new(10.0, 20.0));
177    }
178
179    #[test]
180    fn test_push_beyond_capacity_wrap_back() {
181        const CAP: usize = 5;
182        let mut buffer: VelocityRingBuffer<CAP> = VelocityRingBuffer::default();
183        let base_time = Instant::default();
184
185        // Push more than capacity
186        for i in 0..CAP {
187            let time = base_time + Duration::from_millis(i as u64 * 100);
188            buffer.push(time, Vector2D::new(3.0, -2.0));
189        }
190
191        assert!(!buffer.empty());
192        assert!(buffer.full);
193        assert_eq!(buffer.curr_index, 0);
194        assert_eq!(buffer.latest_index(), CAP - 1);
195        assert_eq!(buffer.last_time(), Some(base_time + Duration::from_millis(400)));
196
197        assert_eq!(buffer.mean_velocity(), Vector2D::new(30.0, -20.0));
198    }
199}