Skip to main content

autd3_rs/mirror/
transition.rs

1use crate::error::Error;
2use crate::params::NUM_BANKS;
3use crate::value::{LoopBehavior, TransitionMode};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum BankLoop {
8    Infinite,
9    Finite,
10}
11
12impl BankLoop {
13    #[must_use]
14    pub const fn of(loop_behavior: LoopBehavior) -> Self {
15        match loop_behavior {
16            LoopBehavior::Infinite => BankLoop::Infinite,
17            LoopBehavior::Finite(_) => BankLoop::Finite,
18        }
19    }
20
21    #[must_use]
22    pub const fn accepts(self, mode: TransitionMode) -> bool {
23        if mode.is_later() {
24            return true;
25        }
26        match self {
27            BankLoop::Infinite => {
28                matches!(mode, TransitionMode::Immediate | TransitionMode::Ext)
29            }
30            BankLoop::Finite => matches!(
31                mode,
32                TransitionMode::SyncIdx
33                    | TransitionMode::SysTime {
34                        time: _,
35                        margin: None
36                    }
37                    | TransitionMode::Gpio(_)
38            ),
39        }
40    }
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub struct TransitionGuardState {
45    pub mod_loop: [BankLoop; NUM_BANKS],
46    pub pattern_loop: [BankLoop; NUM_BANKS],
47}
48
49impl TransitionGuardState {
50    #[must_use]
51    pub fn boot_default() -> Self {
52        Self {
53            mod_loop: [BankLoop::Infinite; NUM_BANKS],
54            pattern_loop: [BankLoop::Infinite; NUM_BANKS],
55        }
56    }
57
58    pub fn note_mod_loop(&mut self, bank: u8, loop_behavior: LoopBehavior) {
59        self.mod_loop[usize::from(bank)] = BankLoop::of(loop_behavior);
60    }
61
62    pub fn note_pattern_loop(&mut self, bank: u8, loop_behavior: LoopBehavior) {
63        self.pattern_loop[usize::from(bank)] = BankLoop::of(loop_behavior);
64    }
65
66    pub fn check_mod_bank(
67        &self,
68        device: usize,
69        bank: u8,
70        mode: TransitionMode,
71    ) -> Result<(), Error> {
72        check(device, self.mod_loop[usize::from(bank)], mode)
73    }
74
75    pub fn check_pattern_bank(
76        &self,
77        device: usize,
78        bank: u8,
79        mode: TransitionMode,
80    ) -> Result<(), Error> {
81        check(device, self.pattern_loop[usize::from(bank)], mode)
82    }
83}
84
85fn check(device: usize, bank_loop: BankLoop, mode: TransitionMode) -> Result<(), Error> {
86    if bank_loop.accepts(mode) {
87        Ok(())
88    } else {
89        Err(Error::TransitionConstraint {
90            device,
91            transition_mode: mode,
92            bank_loop,
93        })
94    }
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use crate::value::{DcSysTime, GpioIn};
101    use core::num::NonZeroU16;
102
103    const FINITE: LoopBehavior = LoopBehavior::ONCE;
104    const INFINITE: LoopBehavior = LoopBehavior::Infinite;
105
106    #[test]
107    fn boot_default_is_all_infinite() {
108        let g = TransitionGuardState::boot_default();
109        assert_eq!(g.mod_loop, [BankLoop::Infinite; NUM_BANKS]);
110        assert_eq!(g.pattern_loop, [BankLoop::Infinite; NUM_BANKS]);
111    }
112
113    #[test]
114    fn infinite_accepts_only_immediate_and_ext() {
115        assert!(BankLoop::Infinite.accepts(TransitionMode::Immediate));
116        assert!(BankLoop::Infinite.accepts(TransitionMode::Ext));
117        assert!(!BankLoop::Infinite.accepts(TransitionMode::SyncIdx));
118        assert!(!BankLoop::Infinite.accepts(TransitionMode::SysTime {
119            time: DcSysTime::from_nanos(0),
120            margin: None
121        }));
122        assert!(!BankLoop::Infinite.accepts(TransitionMode::Gpio(GpioIn::I0)));
123    }
124
125    #[test]
126    fn finite_accepts_only_timed_modes() {
127        assert!(BankLoop::Finite.accepts(TransitionMode::SyncIdx));
128        assert!(BankLoop::Finite.accepts(TransitionMode::SysTime {
129            time: DcSysTime::from_nanos(0),
130            margin: None
131        }));
132        assert!(BankLoop::Finite.accepts(TransitionMode::Gpio(GpioIn::I0)));
133        assert!(!BankLoop::Finite.accepts(TransitionMode::Immediate));
134        assert!(!BankLoop::Finite.accepts(TransitionMode::Ext));
135    }
136
137    #[test]
138    fn later_is_outside_the_loop_constraint() {
139        assert!(BankLoop::Infinite.accepts(TransitionMode::Later));
140        assert!(BankLoop::Finite.accepts(TransitionMode::Later));
141    }
142
143    #[test]
144    fn check_mod_bank_uses_target_bank_loop() {
145        let mut g = TransitionGuardState::boot_default();
146        g.note_mod_loop(1, LoopBehavior::Finite(NonZeroU16::new(5).unwrap()));
147
148        assert!(g.check_mod_bank(0, 0, TransitionMode::Immediate).is_ok());
149        assert!(matches!(
150            g.check_mod_bank(2, 1, TransitionMode::Immediate),
151            Err(Error::TransitionConstraint {
152                device: 2,
153                transition_mode: TransitionMode::Immediate,
154                bank_loop: BankLoop::Finite,
155            })
156        ));
157        assert!(g.check_mod_bank(0, 1, TransitionMode::SyncIdx).is_ok());
158    }
159
160    #[test]
161    fn check_pattern_bank_uses_target_bank_loop() {
162        let mut g = TransitionGuardState::boot_default();
163        g.note_pattern_loop(0, FINITE);
164        g.note_pattern_loop(1, INFINITE);
165
166        assert!(
167            g.check_pattern_bank(0, 0, TransitionMode::Gpio(GpioIn::I1))
168                .is_ok()
169        );
170        assert!(g.check_pattern_bank(0, 0, TransitionMode::Ext).is_err());
171        assert!(g.check_pattern_bank(0, 1, TransitionMode::Ext).is_ok());
172        assert!(g.check_pattern_bank(0, 1, TransitionMode::SyncIdx).is_err());
173    }
174}