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