autocore-std 3.3.20

Standard library for AutoCore control programs - shared memory, IPC, and logging utilities
Documentation
use std::time::{Duration, Instant};

/// Simple One-Shot Timer
///
/// A simple timer for one-shot timing operations. Unlike [`super::Ton`], this timer
/// does NOT follow the IEC 61131-3 standard and is designed for imperative
/// "start then check" patterns rather than cyclic function block calls.
///
/// **Note:** For standard PLC timer patterns (delay while condition is true),
/// use [`super::Ton`] instead. `SimpleTimer` is intended for one-shot use cases
/// like "do X, wait, then do Y".
///
/// # Safety
///
/// This timer uses [`Instant`] (monotonic clock) internally, making it immune
/// to system clock adjustments (NTP sync, manual changes, DST). This is
/// critical for reliable timing in industrial control applications.
///
/// # Example
///
/// ```
/// use autocore_std::fb::SimpleTimer;
/// use std::time::Duration;
///
/// let mut timer = SimpleTimer::new(Duration::from_millis(100));
///
/// // Timer hasn't started yet
/// assert_eq!(timer.is_done(), false);
/// assert_eq!(timer.elapsed(), Duration::ZERO);
///
/// // Start the timer
/// timer.start();
///
/// // Check if done (will be false initially)
/// assert_eq!(timer.is_done(), false);
///
/// // After waiting...
/// std::thread::sleep(Duration::from_millis(110));
/// assert_eq!(timer.is_done(), true);
///
/// // Reset for reuse
/// timer.reset();
/// assert_eq!(timer.is_done(), false);
/// ```
///
/// # Use Cases
///
/// - One-shot delays ("wait 500ms then continue")
/// - Tracking time since an event occurred
/// - Non-cyclic async contexts
/// - Simple timeout checks
///
/// # Comparison with Ton
///
/// | Aspect | `Ton` | `SimpleTimer` |
/// |--------|-------|---------------|
/// | IEC 61131-3 | Yes | No |
/// | Pattern | Cyclic `call()` | Imperative `start()`/`is_done()` |
/// | Auto-reset on disable | Yes | No (explicit `reset()`) |
/// | Best for | PLC-style control | One-shot operations |
#[derive(Debug, Clone)]
pub struct SimpleTimer {
    start_time: Option<Instant>,
    preset: Duration,
}

impl SimpleTimer {
    /// Creates a new simple timer with the specified preset duration.
    ///
    /// The timer starts in the stopped state and must be explicitly started
    /// with [`start()`](Self::start).
    ///
    /// # Arguments
    ///
    /// * `preset` - The duration after which [`is_done()`](Self::is_done) returns `true`
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let timer = SimpleTimer::new(Duration::from_secs(5));
    /// assert_eq!(timer.is_done(), false);
    /// ```
    pub fn new(preset: Duration) -> Self {
        Self {
            start_time: None,
            preset,
        }
    }

    /// Starts (or restarts) the timer.
    ///
    /// Records the current time as the start time. If the timer was already
    /// running, this restarts it from zero.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let mut timer = SimpleTimer::new(Duration::from_millis(100));
    /// timer.start();
    /// // Timer is now counting...
    /// ```
    pub fn start(&mut self) {
        self.start_time = Some(Instant::now());
    }

    /// Checks if the preset time has elapsed since the timer was started.
    ///
    /// Returns `false` if the timer hasn't been started yet.
    ///
    /// # Returns
    ///
    /// `true` if the timer was started and the preset duration has elapsed,
    /// `false` otherwise.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let mut timer = SimpleTimer::new(Duration::from_millis(50));
    ///
    /// // Not started yet
    /// assert_eq!(timer.is_done(), false);
    ///
    /// timer.start();
    /// std::thread::sleep(Duration::from_millis(60));
    /// assert_eq!(timer.is_done(), true);
    /// ```
    pub fn is_done(&self) -> bool {
        self.start_time
            .map(|t| t.elapsed() >= self.preset)
            .unwrap_or(false)
    }

    /// Returns the elapsed time since the timer was started.
    ///
    /// Returns [`Duration::ZERO`] if the timer hasn't been started yet.
    ///
    /// # Returns
    ///
    /// The elapsed time since [`start()`](Self::start) was called, or
    /// `Duration::ZERO` if not started.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let mut timer = SimpleTimer::new(Duration::from_secs(10));
    ///
    /// // Not started
    /// assert_eq!(timer.elapsed(), Duration::ZERO);
    ///
    /// timer.start();
    /// std::thread::sleep(Duration::from_millis(50));
    /// assert!(timer.elapsed() >= Duration::from_millis(50));
    /// ```
    pub fn elapsed(&self) -> Duration {
        self.start_time
            .map(|t| t.elapsed())
            .unwrap_or(Duration::ZERO)
    }

