Skip to main content

des_sim/primitive/time/
micro_step.rs

1//! The `micro_step` module defines `MicroStep` and `MicroStepStatus` for
2//! managing sub-tick ordering in the simulation.
3//!
4//! `MicroStep` represents an ordered step within a single simulation tick,
5//! allowing for deterministic execution of events and sources that occur
6//! at the same `SimTime`. `MicroStepStatus` tracks the current micro-step.
7
8use std::fmt::{Display, Formatter};
9
10/// Represents the sequence of micro-steps within a single simulation tick.
11#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
12pub struct MicroStep(u64);
13
14impl Display for MicroStep {
15    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
16        write!(f, "{}", self.0)
17    }
18}
19
20impl MicroStep {
21    /// Returns a `MicroStep` initialized to zero.
22    #[must_use]
23    pub const fn zero() -> Self {
24        Self(0)
25    }
26
27    /// Creates a new `MicroStep` with the specified value.
28    #[cfg(test)]
29    #[allow(dead_code)]
30    pub const fn new(value: u64) -> Self {
31        Self(value)
32    }
33
34    /// Returns the next `MicroStep` in the sequence.
35    ///
36    /// # Panics
37    ///
38    /// Panics if incrementing would cause an overflow.
39    #[must_use]
40    pub const fn next(self) -> Self {
41        match self.0.checked_add(1) {
42            Some(val) => Self(val),
43            None => panic!("attempt to add with overflow"),
44        }
45    }
46
47    /// Returns the raw numerical value of the micro-step.
48    pub const fn value(&self) -> u64 {
49        self.0
50    }
51}
52
53/// Tracks the current state of micro-steps within a simulation tick.
54#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
55pub struct MicroStepStatus {
56    current_micro_step: MicroStep,
57}
58
59impl MicroStepStatus {
60    /// Creates a new `MicroStepStatus` at the specified micro-step.
61    pub(crate) fn new(current_micro_step: MicroStep) -> Self {
62        MicroStepStatus { current_micro_step }
63    }
64
65    /// Initializes `MicroStepStatus` to the starting step.
66    pub(crate) fn initialize() -> Self {
67        MicroStepStatus {
68            current_micro_step: MicroStep::zero(),
69        }
70    }
71
72    /// Returns the current `MicroStep`.
73    pub fn current(&self) -> MicroStep {
74        self.current_micro_step
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn micro_step_zero() {
84        let step = MicroStep::zero();
85        assert_eq!(step.value(), 0);
86    }
87
88    #[test]
89    fn micro_step_new_and_value() {
90        let step = MicroStep::new(42);
91        assert_eq!(step.value(), 42);
92    }
93
94    #[test]
95    fn micro_step_display() {
96        let step = MicroStep::new(100);
97        assert_eq!(format!("{}", step), "100");
98    }
99
100    #[test]
101    fn micro_step_next() {
102        let step = MicroStep::zero();
103        let next_step = step.next();
104        assert_eq!(next_step.value(), 1);
105
106        let step_large = MicroStep::new(99);
107        assert_eq!(step_large.next().value(), 100);
108    }
109
110    #[test]
111    #[should_panic(expected = "attempt to add with overflow")]
112    fn micro_step_next_overflow_panic() {
113        // Verify that if you call next() in the state of u64::MAX,
114        // an overflow panic will occur correctly at runtime (or evaluation time)
115        // even if it is within a const fn.
116        let max_step = MicroStep::new(u64::MAX);
117        let _ = max_step.next();
118    }
119
120    #[test]
121    fn micro_step_comparison_and_ordering() {
122        let step1 = MicroStep::new(10);
123        let step2 = MicroStep::new(20);
124        let step3 = MicroStep::new(10);
125
126        assert!(step1 < step2);
127        assert!(step2 > step1);
128        assert!(step1 <= step3);
129        assert!(step1 >= step3);
130        assert_eq!(step1, step3);
131        assert_ne!(step1, step2);
132    }
133
134    #[test]
135    fn micro_step_status_initialize() {
136        let status = MicroStepStatus::initialize();
137        assert_eq!(status.current(), MicroStep::zero());
138    }
139
140    #[test]
141    fn micro_step_status_new_and_current() {
142        let step = MicroStep::new(5);
143        let status = MicroStepStatus::new(step);
144        assert_eq!(status.current(), step);
145    }
146
147    #[test]
148    fn micro_step_status_mutation_via_recreation() {
149        let status_initial = MicroStepStatus::initialize();
150        assert_eq!(status_initial.current().value(), 0);
151
152        let next_step = status_initial.current().next();
153        let status_next = MicroStepStatus::new(next_step);
154
155        assert_eq!(status_next.current().value(), 1);
156        // Ensure the original state remains unchanged.
157        assert_eq!(status_initial.current().value(), 0);
158    }
159}