Skip to main content

des_sim/primitive/time/
sim_time.rs

1//! The `sim_time` module defines `SimTime` and `Duration` for representing
2//! simulation time and intervals, respectively.
3//!
4//! It also includes `TickStatus` for tracking the simulation's progression
5//! through discrete time steps, handling skipped durations for efficiency.
6//! These types provide arithmetic operations and utility methods for time management.
7
8use std::fmt::{Display, Formatter};
9use std::ops::{Add, AddAssign, Sub, SubAssign};
10
11/// Represents a duration or point in time measured in simulation ticks.
12pub type TimeTick = usize;
13
14/// Represents a specific point in simulation time.
15#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
16pub struct SimTime(TimeTick);
17
18impl Display for SimTime {
19    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
20        write!(f, "{}", self.0)
21    }
22}
23
24impl From<TimeTick> for SimTime {
25    fn from(v: TimeTick) -> Self {
26        SimTime(v)
27    }
28}
29
30impl Add<Duration> for SimTime {
31    type Output = SimTime;
32
33    fn add(self, rhs: Duration) -> Self::Output {
34        match self.0.checked_add(rhs.0) {
35            Some(val) => SimTime(val),
36            None => panic!("attempt to add with overflow"),
37        }
38    }
39}
40
41impl AddAssign<Duration> for SimTime {
42    fn add_assign(&mut self, rhs: Duration) {
43        match self.0.checked_add(rhs.0) {
44            Some(val) => self.0 = val,
45            None => panic!("attempt to add with overflow"),
46        }
47    }
48}
49
50impl Sub<SimTime> for SimTime {
51    type Output = Duration;
52
53    fn sub(self, rhs: SimTime) -> Self::Output {
54        match self.0.checked_sub(rhs.0) {
55            Some(val) => Duration(val),
56            None => panic!("attempt to subtract with overflow"),
57        }
58    }
59}
60
61impl Sub<Duration> for SimTime {
62    type Output = SimTime;
63
64    fn sub(self, rhs: Duration) -> Self::Output {
65        match self.0.checked_sub(rhs.0) {
66            Some(val) => SimTime(val),
67            None => panic!("attempt to subtract with overflow"),
68        }
69    }
70}
71
72impl SimTime {
73    /// Creates a new `SimTime` from the given number of ticks.
74    pub const fn from_ticks(ticks: TimeTick) -> SimTime {
75        SimTime(ticks)
76    }
77
78    /// Returns a `SimTime` representing the beginning of the simulation (time zero).
79    pub const fn zero() -> SimTime {
80        SimTime(0)
81    }
82
83    /// Returns the underlying tick count.
84    pub const fn as_time_tick(self) -> TimeTick {
85        self.0
86    }
87
88    /// Returns `true` if this `SimTime` is at the beginning of the simulation.
89    pub const fn is_zero(self) -> bool {
90        self.0 == 0
91    }
92
93    /// Checked addition of a `Duration`.
94    pub fn checked_add(self, rhs: Duration) -> Option<SimTime> {
95        self.0.checked_add(rhs.0).map(SimTime)
96    }
97
98    /// Saturating addition of a `Duration`.
99    pub fn saturating_add(self, rhs: Duration) -> SimTime {
100        SimTime(self.0.saturating_add(rhs.0))
101    }
102
103    /// Checked subtraction of another `SimTime`, resulting in a `Duration`.
104    pub fn checked_sub(self, rhs: SimTime) -> Option<Duration> {
105        self.0.checked_sub(rhs.0).map(Duration)
106    }
107
108    /// Saturating subtraction of another `SimTime`, resulting in a `Duration`.
109    pub fn saturating_sub(self, rhs: SimTime) -> Duration {
110        Duration(self.0.saturating_sub(rhs.0))
111    }
112
113    /// Checked subtraction of a `Duration`.
114    pub fn checked_sub_duration(self, rhs: Duration) -> Option<SimTime> {
115        self.0.checked_sub(rhs.0).map(SimTime)
116    }
117
118    /// Saturating subtraction of a `Duration`.
119    pub fn saturating_sub_duration(self, rhs: Duration) -> SimTime {
120        SimTime(self.0.saturating_sub(rhs.0))
121    }
122}
123
124/// Represents a duration of simulation time.
125#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
126pub struct Duration(TimeTick);
127
128impl Display for Duration {
129    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
130        write!(f, "{}", self.0)
131    }
132}
133
134impl From<TimeTick> for Duration {
135    fn from(v: TimeTick) -> Self {
136        Duration(v)
137    }
138}
139
140impl Add<Duration> for Duration {
141    type Output = Duration;
142
143    fn add(self, rhs: Duration) -> Self::Output {
144        match self.0.checked_add(rhs.0) {
145            Some(val) => Duration(val),
146            None => panic!("attempt to add with overflow"),
147        }
148    }
149}
150
151impl AddAssign<Duration> for Duration {
152    fn add_assign(&mut self, rhs: Duration) {
153        match self.0.checked_add(rhs.0) {
154            Some(val) => self.0 = val,
155            None => panic!("attempt to add with overflow"),
156        }
157    }
158}
159
160impl Sub<Duration> for Duration {
161    type Output = Duration;
162
163    fn sub(self, rhs: Duration) -> Self::Output {
164        match self.0.checked_sub(rhs.0) {
165            Some(val) => Duration(val),
166            None => panic!("attempt to subtract with overflow"),
167        }
168    }
169}
170
171impl SubAssign<Duration> for Duration {
172    fn sub_assign(&mut self, rhs: Duration) {
173        match self.0.checked_sub(rhs.0) {
174            Some(val) => self.0 = val,
175            None => panic!("attempt to subtract with overflow"),
176        }
177    }
178}
179
180impl Duration {
181    /// Creates a new `Duration` from a tick count.
182    pub const fn ticks(ticks: TimeTick) -> Duration {
183        Duration(ticks)
184    }
185
186    /// A duration of zero.
187    pub const fn zero() -> Duration {
188        Duration(0)
189    }
190
191    /// A duration of one tick.
192    pub const fn one() -> Duration {
193        Duration(1)
194    }
195
196    /// Returns the underlying tick count.
197    pub const fn as_time_tick(self) -> TimeTick {
198        self.0
199    }
200
201    /// Returns `true` if the duration is zero.
202    pub const fn is_zero(self) -> bool {
203        self.0 == 0
204    }
205
206    /// Checked addition of another `Duration`.
207    pub fn checked_add(self, rhs: Duration) -> Option<Duration> {
208        self.0.checked_add(rhs.0).map(Duration)
209    }
210
211    /// Saturating addition of another `Duration`.
212    pub fn saturating_add(self, rhs: Duration) -> Duration {
213        Duration(self.0.saturating_add(rhs.0))
214    }
215
216    /// Checked subtraction of another `Duration`.
217    pub fn checked_sub(self, rhs: Duration) -> Option<Duration> {
218        self.0.checked_sub(rhs.0).map(Duration)
219    }
220
221    /// Saturating subtraction of another `Duration`.
222    pub fn saturating_sub(self, rhs: Duration) -> Duration {
223        Duration(self.0.saturating_sub(rhs.0))
224    }
225}
226
227/// Tracks the simulation's progress through time ticks, including handled skips.
228#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
229pub struct TickStatus {
230    current_tick: SimTime,
231    skipped: Duration,
232}
233
234impl TickStatus {
235    /// Creates a new `TickStatus` with the specified current tick and skipped duration.
236    pub(crate) fn new(current_tick: SimTime, skipped: Duration) -> Self {
237        TickStatus {
238            current_tick,
239            skipped,
240        }
241    }
242
243    /// Initializes `TickStatus` to the starting state (time zero, no time skipped).
244    pub(crate) fn initialize() -> Self {
245        TickStatus {
246            current_tick: SimTime::zero(),
247            skipped: Duration::zero(),
248        }
249    }
250
251    /// Returns the current simulation time.
252    pub fn current(&self) -> SimTime {
253        self.current_tick
254    }
255
256    /// Returns the total duration of time that has been skipped from previous tick.
257    pub fn skipped(&self) -> Duration {
258        self.skipped
259    }
260
261    /// Calculates the "previous" tick, accounting for skipped time.
262    pub fn previous(&self) -> SimTime {
263        if self.current_tick == SimTime::zero() {
264            self.current_tick
265        } else {
266            self.current_tick - self.skipped - Duration::one()
267        }
268    }
269
270    /// Determines if the simulation has processed the required number of ticks.
271    ///
272    /// This uses the `previous()` time as a baseline to handle cases where time
273    /// is skipped. Even if a large jump occurs, it ensures the simulation processes
274    /// up to the threshold before stopping safely.
275    ///
276    /// # Arguments
277    ///
278    /// * `include_zero_tick` - If `true`, the zero-th tick is included in the count.
279    /// * `tick_count` - The target tick threshold to reach.
280    pub fn is_done_ticks(&self, include_zero_tick: bool, tick_count: TimeTick) -> bool {
281        self.previous().as_time_tick() + if include_zero_tick { 1 } else { 0 } >= tick_count
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn sim_time_creation() {
291        let t = SimTime::from_ticks(10);
292        assert_eq!(t.as_time_tick(), 10);
293        assert!(!t.is_zero());
294
295        let t_zero = SimTime::zero();
296        assert_eq!(t_zero.as_time_tick(), 0);
297        assert!(t_zero.is_zero());
298    }
299
300    #[test]
301    fn sim_time_from_tick() {
302        let t = SimTime::from(5);
303        assert_eq!(t.as_time_tick(), 5);
304    }
305
306    #[test]
307    fn sim_time_from_usize_conversion() {
308        let val: usize = 123;
309        let sim_time = SimTime::from(val);
310        assert_eq!(sim_time.as_time_tick(), val);
311    }
312
313    #[test]
314    fn sim_time_display() {
315        let t = SimTime::from_ticks(100);
316        assert_eq!(format!("{}", t), "100");
317    }
318
319    #[test]
320    fn sim_time_add_duration() {
321        let t1 = SimTime::from_ticks(10);
322        let d = Duration::ticks(5);
323        let t2 = t1 + d;
324        assert_eq!(t2.as_time_tick(), 15);
325    }
326
327    #[test]
328    fn sim_time_add_assign_duration() {
329        let mut t = SimTime::from_ticks(10);
330        let d = Duration::ticks(5);
331        t += d;
332        assert_eq!(t.as_time_tick(), 15);
333    }
334
335    #[test]
336    fn sim_time_sub_sim_time() {
337        let t1 = SimTime::from_ticks(10);
338        let t2 = SimTime::from_ticks(5);
339        let d = t1 - t2;
340        assert_eq!(d.as_time_tick(), 5);
341    }
342
343    #[test]
344    fn sim_time_sub_duration() {
345        let t = SimTime::from_ticks(10);
346        let d = Duration::ticks(5);
347        let t_new = t - d;
348        assert_eq!(t_new.as_time_tick(), 5);
349    }
350
351    #[test]
352    fn sim_time_checked_add() {
353        let t = SimTime::from_ticks(10);
354        let d = Duration::ticks(5);
355        assert_eq!(t.checked_add(d), Some(SimTime::from_ticks(15)));
356        let max_tick = TimeTick::MAX - 1;
357        let t_max = SimTime::from_ticks(max_tick);
358        let d_one = Duration::one();
359        assert_eq!(
360            t_max.checked_add(d_one),
361            Some(SimTime::from_ticks(TimeTick::MAX))
362        );
363        assert_eq!(t_max.checked_add(Duration::ticks(2)), None);
364    }
365
366    #[test]
367    fn sim_time_saturating_add() {
368        let t = SimTime::from_ticks(10);
369        let d = Duration::ticks(5);
370        assert_eq!(t.saturating_add(d), SimTime::from_ticks(15));
371        let max_tick = TimeTick::MAX - 1;
372        let t_max = SimTime::from_ticks(max_tick);
373        let d_two = Duration::ticks(2);
374        assert_eq!(
375            t_max.saturating_add(d_two),
376            SimTime::from_ticks(TimeTick::MAX)
377        );
378    }
379
380    #[test]
381    fn sim_time_checked_sub() {
382        let t1 = SimTime::from_ticks(10);
383        let t2 = SimTime::from_ticks(5);
384        assert_eq!(t1.checked_sub(t2), Some(Duration::ticks(5)));
385        let t_zero = SimTime::zero();
386        assert_eq!(t1.checked_sub(SimTime::from_ticks(15)), None);
387        assert_eq!(t_zero.checked_sub(SimTime::from_ticks(1)), None);
388    }
389
390    #[test]
391    fn sim_time_saturating_sub() {
392        let t1 = SimTime::from_ticks(10);
393        let t2 = SimTime::from_ticks(5);
394        assert_eq!(t1.saturating_sub(t2), Duration::ticks(5));
395        assert_eq!(t1.saturating_sub(SimTime::from_ticks(15)), Duration::zero());
396    }
397
398    #[test]
399    fn sim_time_checked_sub_duration() {
400        let t = SimTime::from_ticks(10);
401        let d = Duration::ticks(5);
402        assert_eq!(t.checked_sub_duration(d), Some(SimTime::from_ticks(5)));
403        assert_eq!(t.checked_sub_duration(Duration::ticks(15)), None);
404    }
405
406    #[test]
407    fn sim_time_saturating_sub_duration() {
408        let t = SimTime::from_ticks(10);
409        let d = Duration::ticks(5);
410        assert_eq!(t.saturating_sub_duration(d), SimTime::from_ticks(5));
411        assert_eq!(
412            t.saturating_sub_duration(Duration::ticks(15)),
413            SimTime::zero()
414        );
415    }
416
417    #[test]
418    fn sim_time_add_zero_duration() {
419        let t = SimTime::from_ticks(100);
420        let d_zero = Duration::zero();
421        assert_eq!(t + d_zero, t);
422    }
423
424    #[test]
425    fn sim_time_sub_zero_duration() {
426        let t = SimTime::from_ticks(100);
427        let d_zero = Duration::zero();
428        assert_eq!(t - d_zero, t);
429    }
430
431    #[test]
432    fn sim_time_max_value_subtraction() {
433        let max_sim_time = SimTime::from_ticks(TimeTick::MAX);
434        let one_duration = Duration::one();
435        let expected_sim_time = SimTime::from_ticks(TimeTick::MAX - 1);
436        assert_eq!(max_sim_time - one_duration, expected_sim_time);
437    }
438
439    #[test]
440    fn sim_time_comparison() {
441        let t1 = SimTime::from_ticks(10);
442        let t2 = SimTime::from_ticks(20);
443        let t3 = SimTime::from_ticks(10);
444
445        assert!(t1 < t2);
446        assert!(t2 > t1);
447        assert!(t1 <= t3);
448        assert!(t1 >= t3);
449        assert_eq!(t1, t3);
450        assert_ne!(t1, t2);
451    }
452
453    #[test]
454    fn duration_creation() {
455        let d = Duration::ticks(10);
456        assert_eq!(d.as_time_tick(), 10);
457        assert!(!d.is_zero());
458
459        let d_zero = Duration::zero();
460        assert_eq!(d_zero.as_time_tick(), 0);
461        assert!(d_zero.is_zero());
462
463        let d_one = Duration::one();
464        assert_eq!(d_one.as_time_tick(), 1);
465    }
466
467    #[test]
468    fn duration_from_tick() {
469        let d = Duration::from(5);
470        assert_eq!(d.as_time_tick(), 5);
471    }
472
473    #[test]
474    fn duration_from_usize_conversion() {
475        let val: usize = 456;
476        let duration = Duration::from(val);
477        assert_eq!(duration.as_time_tick(), val);
478    }
479
480    #[test]
481    fn duration_display() {
482        let d = Duration::ticks(100);
483        assert_eq!(format!("{}", d), "100");
484    }
485
486    #[test]
487    fn duration_add_duration() {
488        let d1 = Duration::ticks(10);
489        let d2 = Duration::ticks(5);
490        let d3 = d1 + d2;
491        assert_eq!(d3.as_time_tick(), 15);
492    }
493
494    #[test]
495    fn duration_add_assign_duration() {
496        let mut d = Duration::ticks(10);
497        let d_add = Duration::ticks(5);
498        d += d_add;
499        assert_eq!(d.as_time_tick(), 15);
500    }
501
502    #[test]
503    fn duration_sub_duration() {
504        let d1 = Duration::ticks(10);
505        let d2 = Duration::ticks(5);
506        let d3 = d1 - d2;
507        assert_eq!(d3.as_time_tick(), 5);
508    }
509
510    #[test]
511    fn duration_sub_assign_duration() {
512        let mut d = Duration::ticks(10);
513        let d_sub = Duration::ticks(5);
514        d -= d_sub;
515        assert_eq!(d.as_time_tick(), 5);
516    }
517
518    #[test]
519    fn duration_checked_add() {
520        let d1 = Duration::ticks(10);
521        let d2 = Duration::ticks(5);
522        assert_eq!(d1.checked_add(d2), Some(Duration::ticks(15)));
523        let max_tick = TimeTick::MAX - 1;
524        let d_max = Duration::ticks(max_tick);
525        let d_one = Duration::one();
526        assert_eq!(
527            d_max.checked_add(d_one),
528            Some(Duration::ticks(TimeTick::MAX))
529        );
530        assert_eq!(d_max.checked_add(Duration::ticks(2)), None);
531    }
532
533    #[test]
534    fn duration_saturating_add() {
535        let d1 = Duration::ticks(10);
536        let d2 = Duration::ticks(5);
537        assert_eq!(d1.saturating_add(d2), Duration::ticks(15));
538        let max_tick = TimeTick::MAX - 1;
539        let d_max = Duration::ticks(max_tick);
540        let d_two = Duration::ticks(2);
541        assert_eq!(d_max.saturating_add(d_two), Duration::ticks(TimeTick::MAX));
542    }
543
544    #[test]
545    fn duration_checked_sub() {
546        let d1 = Duration::ticks(10);
547        let d2 = Duration::ticks(5);
548        assert_eq!(d1.checked_sub(d2), Some(Duration::ticks(5)));
549        assert_eq!(d1.checked_sub(Duration::ticks(15)), None);
550    }
551
552    #[test]
553    fn duration_saturating_sub() {
554        let d1 = Duration::ticks(10);
555        let d2 = Duration::ticks(5);
556        assert_eq!(d1.saturating_sub(d2), Duration::ticks(5));
557        assert_eq!(d1.saturating_sub(Duration::ticks(15)), Duration::zero());
558    }
559
560    #[test]
561    fn duration_add_zero_duration() {
562        let d = Duration::ticks(100);
563        let d_zero = Duration::zero();
564        assert_eq!(d + d_zero, d);
565    }
566
567    #[test]
568    fn duration_sub_zero_duration() {
569        let d = Duration::ticks(100);
570        let d_zero = Duration::zero();
571        assert_eq!(d - d_zero, d);
572    }
573
574    #[test]
575    fn duration_max_value_subtraction() {
576        let max_duration = Duration::ticks(TimeTick::MAX);
577        let one_duration = Duration::one();
578        let expected_duration = Duration::ticks(TimeTick::MAX - 1);
579        assert_eq!(max_duration - one_duration, expected_duration);
580    }
581
582    #[test]
583    fn duration_comparison() {
584        let d1 = Duration::ticks(10);
585        let d2 = Duration::ticks(20);
586        let d3 = Duration::ticks(10);
587
588        assert!(d1 < d2);
589        assert!(d2 > d1);
590        assert!(d1 <= d3);
591        assert!(d1 >= d3);
592        assert_eq!(d1, d3);
593        assert_ne!(d1, d2);
594    }
595
596    #[test]
597    fn tick_status_initialize() {
598        let status = TickStatus::initialize();
599        assert_eq!(status.current(), SimTime::zero());
600        assert_eq!(status.skipped(), Duration::zero());
601        assert_eq!(status.previous(), SimTime::zero());
602    }
603
604    #[test]
605    fn tick_status_new() {
606        let current = SimTime::from_ticks(10);
607        let skipped = Duration::ticks(2);
608        let status = TickStatus::new(current, skipped);
609        assert_eq!(status.current(), current);
610        assert_eq!(status.skipped(), skipped);
611    }
612
613    #[test]
614    fn tick_status_previous() {
615        // current_tick = 0, skipped = 0 => previous = 0
616        let status = TickStatus::initialize();
617        assert_eq!(status.previous(), SimTime::zero());
618
619        // current_tick = 5, skipped = 0 => previous = 5 - 0 - 1 = 4
620        let status = TickStatus::new(SimTime::from_ticks(5), Duration::zero());
621        assert_eq!(status.previous(), SimTime::from_ticks(4));
622
623        // current_tick = 5, skipped = 2 => previous = 5 - 2 - 1 = 2
624        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(2));
625        assert_eq!(status.previous(), SimTime::from_ticks(2));
626    }
627
628    #[test]
629    fn tick_status_is_done_ticks_no_skip() {
630        // tick_count = 2, include_zero_tick = true
631        // previous = 0, 0 + 1 >= 2 => false
632        let status = TickStatus::initialize();
633        assert!(!status.is_done_ticks(true, 2));
634
635        // previous = 1, 1 + 1 >= 2 => true
636        let status = TickStatus::new(SimTime::from_ticks(2), Duration::zero());
637        assert!(status.is_done_ticks(true, 2));
638
639        // tick_count = 2, include_zero_tick = false
640        // previous = 0, 0 + 0 >= 2 => false
641        let status = TickStatus::initialize();
642        assert!(!status.is_done_ticks(false, 2));
643
644        // previous = 1, 1 + 0 >= 2 => false
645        let status = TickStatus::new(SimTime::from_ticks(2), Duration::zero());
646        assert!(!status.is_done_ticks(false, 2));
647
648        // previous = 2, 2 + 0 >= 2 => true
649        let status = TickStatus::new(SimTime::from_ticks(3), Duration::zero());
650        assert!(status.is_done_ticks(false, 2));
651    }
652
653    #[test]
654    fn tick_status_is_done_ticks_with_skip() {
655        // current_tick = 5, skipped = 2. previous = 2
656        // tick_count = 2, include_zero_tick = true
657        // previous = 2, 2 + 1 >= 2 => true
658        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(2));
659        assert!(status.is_done_ticks(true, 2));
660
661        // tick_count = 3, include_zero_tick = true
662        // previous = 2, 2 + 1 >= 3 => true
663        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(2));
664        assert!(status.is_done_ticks(true, 3));
665
666        // tick_count = 4, include_zero_tick = true
667        // previous = 2, 2 + 1 >= 4 => false
668        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(2));
669        assert!(!status.is_done_ticks(true, 4));
670
671        // current_tick = 5, skipped = 2. previous = 2
672        // tick_count = 2, include_zero_tick = false
673        // previous = 2, 2 + 0 >= 2 => true
674        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(2));
675        assert!(status.is_done_ticks(false, 2));
676
677        // tick_count = 3, include_zero_tick = false
678        // previous = 2, 2 + 0 >= 3 => false
679        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(2));
680        assert!(!status.is_done_ticks(false, 3));
681    }
682
683    #[test]
684    fn tick_status_previous_large_current_tick() {
685        let current = SimTime::from_ticks(TimeTick::MAX);
686        let skipped = Duration::ticks(100);
687        let status = TickStatus::new(current, skipped);
688        assert_eq!(
689            status.previous(),
690            SimTime::from_ticks(TimeTick::MAX - 100 - 1)
691        );
692    }
693
694    #[test]
695    fn tick_status_is_done_ticks_edge_cases() {
696        // tick_count = 0
697        let status = TickStatus::initialize();
698        assert!(status.is_done_ticks(true, 0));
699        assert!(status.is_done_ticks(false, 0));
700
701        // tick_count = 1, include_zero_tick = true
702        // previous = 0, 0 + 1 >= 1 => true
703        let status = TickStatus::initialize();
704        assert!(status.is_done_ticks(true, 1));
705
706        // tick_count = 1, include_zero_tick = false
707        // previous = 0, 0 + 0 >= 1 => false
708        let status = TickStatus::initialize();
709        assert!(!status.is_done_ticks(false, 1));
710
711        // current_tick = 1, skipped = 0. previous = 0
712        // tick_count = 1, include_zero_tick = false
713        // previous = 0, 0 + 0 >= 1 => false
714        let status = TickStatus::new(SimTime::from_ticks(1), Duration::zero());
715        assert!(!status.is_done_ticks(false, 1));
716
717        // current_tick = 2, skipped = 0. previous = 1
718        // tick_count = 1, include_zero_tick = false
719        // previous = 1, 1 + 0 >= 1 => true
720        let status = TickStatus::new(SimTime::from_ticks(2), Duration::zero());
721        assert!(status.is_done_ticks(false, 1));
722    }
723
724    #[test]
725    fn tick_status_previous_with_skipped_equal_to_current_minus_one() {
726        // current_tick = 5, skipped = 4 => previous = 5 - 4 - 1 = 0
727        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(4));
728        assert_eq!(status.previous(), SimTime::zero());
729    }
730
731    #[test]
732    #[should_panic(expected = "attempt to subtract with overflow")]
733    fn tick_status_previous_with_skipped_greater_than_current_minus_one() {
734        // current_tick = 5, skipped = 5 => previous = 5 - 5 - 1 = 0 (saturating sub)
735        let status = TickStatus::new(SimTime::from_ticks(5), Duration::ticks(5));
736        status.previous();
737    }
738
739    #[test]
740    fn sim_time_saturating_add_boundary() {
741        let max_sim_time = SimTime::from_ticks(TimeTick::MAX);
742        let d_one = Duration::one();
743        assert_eq!(max_sim_time.saturating_add(d_one), max_sim_time);
744
745        let d_max = Duration::ticks(TimeTick::MAX);
746        assert_eq!(max_sim_time.saturating_add(d_max), max_sim_time);
747    }
748
749    #[test]
750    fn sim_time_saturating_sub_boundary() {
751        let t_zero = SimTime::zero();
752        let t_five = SimTime::from_ticks(5);
753        let d_five = Duration::ticks(5);
754
755        assert_eq!(t_zero.saturating_sub(t_five), Duration::zero());
756        assert_eq!(t_zero.saturating_sub_duration(d_five), SimTime::zero());
757
758        let t_three = SimTime::from_ticks(3);
759        assert_eq!(t_three.saturating_sub(t_five), Duration::zero());
760        assert_eq!(t_three.saturating_sub_duration(d_five), SimTime::zero());
761    }
762
763    #[test]
764    fn sim_time_checked_sub_duration_underflow() {
765        let t_three = SimTime::from_ticks(3);
766        let d_five = Duration::ticks(5);
767        assert_eq!(t_three.checked_sub_duration(d_five), None);
768
769        let t_zero = SimTime::zero();
770        let d_one = Duration::one();
771        assert_eq!(t_zero.checked_sub_duration(d_one), None);
772    }
773
774    #[test]
775    #[should_panic(expected = "attempt to subtract with overflow")]
776    fn sim_time_sub_operator_underflow_panic() {
777        let t_three = SimTime::from_ticks(3);
778        let d_five = Duration::ticks(5);
779        let _ = t_three - d_five;
780    }
781}