more_fps/
time_ranges.rs

1use crate::NonZeroDecimal;
2use rust_decimal::Decimal;
3use std::num::NonZeroUsize;
4
5#[derive(Debug, PartialEq)]
6pub struct TimeRange {
7    pub start: Decimal,
8    end: NonZeroDecimal,
9}
10
11impl TimeRange {
12    pub fn duration(&self) -> NonZeroDecimal {
13        // unwrapping because this struct can only be created via
14        // TimeRange's next which has an if statement to check if start == *end
15
16        // rounding to 3 decimal places to confirm we always extract at least 1 frame
17        NonZeroDecimal::try_new((*self.end - self.start).round_dp(3)).unwrap()
18    }
19}
20
21#[derive(Debug, PartialEq)]
22pub struct TimeRanges {
23    start: Decimal,
24    max_step_size: NonZeroUsize,
25    end: NonZeroDecimal,
26}
27
28impl TimeRanges {
29    pub fn try_new<A, B, C>(start: A, max_step_size: B, end: C) -> Option<Self>
30    where
31        A: TryInto<Decimal>,
32        B: TryInto<NonZeroUsize>,
33        C: TryInto<Decimal>,
34    {
35        let start = start.try_into().ok()?;
36        let end = NonZeroDecimal::try_new(end.try_into().ok()?)?;
37        if start >= *end {
38            return None;
39        }
40        let max_step_size = max_step_size.try_into().ok()?;
41        Some(Self {
42            start,
43            max_step_size,
44            end,
45        })
46    }
47    pub fn end(&self) -> &Decimal {
48        &self.end
49    }
50}
51
52impl Iterator for TimeRanges {
53    type Item = TimeRange;
54
55    fn next(&mut self) -> Option<Self::Item> {
56        let start = self.start;
57        let end = self.end.get();
58        if start == *end {
59            return None;
60        }
61        let step_size: Decimal = self.max_step_size.get().try_into().ok()?;
62        let next_start = vec![(start + step_size).round(), *end]
63            .into_iter()
64            .min()
65            .unwrap();
66        self.start = next_start;
67        Some(TimeRange {
68            start,
69            end: NonZeroDecimal::try_new(next_start)?,
70        })
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    #[test]
79    fn flatten() {
80        // 10 seconds worth of frames
81        let max_step_size = NonZeroUsize::new(10).unwrap();
82
83        let time_ranges = vec![
84            TimeRanges::try_new(0, max_step_size, "6.675000").unwrap(),
85            TimeRanges::try_new("6.675000", max_step_size, "35").unwrap(),
86        ];
87        let actual = time_ranges
88            .into_iter()
89            .flatten()
90            .collect::<Vec<TimeRange>>();
91        let expected = vec![
92            TimeRange {
93                start: Decimal::ZERO,
94                end: "6.675000".try_into().unwrap(),
95            },
96            TimeRange {
97                start: "6.675000".try_into().unwrap(),
98                end: "17".try_into().unwrap(),
99            },
100            TimeRange {
101                start: "17".try_into().unwrap(),
102                end: "27".try_into().unwrap(),
103            },
104            TimeRange {
105                start: "27".try_into().unwrap(),
106                end: "35".try_into().unwrap(),
107            },
108        ];
109        assert_eq!(actual, expected);
110    }
111    #[test]
112    fn start_equals_end() {
113        let actual = TimeRanges::try_new(1, 2, 1);
114        assert_eq!(actual, None);
115    }
116
117    #[test]
118    fn start_gt_end() {
119        let actual = TimeRanges::try_new(2, 2, 1);
120        assert_eq!(actual, None);
121    }
122    #[test]
123    fn next_doesnt_exceed_end() {
124        let start = 0;
125
126        // 10 seconds worth of frames
127        let max_step_size = 10;
128
129        let end = Decimal::from_str_exact("9.76").unwrap();
130
131        let mut time_ranges = TimeRanges::try_new(start, max_step_size, end)
132            .unwrap()
133            .into_iter();
134        assert_eq!(
135            time_ranges.next().unwrap(),
136            TimeRange {
137                start: Decimal::ZERO,
138                end: NonZeroDecimal::try_new(end).unwrap()
139            }
140        );
141        assert!(time_ranges.next().is_none());
142    }
143
144    #[test]
145    fn start_is_zero_multiple_next() {
146        let start = 0;
147        let max_step_size = 3;
148        let end = Decimal::from_str_exact("9.76").unwrap();
149        let mut time_ranges = TimeRanges::try_new(start, max_step_size, end)
150            .unwrap()
151            .into_iter();
152        assert_eq!(
153            time_ranges.next().unwrap(),
154            TimeRange {
155                start: Decimal::ZERO,
156                end: NonZeroDecimal::try_new(max_step_size).unwrap()
157            }
158        );
159        assert_eq!(
160            time_ranges.next().unwrap(),
161            TimeRange {
162                start: max_step_size.try_into().unwrap(),
163                end: NonZeroDecimal::try_new(max_step_size * 2).unwrap()
164            }
165        );
166        assert_eq!(
167            time_ranges.next().unwrap(),
168            TimeRange {
169                start: (max_step_size * 2).try_into().unwrap(),
170                end: NonZeroDecimal::try_new(max_step_size * 3).unwrap()
171            }
172        );
173        assert_eq!(
174            time_ranges.next().unwrap(),
175            TimeRange {
176                start: (max_step_size * 3).try_into().unwrap(),
177                end: NonZeroDecimal::try_new(end).unwrap()
178            }
179        );
180        assert!(time_ranges.next().is_none());
181    }
182
183    #[test]
184    fn start_is_decimal_multiple_next() {
185        let start = Decimal::from_str_exact("522.981000").unwrap();
186        let end = Decimal::from_str_exact("568.151000").unwrap();
187        let max_step_size = 20;
188
189        let mut time_ranges = TimeRanges::try_new(start, max_step_size, end)
190            .unwrap()
191            .into_iter();
192        assert_eq!(
193            time_ranges.next().unwrap(),
194            TimeRange {
195                start,
196                end: NonZeroDecimal::try_new(543).unwrap()
197            }
198        );
199        assert_eq!(
200            time_ranges.next().unwrap(),
201            TimeRange {
202                start: Decimal::from_str_exact("543").unwrap(),
203                end: NonZeroDecimal::try_new(563).unwrap()
204            }
205        );
206        assert_eq!(
207            time_ranges.next().unwrap(),
208            TimeRange {
209                start: Decimal::from_str_exact("563").unwrap(),
210                end: NonZeroDecimal::try_new(end).unwrap()
211            }
212        );
213    }
214}