    /// Resets the timer to its initial (stopped) state.
    ///
    /// After calling this, [`is_done()`](Self::is_done) will return `false`
    /// and [`elapsed()`](Self::elapsed) will return zero until
    /// [`start()`](Self::start) is called again.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let mut timer = SimpleTimer::new(Duration::from_millis(50));
    /// timer.start();
    /// std::thread::sleep(Duration::from_millis(60));
    /// assert_eq!(timer.is_done(), true);
    ///
    /// timer.reset();
    /// assert_eq!(timer.is_done(), false);
    /// assert_eq!(timer.elapsed(), Duration::ZERO);
    /// ```
    pub fn reset(&mut self) {
        self.start_time = None;
    }

    /// Returns the preset duration for this timer.
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let timer = SimpleTimer::new(Duration::from_secs(5));
    /// assert_eq!(timer.preset(), Duration::from_secs(5));
    /// ```
    pub fn preset(&self) -> Duration {
        self.preset
    }

    /// Sets a new preset duration.
    ///
    /// This does not reset or restart the timer. If the timer is running,
    /// the new preset takes effect immediately for [`is_done()`](Self::is_done)
    /// checks.
    ///
    /// # Arguments
    ///
    /// * `preset` - The new duration after which `is_done()` returns `true`
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let mut timer = SimpleTimer::new(Duration::from_secs(5));
    /// timer.set_preset(Duration::from_secs(10));
    /// assert_eq!(timer.preset(), Duration::from_secs(10));
    /// ```
    pub fn set_preset(&mut self, preset: Duration) {
        self.preset = preset;
    }

    /// Returns whether the timer is currently running (has been started but not reset).
    ///
    /// # Example
    ///
    /// ```
    /// use autocore_std::fb::SimpleTimer;
    /// use std::time::Duration;
    ///
    /// let mut timer = SimpleTimer::new(Duration::from_secs(5));
    /// assert_eq!(timer.is_running(), false);
    ///
    /// timer.start();
    /// assert_eq!(timer.is_running(), true);
    ///
    /// timer.reset();
    /// assert_eq!(timer.is_running(), false);
    /// ```
    pub fn is_running(&self) -> bool {
        self.start_time.is_some()
    }
}

impl Default for SimpleTimer {
    /// Creates a timer with a preset of zero (will be immediately done when started).
    fn default() -> Self {
        Self::new(Duration::ZERO)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_simple_timer_basic() {
        let mut timer = SimpleTimer::new(Duration::from_millis(50));

        // Not started yet
        assert_eq!(timer.is_done(), false);
        assert_eq!(timer.is_running(), false);
        assert_eq!(timer.elapsed(), Duration::ZERO);

        // Start
        timer.start();
        assert_eq!(timer.is_running(), true);
        assert_eq!(timer.is_done(), false);

        // Wait for timer
        std::thread::sleep(Duration::from_millis(60));
        assert_eq!(timer.is_done(), true);
        assert!(timer.elapsed() >= Duration::from_millis(50));

        // Reset
        timer.reset();
        assert_eq!(timer.is_done(), false);
        assert_eq!(timer.is_running(), false);
        assert_eq!(timer.elapsed(), Duration::ZERO);
    }

    #[test]
    fn test_simple_timer_restart() {
        let mut timer = SimpleTimer::new(Duration::from_millis(100));

        timer.start();
        std::thread::sleep(Duration::from_millis(30));

        // Restart resets the timer
        timer.start();
        assert!(timer.elapsed() < Duration::from_millis(20));
    }

    #[test]
    fn test_simple_timer_preset() {
        let mut timer = SimpleTimer::new(Duration::from_secs(5));

        assert_eq!(timer.preset(), Duration::from_secs(5));

        timer.set_preset(Duration::from_secs(10));
        assert_eq!(timer.preset(), Duration::from_secs(10));
    }

    #[test]
    fn test_simple_timer_default() {
        let mut timer = SimpleTimer::default();

        // Default preset is zero, so immediately done when started
        assert_eq!(timer.preset(), Duration::ZERO);

        timer.start();
        assert_eq!(timer.is_done(), true);
    }
}