Skip to main content

autocore_std/fb/
simple_timer.rs

1use std::time::{Duration, Instant};
2
3/// Simple One-Shot Timer
4///
5/// A simple timer for one-shot timing operations. Unlike [`super::Ton`], this timer
6/// does NOT follow the IEC 61131-3 standard and is designed for imperative
7/// "start then check" patterns rather than cyclic function block calls.
8///
9/// **Note:** For standard PLC timer patterns (delay while condition is true),
10/// use [`super::Ton`] instead. `SimpleTimer` is intended for one-shot use cases
11/// like "do X, wait, then do Y".
12///
13/// # Safety
14///
15/// This timer uses [`Instant`] (monotonic clock) internally, making it immune
16/// to system clock adjustments (NTP sync, manual changes, DST). This is
17/// critical for reliable timing in industrial control applications.
18///
19/// # Example
20///
21/// ```
22/// use autocore_std::fb::SimpleTimer;
23/// use std::time::Duration;
24///
25/// let mut timer = SimpleTimer::new(Duration::from_millis(100));
26///
27/// // Timer hasn't started yet
28/// assert_eq!(timer.is_done(), false);
29/// assert_eq!(timer.elapsed(), Duration::ZERO);
30///
31/// // Start the timer
32/// timer.start();
33///
34/// // Check if done (will be false initially)
35/// assert_eq!(timer.is_done(), false);
36///
37/// // After waiting...
38/// std::thread::sleep(Duration::from_millis(110));
39/// assert_eq!(timer.is_done(), true);
40///
41/// // Reset for reuse
42/// timer.reset();
43/// assert_eq!(timer.is_done(), false);
44/// ```
45///
46/// # Use Cases
47///
48/// - One-shot delays ("wait 500ms then continue")
49/// - Tracking time since an event occurred
50/// - Non-cyclic async contexts
51/// - Simple timeout checks
52///
53/// # Comparison with Ton
54///
55/// | Aspect | `Ton` | `SimpleTimer` |
56/// |--------|-------|---------------|
57/// | IEC 61131-3 | Yes | No |
58/// | Pattern | Cyclic `call()` | Imperative `start()`/`is_done()` |
59/// | Auto-reset on disable | Yes | No (explicit `reset()`) |
60/// | Best for | PLC-style control | One-shot operations |
61#[derive(Debug, Clone)]
62pub struct SimpleTimer {
63    start_time: Option<Instant>,
64    preset: Duration,
65}
66
67impl SimpleTimer {
68    /// Creates a new simple timer with the specified preset duration.
69    ///
70    /// The timer starts in the stopped state and must be explicitly started
71    /// with [`start()`](Self::start).
72    ///
73    /// # Arguments
74    ///
75    /// * `preset` - The duration after which [`is_done()`](Self::is_done) returns `true`
76    ///
77    /// # Example
78    ///
79    /// ```
80    /// use autocore_std::fb::SimpleTimer;
81    /// use std::time::Duration;
82    ///
83    /// let timer = SimpleTimer::new(Duration::from_secs(5));
84    /// assert_eq!(timer.is_done(), false);
85    /// ```
86    pub fn new(preset: Duration) -> Self {
87        Self {
88            start_time: None,
89            preset,
90        }
91    }
92
93    /// Starts (or restarts) the timer.
94    ///
95    /// Records the current time as the start time. If the timer was already
96    /// running, this restarts it from zero.
97    ///
98    /// # Example
99    ///
100    /// ```
101    /// use autocore_std::fb::SimpleTimer;
102    /// use std::time::Duration;
103    ///
104    /// let mut timer = SimpleTimer::new(Duration::from_millis(100));
105    /// timer.start();
106    /// // Timer is now counting...
107    /// ```
108    pub fn start(&mut self) {
109        self.start_time = Some(Instant::now());
110    }
111
112    /// Checks if the preset time has elapsed since the timer was started.
113    ///
114    /// Returns `false` if the timer hasn't been started yet.
115    ///
116    /// # Returns
117    ///
118    /// `true` if the timer was started and the preset duration has elapsed,
119    /// `false` otherwise.
120    ///
121    /// # Example
122    ///
123    /// ```
124    /// use autocore_std::fb::SimpleTimer;
125    /// use std::time::Duration;
126    ///
127    /// let mut timer = SimpleTimer::new(Duration::from_millis(50));
128    ///
129    /// // Not started yet
130    /// assert_eq!(timer.is_done(), false);
131    ///
132    /// timer.start();
133    /// std::thread::sleep(Duration::from_millis(60));
134    /// assert_eq!(timer.is_done(), true);
135    /// ```
136    pub fn is_done(&self) -> bool {
137        self.start_time
138            .map(|t| t.elapsed() >= self.preset)
139            .unwrap_or(false)
140    }
141
142    /// Returns the elapsed time since the timer was started.
143    ///
144    /// Returns [`Duration::ZERO`] if the timer hasn't been started yet.
145    ///
146    /// # Returns
147    ///
148    /// The elapsed time since [`start()`](Self::start) was called, or
149    /// `Duration::ZERO` if not started.
150    ///
151    /// # Example
152    ///
153    /// ```
154    /// use autocore_std::fb::SimpleTimer;
155    /// use std::time::Duration;
156    ///
157    /// let mut timer = SimpleTimer::new(Duration::from_secs(10));
158    ///
159    /// // Not started
160    /// assert_eq!(timer.elapsed(), Duration::ZERO);
161    ///
162    /// timer.start();
163    /// std::thread::sleep(Duration::from_millis(50));
164    /// assert!(timer.elapsed() >= Duration::from_millis(50));
165    /// ```
166    pub fn elapsed(&self) -> Duration {
167        self.start_time
168            .map(|t| t.elapsed())
169            .unwrap_or(Duration::ZERO)
170    }
171
172    /// Resets the timer to its initial (stopped) state.
173    ///
174    /// After calling this, [`is_done()`](Self::is_done) will return `false`
175    /// and [`elapsed()`](Self::elapsed) will return zero until
176    /// [`start()`](Self::start) is called again.
177    ///
178    /// # Example
179    ///
180    /// ```
181    /// use autocore_std::fb::SimpleTimer;
182    /// use std::time::Duration;
183    ///
184    /// let mut timer = SimpleTimer::new(Duration::from_millis(50));
185    /// timer.start();
186    /// std::thread::sleep(Duration::from_millis(60));
187    /// assert_eq!(timer.is_done(), true);
188    ///
189    /// timer.reset();
190    /// assert_eq!(timer.is_done(), false);
191    /// assert_eq!(timer.elapsed(), Duration::ZERO);
192    /// ```
193    pub fn reset(&mut self) {
194        self.start_time = None;
195    }
196
197    /// Returns the preset duration for this timer.
198    ///
199    /// # Example
200    ///
201    /// ```
202    /// use autocore_std::fb::SimpleTimer;
203    /// use std::time::Duration;
204    ///
205    /// let timer = SimpleTimer::new(Duration::from_secs(5));
206    /// assert_eq!(timer.preset(), Duration::from_secs(5));
207    /// ```
208    pub fn preset(&self) -> Duration {
209        self.preset
210    }
211
212    /// Sets a new preset duration.
213    ///
214    /// This does not reset or restart the timer. If the timer is running,
215    /// the new preset takes effect immediately for [`is_done()`](Self::is_done)
216    /// checks.
217    ///
218    /// # Arguments
219    ///
220    /// * `preset` - The new duration after which `is_done()` returns `true`
221    ///
222    /// # Example
223    ///
224    /// ```
225    /// use autocore_std::fb::SimpleTimer;
226    /// use std::time::Duration;
227    ///
228    /// let mut timer = SimpleTimer::new(Duration::from_secs(5));
229    /// timer.set_preset(Duration::from_secs(10));
230    /// assert_eq!(timer.preset(), Duration::from_secs(10));
231    /// ```
232    pub fn set_preset(&mut self, preset: Duration) {
233        self.preset = preset;
234    }
235
236    /// Returns whether the timer is currently running (has been started but not reset).
237    ///
238    /// # Example
239    ///
240    /// ```
241    /// use autocore_std::fb::SimpleTimer;
242    /// use std::time::Duration;
243    ///
244    /// let mut timer = SimpleTimer::new(Duration::from_secs(5));
245    /// assert_eq!(timer.is_running(), false);
246    ///
247    /// timer.start();
248    /// assert_eq!(timer.is_running(), true);
249    ///
250    /// timer.reset();
251    /// assert_eq!(timer.is_running(), false);
252    /// ```
253    pub fn is_running(&self) -> bool {
254        self.start_time.is_some()
255    }
256}
257
258impl Default for SimpleTimer {
259    /// Creates a timer with a preset of zero (will be immediately done when started).
260    fn default() -> Self {
261        Self::new(Duration::ZERO)
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use super::*;
268
269    #[test]
270    fn test_simple_timer_basic() {
271        let mut timer = SimpleTimer::new(Duration::from_millis(50));
272
273        // Not started yet
274        assert_eq!(timer.is_done(), false);
275        assert_eq!(timer.is_running(), false);
276        assert_eq!(timer.elapsed(), Duration::ZERO);
277
278        // Start
279        timer.start();
280        assert_eq!(timer.is_running(), true);
281        assert_eq!(timer.is_done(), false);
282
283        // Wait for timer
284        std::thread::sleep(Duration::from_millis(60));
285        assert_eq!(timer.is_done(), true);
286        assert!(timer.elapsed() >= Duration::from_millis(50));
287
288        // Reset
289        timer.reset();
290        assert_eq!(timer.is_done(), false);
291        assert_eq!(timer.is_running(), false);
292        assert_eq!(timer.elapsed(), Duration::ZERO);
293    }
294
295    #[test]
296    fn test_simple_timer_restart() {
297        let mut timer = SimpleTimer::new(Duration::from_millis(100));
298
299        timer.start();
300        std::thread::sleep(Duration::from_millis(30));
301
302        // Restart resets the timer
303        timer.start();
304        assert!(timer.elapsed() < Duration::from_millis(20));
305    }
306
307    #[test]
308    fn test_simple_timer_preset() {
309        let mut timer = SimpleTimer::new(Duration::from_secs(5));
310
311        assert_eq!(timer.preset(), Duration::from_secs(5));
312
313        timer.set_preset(Duration::from_secs(10));
314        assert_eq!(timer.preset(), Duration::from_secs(10));
315    }
316
317    #[test]
318    fn test_simple_timer_default() {
319        let mut timer = SimpleTimer::default();
320
321        // Default preset is zero, so immediately done when started
322        assert_eq!(timer.preset(), Duration::ZERO);
323
324        timer.start();
325        assert_eq!(timer.is_done(), true);
326    }
327}