| 1 | /// A plan's place in time. Every phase lasts `phase_ns`. |
| 2 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
| 3 | pub struct Timeline { |
| 4 | pub start_ns: u64, |
| 5 | pub phase_ns: u64, |
| 6 | pub phases: usize, |
| 7 | } |
| 8 | |
| 9 | #[derive(Clone, Copy, Debug, PartialEq)] |
| 10 | pub enum Position { |
| 11 | NotStarted, |
| 12 | /// Inside `phase`, with linear time `t` in [0, 1) before easing. |
| 13 | Phase { |
| 14 | phase: usize, |
| 15 | t: f64, |
| 16 | }, |
| 17 | Finished, |
| 18 | } |
| 19 | |
| 20 | impl Timeline { |
| 21 | pub fn end_ns(&self) -> u64 { |
| 22 | self.start_ns + self.phase_ns * self.phases as u64 |
| 23 | } |
| 24 | |
| 25 | pub fn locate(&self, now_ns: u64) -> Position { |
| 26 | if now_ns < self.start_ns { |
| 27 | return Position::NotStarted; |
| 28 | } |
| 29 | if self.phase_ns == 0 || now_ns >= self.end_ns() { |
| 30 | return Position::Finished; |
| 31 | } |
| 32 | let elapsed = now_ns - self.start_ns; |
| 33 | let phase = (elapsed / self.phase_ns) as usize; |
| 34 | let t = (elapsed % self.phase_ns) as f64 / self.phase_ns as f64; |
| 35 | Position::Phase { phase, t } |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | #[cfg(test)] |
| 40 | mod tests { |
| 41 | use super::*; |
| 42 | |
| 43 | #[test] |
| 44 | fn locates_phases() { |
| 45 | let tl = Timeline { |
| 46 | start_ns: 1_000, |
| 47 | phase_ns: 100, |
| 48 | phases: 3, |
| 49 | }; |
| 50 | assert_eq!(tl.locate(999), Position::NotStarted); |
| 51 | assert_eq!(tl.locate(1_000), Position::Phase { phase: 0, t: 0.0 }); |
| 52 | assert_eq!(tl.locate(1_150), Position::Phase { phase: 1, t: 0.5 }); |
| 53 | assert_eq!(tl.locate(1_300), Position::Finished); |
| 54 | } |
| 55 | |
| 56 | #[test] |
| 57 | fn total_time_is_phases_times_duration() { |
| 58 | let tl = Timeline { |
| 59 | start_ns: 0, |
| 60 | phase_ns: 160_000_000, |
| 61 | phases: 4, |
| 62 | }; |
| 63 | assert_eq!(tl.end_ns(), 640_000_000); |
| 64 | } |
| 65 | } |