Skip to main content

pictorus_blocks/core_blocks/
timer_block.rs

1use pictorus_block_data::BlockData as OldBlockData;
2use pictorus_traits::{PassBy, ProcessBlock, Scalar};
3
4#[derive(strum::EnumString)]
5pub enum Method {
6    CountDown,
7    StopWatch,
8}
9
10/// Parameters for the TimerBlock
11pub struct Parameters {
12    /// Method of the TimerBlock: CountDown or StopWatch. CountDown will count down from
13    /// the countdown_time_s, while StopWatch will count up from 0.
14    pub method: Method,
15    /// If the TimerBlock is interruptable. If this is true and the trigger is > 0, the timer will restart.
16    pub interruptable: bool,
17    /// The time in seconds to count down from. Only used if method is CountDown.
18    pub countdown_time_s: f64,
19}
20
21impl Parameters {
22    pub fn new(method: &str, interruptable: bool, countdown_time_s: f64) -> Parameters {
23        Parameters {
24            method: method.parse().expect("Faile to parse Timer Method"),
25            interruptable,
26            countdown_time_s,
27        }
28    }
29}
30
31/// The Timer block allows timekeeping around discrete events - either by Stopwatch mode or Countdown mode.
32///
33/// The input signal serves as the trigger to start the timer. Any input which is "True" will commence the timer.
34/// If the Interruptible option is enabled, the timer will reset every iteration where the input is True.
35/// Otherwise, the timer will commence on the first True value. It will then either count down and reset at zero,
36/// or count up without ever restarting.
37///
38/// This block is useful for tracking how much time has passed since an event for logical conditions.
39pub struct TimerBlock<T> {
40    pub data: OldBlockData,
41    buffer: T,
42    timer_running: bool,
43    start_time_s: T,
44}
45
46impl<T> Default for TimerBlock<T>
47where
48    T: Scalar + num_traits::Zero,
49{
50    fn default() -> Self {
51        Self {
52            data: OldBlockData::from_scalar(0.0),
53            buffer: T::zero(),
54            timer_running: false,
55            start_time_s: T::zero(),
56        }
57    }
58}
59
60impl TimerBlock<f64> {
61    fn _do_countdown(&mut self, time_since_start: f64, countdown_time_s: f64) {
62        if time_since_start < countdown_time_s {
63            self.buffer = countdown_time_s - time_since_start;
64        } else {
65            self.buffer = 0.0;
66            self.timer_running = false;
67        }
68    }
69
70    fn _do_stopwatch(&mut self, time_since_start: f64) {
71        self.buffer = time_since_start;
72    }
73}
74
75impl ProcessBlock for TimerBlock<f64> {
76    type Inputs = f64;
77    type Output = f64;
78    type Parameters = Parameters;
79
80    fn process(
81        &mut self,
82        parameters: &Self::Parameters,
83        context: &dyn pictorus_traits::Context,
84        input: PassBy<Self::Inputs>,
85    ) -> PassBy<Self::Output> {
86        let time = context.time().as_secs_f64();
87
88        let trigger_high = input > 0.0;
89        // Early exit if not running and input trigger is false
90        if !self.timer_running && !trigger_high {
91            self.data.set_scalar(self.buffer);
92            return self.buffer;
93        }
94
95        if trigger_high {
96            if !self.timer_running {
97                // Start the timer
98                self.start_time_s = time;
99                self.timer_running = true;
100            } else if self.timer_running && parameters.interruptable {
101                // Interrupt and restart the timer
102                self.start_time_s = time;
103            }
104        }
105
106        let time_since_start = time - self.start_time_s;
107
108        match parameters.method {
109            Method::CountDown => {
110                self._do_countdown(time_since_start, parameters.countdown_time_s);
111            }
112            Method::StopWatch => {
113                self._do_stopwatch(time_since_start);
114            }
115        }
116
117        self.data.set_scalar(self.buffer);
118        self.buffer
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use crate::testing::StubRuntime;
125    use core::time;
126
127    use super::*;
128
129    #[test]
130    fn test_countdown_timer_non_interruptable() {
131        let mut runtime = StubRuntime::default();
132        let p = Parameters::new("CountDown", false, 5.0);
133        let mut block = TimerBlock::<f64>::default();
134
135        let output = block.process(&p, &runtime.context(), 0.0);
136        assert_eq!(block.data.scalar(), 0.0);
137        assert_eq!(output, 0.0);
138
139        runtime.set_time(time::Duration::from_secs_f64(1.0));
140        let output = block.process(&p, &runtime.context(), 1.0);
141        assert_eq!(block.data.scalar(), 5.0);
142        assert_eq!(output, 5.0);
143
144        runtime.set_time(time::Duration::from_secs_f64(2.0));
145        let output = block.process(&p, &runtime.context(), 0.0);
146        assert_eq!(block.data.scalar(), 4.0);
147        assert_eq!(output, 4.0);
148
149        // Countdown not interrupted
150        runtime.set_time(time::Duration::from_secs_f64(3.0));
151        let output = block.process(&p, &runtime.context(), 1.0);
152        assert_eq!(block.data.scalar(), 3.0);
153        assert_eq!(output, 3.0);
154
155        runtime.set_time(time::Duration::from_secs_f64(10.0));
156        let output = block.process(&p, &runtime.context(), 0.0);
157        assert_eq!(block.data.scalar(), 0.0);
158        assert_eq!(output, 0.0);
159
160        runtime.set_time(time::Duration::from_secs_f64(11.0));
161        let output = block.process(&p, &runtime.context(), 0.0);
162        assert_eq!(block.data.scalar(), 0.0);
163        assert_eq!(output, 0.0);
164    }
165
166    #[test]
167    fn test_countdown_timer_interruptable() {
168        let mut runtime = StubRuntime::default();
169        let p = Parameters::new("CountDown", true, 5.0);
170        let mut block = TimerBlock::<f64>::default();
171
172        // Timer hasn't started
173        runtime.set_time(time::Duration::from_secs_f64(1.0));
174        let output = block.process(&p, &runtime.context(), 0.0);
175        assert_eq!(block.data.scalar(), 0.0);
176        assert_eq!(output, 0.0);
177
178        // Timer started, should be at countdown_time_s
179        runtime.set_time(time::Duration::from_secs_f64(2.0));
180        let output = block.process(&p, &runtime.context(), 1.0);
181        assert_eq!(block.data.scalar(), 5.0);
182        assert_eq!(output, 5.0);
183
184        runtime.set_time(time::Duration::from_secs_f64(3.0));
185        let output = block.process(&p, &runtime.context(), 0.0);
186        assert_eq!(block.data.scalar(), 4.0);
187        assert_eq!(output, 4.0);
188
189        // Countdown interrupted, resets
190        runtime.set_time(time::Duration::from_secs_f64(4.0));
191        let output = block.process(&p, &runtime.context(), 1.0);
192        assert_eq!(block.data.scalar(), 5.0);
193        assert_eq!(output, 5.0);
194
195        // Countdown interrupted, resets
196        runtime.set_time(time::Duration::from_secs_f64(5.0));
197        let output = block.process(&p, &runtime.context(), 1.0);
198        assert_eq!(block.data.scalar(), 5.0);
199        assert_eq!(output, 5.0);
200
201        // Countdown resumes
202        runtime.set_time(time::Duration::from_secs_f64(6.0));
203        let output = block.process(&p, &runtime.context(), 0.0);
204        assert_eq!(block.data.scalar(), 4.0);
205        assert_eq!(output, 4.0);
206    }
207
208    #[test]
209    fn test_stopwatch_timer_non_interruptable() {
210        let mut runtime = StubRuntime::default();
211        let p = Parameters::new("StopWatch", false, 5.0);
212        let mut block = TimerBlock::<f64>::default();
213
214        // Timer hasn't started
215        runtime.set_time(time::Duration::from_secs_f64(1.0));
216        let output = block.process(&p, &runtime.context(), 0.0);
217        assert_eq!(block.data.scalar(), 0.0);
218        assert_eq!(output, 0.0);
219
220        // Timer started, should be at time since start
221        runtime.set_time(time::Duration::from_secs_f64(2.0));
222        let output = block.process(&p, &runtime.context(), 1.0);
223        assert_eq!(block.data.scalar(), 0.0);
224        assert_eq!(output, 0.0);
225
226        runtime.set_time(time::Duration::from_secs_f64(3.0));
227        let output = block.process(&p, &runtime.context(), 0.0);
228        assert_eq!(block.data.scalar(), 1.0);
229        assert_eq!(output, 1.0);
230
231        // StopWatch not interrupted
232        runtime.set_time(time::Duration::from_secs_f64(4.0));
233        let output = block.process(&p, &runtime.context(), 1.0);
234        assert_eq!(block.data.scalar(), 2.0);
235        assert_eq!(output, 2.0);
236
237        runtime.set_time(time::Duration::from_secs_f64(10.0));
238        let output = block.process(&p, &runtime.context(), 0.0);
239        assert_eq!(block.data.scalar(), 8.0);
240        assert_eq!(output, 8.0);
241
242        runtime.set_time(time::Duration::from_secs_f64(100.0));
243        let output = block.process(&p, &runtime.context(), 0.0);
244        assert_eq!(block.data.scalar(), 98.0);
245        assert_eq!(output, 98.0);
246    }
247
248    #[test]
249    fn test_stopwatch_timer_interruptable() {
250        let mut runtime = StubRuntime::default();
251        let p = Parameters::new("StopWatch", true, 5.0);
252        let mut block = TimerBlock::<f64>::default();
253
254        // Timer hasn't started
255        runtime.set_time(time::Duration::from_secs_f64(1.0));
256        let output = block.process(&p, &runtime.context(), 0.0);
257        assert_eq!(block.data.scalar(), 0.0);
258        assert_eq!(output, 0.0);
259
260        // Timer started, should be at time since start
261        runtime.set_time(time::Duration::from_secs_f64(2.0));
262        let output = block.process(&p, &runtime.context(), 1.0);
263        assert_eq!(block.data.scalar(), 0.0);
264        assert_eq!(output, 0.0);
265
266        runtime.set_time(time::Duration::from_secs_f64(3.0));
267        let output = block.process(&p, &runtime.context(), 0.0);
268        assert_eq!(block.data.scalar(), 1.0);
269        assert_eq!(output, 1.0);
270
271        // StopWatch interrupted
272        runtime.set_time(time::Duration::from_secs_f64(4.0));
273        let output = block.process(&p, &runtime.context(), 1.0);
274        assert_eq!(block.data.scalar(), 0.0);
275        assert_eq!(output, 0.0);
276
277        // StopWatch resumes
278        runtime.set_time(time::Duration::from_secs_f64(10.0));
279        let output = block.process(&p, &runtime.context(), 0.0);
280        assert_eq!(block.data.scalar(), 6.0);
281        assert_eq!(output, 6.0);
282
283        runtime.set_time(time::Duration::from_secs_f64(100.0));
284        let output = block.process(&p, &runtime.context(), 0.0);
285        assert_eq!(block.data.scalar(), 96.0);
286        assert_eq!(output, 96.0);
287    }
288}