Skip to main content

nv_view/
transition.rs

1//! Transition phase state machine for camera motion events.
2
3/// Where in a camera motion transition the current frame sits.
4///
5/// This is a strict state machine updated every frame by the view system.
6///
7/// ## State transitions
8///
9/// | Previous | Current motion | Next |
10/// |---|---|---|
11/// | `Settled` | `Stable` | `Settled` |
12/// | `Settled` | `Moving`/`Unknown` | `MoveStart` |
13/// | `MoveStart` | `Moving`/`Unknown` | `Moving` |
14/// | `MoveStart` | `Stable` | `MoveEnd` |
15/// | `Moving` | `Moving`/`Unknown` | `Moving` |
16/// | `Moving` | `Stable` | `MoveEnd` |
17/// | `MoveEnd` | `Stable` | `Settled` |
18/// | `MoveEnd` | `Moving`/`Unknown` | `MoveStart` |
19///
20/// `MoveStart` and `MoveEnd` each last exactly one frame (edge-triggered).
21/// `Settled` and `Moving` persist across multiple frames (level-triggered).
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23pub enum TransitionPhase {
24    /// No transition in progress. Camera is (or has been) stable.
25    Settled,
26    /// First frame of detected motion.
27    MoveStart,
28    /// Camera is mid-move.
29    Moving,
30    /// First frame of detected stability after motion.
31    MoveEnd,
32}
33
34impl TransitionPhase {
35    /// Compute the next phase given the current motion state.
36    ///
37    /// `is_moving` should be `true` if the camera motion state is
38    /// `Moving` or `Unknown` (conservative: unknown = potentially moving).
39    #[must_use]
40    pub fn next(self, is_moving: bool) -> Self {
41        match (self, is_moving) {
42            (Self::Settled, false) => Self::Settled,
43            (Self::Settled, true) => Self::MoveStart,
44            (Self::MoveStart, true) => Self::Moving,
45            (Self::MoveStart, false) => Self::MoveEnd,
46            (Self::Moving, true) => Self::Moving,
47            (Self::Moving, false) => Self::MoveEnd,
48            (Self::MoveEnd, false) => Self::Settled,
49            (Self::MoveEnd, true) => Self::MoveStart,
50        }
51    }
52}
53
54#[cfg(test)]
55mod tests {
56    use super::*;
57
58    #[test]
59    fn settled_stays_settled() {
60        assert_eq!(
61            TransitionPhase::Settled.next(false),
62            TransitionPhase::Settled
63        );
64    }
65
66    #[test]
67    fn settled_to_move_start() {
68        assert_eq!(
69            TransitionPhase::Settled.next(true),
70            TransitionPhase::MoveStart
71        );
72    }
73
74    #[test]
75    fn move_start_to_moving() {
76        assert_eq!(
77            TransitionPhase::MoveStart.next(true),
78            TransitionPhase::Moving
79        );
80    }
81
82    #[test]
83    fn move_start_one_frame_jitter() {
84        assert_eq!(
85            TransitionPhase::MoveStart.next(false),
86            TransitionPhase::MoveEnd
87        );
88    }
89
90    #[test]
91    fn moving_to_move_end() {
92        assert_eq!(
93            TransitionPhase::Moving.next(false),
94            TransitionPhase::MoveEnd
95        );
96    }
97
98    #[test]
99    fn move_end_to_settled() {
100        assert_eq!(
101            TransitionPhase::MoveEnd.next(false),
102            TransitionPhase::Settled
103        );
104    }
105
106    #[test]
107    fn move_end_resume_motion() {
108        assert_eq!(
109            TransitionPhase::MoveEnd.next(true),
110            TransitionPhase::MoveStart
111        );
112    }
113
114    #[test]
115    fn full_cycle() {
116        let mut phase = TransitionPhase::Settled;
117        phase = phase.next(true); // MoveStart
118        assert_eq!(phase, TransitionPhase::MoveStart);
119        phase = phase.next(true); // Moving
120        assert_eq!(phase, TransitionPhase::Moving);
121        phase = phase.next(true); // Moving
122        assert_eq!(phase, TransitionPhase::Moving);
123        phase = phase.next(false); // MoveEnd
124        assert_eq!(phase, TransitionPhase::MoveEnd);
125        phase = phase.next(false); // Settled
126        assert_eq!(phase, TransitionPhase::Settled);
127    }
128}