ready-active-safe 0.1.3

Lifecycle engine for externally driven systems
Documentation
//! Clock, instant, and deadline abstractions for lifecycle timing.
//!
//! The base [`Machine`](crate::Machine) trait intentionally has no concept of
//! time. Time composes externally through your runtime by encoding time into
//! events (for example `Event::Tick(Instant)`), or by generating timeout events
//! when a [`Deadline`] expires.
//!
//! This module provides small building blocks so time can be:
//!
//! - **Real** in production (see [`SystemClock`] with `std`)
//! - **Virtual** in tests and simulation (see [`ManualClock`])
//!
//! It is deliberately **not** a timer scheduler. Scheduling is a runtime concern.

use core::cell::Cell;
use core::time::Duration;

/// A source of time for lifecycles.
///
/// A clock returns the current [`Instant`]. The meaning of "now" is determined
/// by the implementation:
///
/// - [`ManualClock`] is deterministic and controlled by tests.
/// - [`SystemClock`] is monotonic time since the clock was created.
pub trait Clock {
    /// Returns the current instant.
    fn now(&self) -> Instant;
}

/// A monotonic instant represented as nanoseconds since an arbitrary origin.
///
/// This type is intentionally small and `no_std` friendly. It is suitable for:
///
/// - deadlines (`Deadline`)
/// - timeouts and elapsed-time calculations
/// - deterministic replay (when driven by a virtual clock)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(u64);

impl Instant {
    /// Creates an [`Instant`] from a nanosecond counter.
    #[must_use]
    pub const fn from_nanos(nanos: u64) -> Self {
        Self(nanos)
    }

    /// Returns this instant as nanoseconds since an arbitrary origin.
    #[must_use]
    pub const fn as_nanos(self) -> u64 {
        self.0
    }

    /// Returns the duration between `earlier` and `self`, if `self >= earlier`.
    #[must_use]
    pub const fn checked_duration_since(self, earlier: Self) -> Option<Duration> {
        if self.0 >= earlier.0 {
            Some(Duration::from_nanos(self.0 - earlier.0))
        } else {
            None
        }
    }

    /// Returns the result of adding `duration` to this instant, or `None` on overflow.
    #[must_use]
    pub fn checked_add(self, duration: Duration) -> Option<Self> {
        let nanos = u128::from(self.0).checked_add(duration.as_nanos())?;
        let nanos_u64 = u64::try_from(nanos).ok()?;
        Some(Self(nanos_u64))
    }

    /// Returns the result of subtracting `duration` from this instant, or `None` on underflow.
    #[must_use]
    pub fn checked_sub(self, duration: Duration) -> Option<Self> {
        let nanos = u128::from(self.0).checked_sub(duration.as_nanos())?;
        let nanos_u64 = u64::try_from(nanos).ok()?;
        Some(Self(nanos_u64))
    }
}

/// A deadline expressed as an [`Instant`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Deadline {
    at: Instant,
}

impl Deadline {
    /// Creates a deadline at the given instant.
    #[must_use]
    pub const fn at(at: Instant) -> Self {
        Self { at }
    }

    /// Returns the instant at which the deadline expires.
    #[must_use]
    pub const fn instant(self) -> Instant {
        self.at
    }

    /// Returns `true` if the deadline has expired at `now`.
    #[must_use]
    pub const fn is_expired(self, now: Instant) -> bool {
        now.as_nanos() >= self.at.as_nanos()
    }
}

/// A deterministic clock for tests and simulation.
///
/// This clock is controlled explicitly via [`ManualClock::set`] and
/// [`ManualClock::advance`].
#[derive(Debug)]
pub struct ManualClock {
    now: Cell<Instant>,
}

impl ManualClock {
    /// Creates a manual clock starting at the provided instant.
    #[must_use]
    pub const fn new(start: Instant) -> Self {
        Self {
            now: Cell::new(start),
        }
    }

    /// Sets the current instant.
    pub fn set(&self, now: Instant) {
        self.now.set(now);
    }

    /// Advances the clock by the provided duration.
    ///
    /// Saturates at `u64::MAX` nanoseconds on overflow.
    pub fn advance(&self, by: Duration) {
        let next = self
            .now
            .get()
            .checked_add(by)
            .unwrap_or_else(|| Instant::from_nanos(u64::MAX));
        self.now.set(next);
    }
}

impl Clock for ManualClock {
    fn now(&self) -> Instant {
        self.now.get()
    }
}

/// A monotonic system clock.
///
/// This clock requires `std` and measures time as "elapsed since creation".
/// It is appropriate for deadlines and timeouts where only monotonic progress
/// matters.
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
#[derive(Debug, Clone)]
pub struct SystemClock {
    origin: std::time::Instant,
}

#[cfg(feature = "std")]
impl SystemClock {
    /// Creates a new system clock.
    #[must_use]
    pub fn new() -> Self {
        Self {
            origin: std::time::Instant::now(),
        }
    }
}

#[cfg(feature = "std")]
impl Default for SystemClock {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(feature = "std")]
impl Clock for SystemClock {
    fn now(&self) -> Instant {
        let nanos = self.origin.elapsed().as_nanos().min(u128::from(u64::MAX));
        let nanos_u64 = u64::try_from(nanos).map_or(u64::MAX, |nanos| nanos);
        Instant::from_nanos(nanos_u64)
    }
}