Skip to main content

cu29_clock/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3extern crate alloc;
4#[cfg(test)]
5extern crate approx;
6
7mod calibration;
8#[cfg_attr(target_arch = "aarch64", path = "aarch64.rs")]
9#[cfg_attr(all(target_os = "none", target_arch = "arm"), path = "cortexm.rs")]
10#[cfg_attr(target_arch = "riscv64", path = "riscv64.rs")]
11#[cfg_attr(
12    all(feature = "std", target_arch = "wasm32", target_os = "unknown"),
13    path = "wasm.rs"
14)]
15#[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")]
16#[cfg_attr(
17    not(any(
18        target_arch = "x86_64",
19        target_arch = "aarch64",
20        all(target_os = "none", target_arch = "arm"),
21        target_arch = "riscv64",
22        all(feature = "std", target_arch = "wasm32", target_os = "unknown")
23    )),
24    path = "fallback.rs"
25)]
26mod raw_counter;
27
28pub use raw_counter::*;
29
30#[cfg(feature = "reflect")]
31use bevy_reflect::Reflect;
32use bincode::BorrowDecode;
33use bincode::de::BorrowDecoder;
34use bincode::de::Decoder;
35use bincode::enc::Encoder;
36use bincode::error::{DecodeError, EncodeError};
37use bincode::{Decode, Encode};
38use core::ops::{Add, Sub};
39use serde::{Deserialize, Serialize};
40
41// We use this to be able to support embedded 32bit platforms
42use portable_atomic::{AtomicU64, Ordering};
43
44use alloc::format;
45use alloc::sync::Arc;
46use core::fmt::{Display, Formatter};
47use core::ops::{AddAssign, Div, Mul, SubAssign};
48
49/// High-precision instant in time, represented as nanoseconds since an arbitrary epoch
50#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
51pub struct CuInstant(u64);
52
53pub type Instant = CuInstant; // Backward compatibility
54
55impl CuInstant {
56    pub fn now() -> Self {
57        CuInstant(calibration::counter_to_nanos(read_raw_counter))
58    }
59
60    pub fn as_nanos(&self) -> u64 {
61        self.0
62    }
63}
64
65impl Sub for CuInstant {
66    type Output = CuDuration;
67
68    fn sub(self, other: CuInstant) -> CuDuration {
69        CuDuration(self.0.saturating_sub(other.0))
70    }
71}
72
73impl Sub<CuDuration> for CuInstant {
74    type Output = CuInstant;
75
76    fn sub(self, duration: CuDuration) -> CuInstant {
77        CuInstant(self.0.saturating_sub(duration.as_nanos()))
78    }
79}
80
81impl Add<CuDuration> for CuInstant {
82    type Output = CuInstant;
83
84    fn add(self, duration: CuDuration) -> CuInstant {
85        CuInstant(self.0.saturating_add(duration.as_nanos()))
86    }
87}
88
89/// For Robot times, the underlying type is a u64 representing nanoseconds.
90/// It is always positive to simplify the reasoning on the user side.
91#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
92#[cfg_attr(feature = "reflect", derive(Reflect))]
93pub struct CuDuration(pub u64);
94
95impl CuDuration {
96    // Lowest value a CuDuration can have.
97    pub const MIN: CuDuration = CuDuration(0u64);
98    // Highest value a CuDuration can have reserving the max value for None.
99    pub const MAX: CuDuration = CuDuration(NONE_VALUE - 1);
100
101    pub fn max(self, other: CuDuration) -> CuDuration {
102        let Self(lhs) = self;
103        let Self(rhs) = other;
104        CuDuration(lhs.max(rhs))
105    }
106
107    pub fn min(self, other: CuDuration) -> CuDuration {
108        let Self(lhs) = self;
109        let Self(rhs) = other;
110        CuDuration(lhs.min(rhs))
111    }
112
113    pub fn as_nanos(&self) -> u64 {
114        let Self(nanos) = self;
115        *nanos
116    }
117
118    pub fn as_micros(&self) -> u64 {
119        let Self(nanos) = self;
120        nanos / 1_000
121    }
122
123    pub fn as_millis(&self) -> u64 {
124        let Self(nanos) = self;
125        nanos / 1_000_000
126    }
127
128    pub fn as_secs(&self) -> u64 {
129        let Self(nanos) = self;
130        nanos / 1_000_000_000
131    }
132
133    pub fn from_nanos(nanos: u64) -> Self {
134        CuDuration(nanos)
135    }
136
137    pub fn from_micros(micros: u64) -> Self {
138        CuDuration(micros * 1_000)
139    }
140
141    pub fn from_millis(millis: u64) -> Self {
142        CuDuration(millis * 1_000_000)
143    }
144
145    pub fn from_secs(secs: u64) -> Self {
146        CuDuration(secs * 1_000_000_000)
147    }
148}
149
150/// Saturating subtraction for time and duration types.
151pub trait SaturatingSub {
152    fn saturating_sub(self, other: Self) -> Self;
153}
154
155impl SaturatingSub for CuDuration {
156    fn saturating_sub(self, other: Self) -> Self {
157        let Self(lhs) = self;
158        let Self(rhs) = other;
159        CuDuration(lhs.saturating_sub(rhs))
160    }
161}
162
163/// bridge the API with standard Durations.
164#[cfg(feature = "std")]
165impl From<std::time::Duration> for CuDuration {
166    fn from(duration: std::time::Duration) -> Self {
167        CuDuration(duration.as_nanos() as u64)
168    }
169}
170
171#[cfg(not(feature = "std"))]
172impl From<core::time::Duration> for CuDuration {
173    fn from(duration: core::time::Duration) -> Self {
174        CuDuration(duration.as_nanos() as u64)
175    }
176}
177
178#[cfg(feature = "std")]
179impl From<CuDuration> for std::time::Duration {
180    fn from(val: CuDuration) -> Self {
181        let CuDuration(nanos) = val;
182        std::time::Duration::from_nanos(nanos)
183    }
184}
185
186impl From<u64> for CuDuration {
187    fn from(duration: u64) -> Self {
188        CuDuration(duration)
189    }
190}
191
192impl From<CuDuration> for u64 {
193    fn from(val: CuDuration) -> Self {
194        let CuDuration(nanos) = val;
195        nanos
196    }
197}
198
199impl Sub for CuDuration {
200    type Output = Self;
201
202    fn sub(self, rhs: Self) -> Self::Output {
203        let CuDuration(lhs) = self;
204        let CuDuration(rhs) = rhs;
205        CuDuration(lhs - rhs)
206    }
207}
208
209impl Add for CuDuration {
210    type Output = Self;
211
212    fn add(self, rhs: Self) -> Self::Output {
213        let CuDuration(lhs) = self;
214        let CuDuration(rhs) = rhs;
215        CuDuration(lhs + rhs)
216    }
217}
218
219impl AddAssign for CuDuration {
220    fn add_assign(&mut self, rhs: Self) {
221        let CuDuration(lhs) = self;
222        let CuDuration(rhs) = rhs;
223        *lhs += rhs;
224    }
225}
226
227impl SubAssign for CuDuration {
228    fn sub_assign(&mut self, rhs: Self) {
229        let CuDuration(lhs) = self;
230        let CuDuration(rhs) = rhs;
231        *lhs -= rhs;
232    }
233}
234
235// a way to divide a duration by a scalar.
236// useful to compute averages for example.
237impl<T> Div<T> for CuDuration
238where
239    T: Into<u64>,
240{
241    type Output = Self;
242    fn div(self, rhs: T) -> Self {
243        let CuDuration(lhs) = self;
244        CuDuration(lhs / rhs.into())
245    }
246}
247
248// a way to multiply a duration by a scalar.
249// useful to compute offsets for example.
250// CuDuration * scalar
251impl<T> Mul<T> for CuDuration
252where
253    T: Into<u64>,
254{
255    type Output = CuDuration;
256
257    fn mul(self, rhs: T) -> CuDuration {
258        let CuDuration(lhs) = self;
259        CuDuration(lhs * rhs.into())
260    }
261}
262
263// u64 * CuDuration
264impl Mul<CuDuration> for u64 {
265    type Output = CuDuration;
266
267    fn mul(self, rhs: CuDuration) -> CuDuration {
268        let CuDuration(nanos) = rhs;
269        CuDuration(self * nanos)
270    }
271}
272
273// u32 * CuDuration
274impl Mul<CuDuration> for u32 {
275    type Output = CuDuration;
276
277    fn mul(self, rhs: CuDuration) -> CuDuration {
278        let CuDuration(nanos) = rhs;
279        CuDuration(self as u64 * nanos)
280    }
281}
282
283// i32 * CuDuration
284impl Mul<CuDuration> for i32 {
285    type Output = CuDuration;
286
287    fn mul(self, rhs: CuDuration) -> CuDuration {
288        let CuDuration(nanos) = rhs;
289        CuDuration(self as u64 * nanos)
290    }
291}
292
293impl Encode for CuDuration {
294    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
295        let CuDuration(nanos) = self;
296        nanos.encode(encoder)
297    }
298}
299
300impl<Context> Decode<Context> for CuDuration {
301    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
302        Ok(CuDuration(u64::decode(decoder)?))
303    }
304}
305
306impl<'de, Context> BorrowDecode<'de, Context> for CuDuration {
307    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
308        Ok(CuDuration(u64::decode(decoder)?))
309    }
310}
311
312impl Display for CuDuration {
313    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
314        let Self(nanos) = *self;
315        if nanos >= 86_400_000_000_000 {
316            write!(f, "{:.3} d", nanos as f64 / 86_400_000_000_000.0)
317        } else if nanos >= 3_600_000_000_000 {
318            write!(f, "{:.3} h", nanos as f64 / 3_600_000_000_000.0)
319        } else if nanos >= 60_000_000_000 {
320            write!(f, "{:.3} m", nanos as f64 / 60_000_000_000.0)
321        } else if nanos >= 1_000_000_000 {
322            write!(f, "{:.3} s", nanos as f64 / 1_000_000_000.0)
323        } else if nanos >= 1_000_000 {
324            write!(f, "{:.3} ms", nanos as f64 / 1_000_000.0)
325        } else if nanos >= 1_000 {
326            write!(f, "{:.3} µs", nanos as f64 / 1_000.0)
327        } else {
328            write!(f, "{nanos} ns")
329        }
330    }
331}
332
333/// A robot time is just a duration from a fixed point in time.
334pub type CuTime = CuDuration;
335
336/// A busy looping function based on this clock for a duration.
337/// Mainly useful for embedded to spinlocking.
338#[inline(always)]
339pub fn busy_wait_for(duration: CuDuration) {
340    busy_wait_until(CuInstant::now() + duration);
341}
342
343/// A busy looping function based on this until a specific time.
344/// Mainly useful for embedded to spinlocking.
345#[inline(always)]
346pub fn busy_wait_until(time: CuInstant) {
347    while CuInstant::now() < time {
348        core::hint::spin_loop();
349    }
350}
351
352/// Homebrewed `Option<CuDuration>` to avoid using 128bits just to represent an Option.
353#[derive(Copy, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
354#[cfg_attr(feature = "reflect", derive(Reflect))]
355pub struct OptionCuTime(CuTime);
356
357const NONE_VALUE: u64 = 0xFFFFFFFFFFFFFFFF;
358
359impl OptionCuTime {
360    #[inline]
361    pub fn is_none(&self) -> bool {
362        let Self(CuDuration(nanos)) = self;
363        *nanos == NONE_VALUE
364    }
365
366    #[inline]
367    pub const fn none() -> Self {
368        OptionCuTime(CuDuration(NONE_VALUE))
369    }
370
371    #[inline]
372    pub fn unwrap(self) -> CuTime {
373        if self.is_none() {
374            panic!("called `OptionCuTime::unwrap()` on a `None` value");
375        }
376        self.0
377    }
378}
379
380impl Display for OptionCuTime {
381    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
382        if self.is_none() {
383            write!(f, "None")
384        } else {
385            write!(f, "{}", self.0)
386        }
387    }
388}
389
390impl Default for OptionCuTime {
391    fn default() -> Self {
392        Self::none()
393    }
394}
395
396impl From<Option<CuTime>> for OptionCuTime {
397    #[inline]
398    fn from(duration: Option<CuTime>) -> Self {
399        match duration {
400            Some(duration) => OptionCuTime(duration),
401            None => OptionCuTime(CuDuration(NONE_VALUE)),
402        }
403    }
404}
405
406impl From<OptionCuTime> for Option<CuTime> {
407    #[inline]
408    fn from(val: OptionCuTime) -> Self {
409        let OptionCuTime(CuDuration(nanos)) = val;
410        if nanos == NONE_VALUE {
411            None
412        } else {
413            Some(CuDuration(nanos))
414        }
415    }
416}
417
418impl From<CuTime> for OptionCuTime {
419    #[inline]
420    fn from(val: CuTime) -> Self {
421        Some(val).into()
422    }
423}
424
425/// Represents a time range.
426#[derive(Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize, PartialEq)]
427#[cfg_attr(feature = "reflect", derive(Reflect))]
428pub struct CuTimeRange {
429    pub start: CuTime,
430    pub end: CuTime,
431}
432
433/// Builds a time range from a slice of CuTime.
434/// This is an O(n) operation.
435impl From<&[CuTime]> for CuTimeRange {
436    fn from(slice: &[CuTime]) -> Self {
437        CuTimeRange {
438            start: *slice.iter().min().expect("Empty slice"),
439            end: *slice.iter().max().expect("Empty slice"),
440        }
441    }
442}
443
444/// Represents a time range with possible undefined start or end or both.
445#[derive(Default, Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize)]
446#[cfg_attr(feature = "reflect", derive(Reflect))]
447pub struct PartialCuTimeRange {
448    pub start: OptionCuTime,
449    pub end: OptionCuTime,
450}
451
452impl Display for PartialCuTimeRange {
453    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
454        let start = if self.start.is_none() {
455            "…"
456        } else {
457            &format!("{}", self.start)
458        };
459        let end = if self.end.is_none() {
460            "…"
461        } else {
462            &format!("{}", self.end)
463        };
464        write!(f, "[{start} – {end}]")
465    }
466}
467
468/// The time of validity of a message can be more than one time but can be a time range of Tovs.
469/// For example a sub scan for a lidar, a set of images etc... can have a range of validity.
470#[derive(Default, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize, Copy)]
471#[cfg_attr(feature = "reflect", derive(Reflect))]
472pub enum Tov {
473    #[default]
474    None,
475    Time(CuTime),
476    Range(CuTimeRange),
477}
478
479impl Display for Tov {
480    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
481        match self {
482            Tov::None => write!(f, "None"),
483            Tov::Time(t) => write!(f, "{t}"),
484            Tov::Range(r) => write!(f, "[{} – {}]", r.start, r.end),
485        }
486    }
487}
488
489impl From<Option<CuDuration>> for Tov {
490    fn from(duration: Option<CuDuration>) -> Self {
491        match duration {
492            Some(duration) => Tov::Time(duration),
493            None => Tov::None,
494        }
495    }
496}
497
498impl From<CuDuration> for Tov {
499    fn from(duration: CuDuration) -> Self {
500        Tov::Time(duration)
501    }
502}
503
504/// Internal clock implementation that provides high-precision timing
505#[derive(Clone, Debug)]
506struct InternalClock {
507    // For real clocks, this stores the initialization time
508    // For mock clocks, this references the mock state
509    mock_state: Option<Arc<AtomicU64>>,
510}
511
512// Implements the std version of the RTC clock
513#[cfg(all(
514    feature = "std",
515    not(all(target_arch = "wasm32", target_os = "unknown"))
516))]
517#[inline(always)]
518fn read_rtc_ns() -> u64 {
519    std::time::SystemTime::now()
520        .duration_since(std::time::UNIX_EPOCH)
521        .unwrap()
522        .as_nanos() as u64
523}
524
525#[cfg(all(feature = "std", target_arch = "wasm32", target_os = "unknown"))]
526#[inline(always)]
527fn read_rtc_ns() -> u64 {
528    read_raw_counter()
529}
530
531#[cfg(all(
532    feature = "std",
533    not(all(target_arch = "wasm32", target_os = "unknown"))
534))]
535#[inline(always)]
536fn sleep_ns(ns: u64) {
537    std::thread::sleep(std::time::Duration::from_nanos(ns));
538}
539
540#[cfg(all(feature = "std", target_arch = "wasm32", target_os = "unknown"))]
541#[inline(always)]
542fn sleep_ns(ns: u64) {
543    let start = read_raw_counter();
544    while read_raw_counter().saturating_sub(start) < ns {
545        core::hint::spin_loop();
546    }
547}
548
549impl InternalClock {
550    fn new(
551        read_rtc_ns: impl Fn() -> u64 + Send + Sync + 'static,
552        sleep_ns: impl Fn(u64) + Send + Sync + 'static,
553    ) -> Self {
554        initialize();
555
556        // Initialize the frequency calibration
557        calibration::calibrate(read_raw_counter, read_rtc_ns, sleep_ns);
558        InternalClock { mock_state: None }
559    }
560
561    fn mock() -> (Self, Arc<AtomicU64>) {
562        let mock_state = Arc::new(AtomicU64::new(0));
563        let clock = InternalClock {
564            mock_state: Some(Arc::clone(&mock_state)),
565        };
566        (clock, mock_state)
567    }
568
569    fn now(&self) -> CuInstant {
570        if let Some(ref mock_state) = self.mock_state {
571            CuInstant(mock_state.load(Ordering::Relaxed))
572        } else {
573            CuInstant::now()
574        }
575    }
576
577    fn recent(&self) -> CuInstant {
578        // For simplicity, we use the same implementation as now()
579        // In a more sophisticated implementation, this could use a cached value
580        self.now()
581    }
582}
583
584/// A running Robot clock.
585/// The clock is a monotonic clock that starts at an arbitrary reference time.
586/// It is clone resilient, ie a clone will be the same clock, even when mocked.
587#[derive(Clone, Debug)]
588pub struct RobotClock {
589    inner: InternalClock,
590    ref_time: CuInstant,
591}
592
593/// A mock clock that can be controlled by the user.
594#[derive(Debug, Clone)]
595pub struct RobotClockMock(Arc<AtomicU64>);
596
597impl RobotClockMock {
598    pub fn increment(&self, amount: CuDuration) {
599        let Self(mock_state) = self;
600        mock_state.fetch_add(amount.as_nanos(), Ordering::Relaxed);
601    }
602
603    /// Decrements the time by the given amount.
604    /// Be careful this breaks the monotonicity of the clock.
605    pub fn decrement(&self, amount: CuDuration) {
606        let Self(mock_state) = self;
607        mock_state.fetch_sub(amount.as_nanos(), Ordering::Relaxed);
608    }
609
610    /// Gets the current value of time.
611    pub fn value(&self) -> u64 {
612        let Self(mock_state) = self;
613        mock_state.load(Ordering::Relaxed)
614    }
615
616    /// A convenient way to get the current time from the mocking side.
617    pub fn now(&self) -> CuTime {
618        let Self(mock_state) = self;
619        CuDuration(mock_state.load(Ordering::Relaxed))
620    }
621
622    /// Sets the absolute value of the time.
623    pub fn set_value(&self, value: u64) {
624        let Self(mock_state) = self;
625        mock_state.store(value, Ordering::Relaxed);
626    }
627}
628
629impl RobotClock {
630    /// Creates a RobotClock using now as its reference time.
631    /// It will start at 0ns incrementing monotonically.
632    /// This uses the std System Time as a reference clock.
633    #[cfg(feature = "std")]
634    pub fn new() -> Self {
635        let clock = InternalClock::new(read_rtc_ns, sleep_ns);
636        let ref_time = clock.now();
637        RobotClock {
638            inner: clock,
639            ref_time,
640        }
641    }
642
643    /// Builds a RobotClock using a reference RTC clock to calibrate with.
644    /// This is mandatory to use with the no-std platforms as we have no idea where to find a reference clock.
645    pub fn new_with_rtc(
646        read_rtc_ns: impl Fn() -> u64 + Send + Sync + 'static,
647        sleep_ns: impl Fn(u64) + Send + Sync + 'static,
648    ) -> Self {
649        let clock = InternalClock::new(read_rtc_ns, sleep_ns);
650        let ref_time = clock.now();
651        RobotClock {
652            inner: clock,
653            ref_time,
654        }
655    }
656
657    /// Builds a monotonic clock starting at the given reference time.
658    #[cfg(feature = "std")]
659    pub fn from_ref_time(ref_time_ns: u64) -> Self {
660        let clock = InternalClock::new(read_rtc_ns, sleep_ns);
661        let ref_time = clock.now() - CuDuration(ref_time_ns);
662        RobotClock {
663            inner: clock,
664            ref_time,
665        }
666    }
667
668    /// Overrides the RTC with a custom implementation, should be the same as the new_with_rtc.
669    pub fn from_ref_time_with_rtc(
670        read_rtc_ns: fn() -> u64,
671        sleep_ns: fn(u64),
672        ref_time_ns: u64,
673    ) -> Self {
674        let clock = InternalClock::new(read_rtc_ns, sleep_ns);
675        let ref_time = clock.now() - CuDuration(ref_time_ns);
676        RobotClock {
677            inner: clock,
678            ref_time,
679        }
680    }
681
682    /// Build a fake clock with a reference time of 0.
683    /// The RobotMock interface enables you to control all the clones of the clock given.
684    pub fn mock() -> (Self, RobotClockMock) {
685        let (clock, mock_state) = InternalClock::mock();
686        let ref_time = clock.now();
687        (
688            RobotClock {
689                inner: clock,
690                ref_time,
691            },
692            RobotClockMock(mock_state),
693        )
694    }
695
696    /// Now returns the time that passed since the reference time, usually the start time.
697    /// It is a monotonically increasing value.
698    #[inline]
699    pub fn now(&self) -> CuTime {
700        self.inner.now() - self.ref_time
701    }
702
703    /// A less precise but quicker time
704    #[inline]
705    pub fn recent(&self) -> CuTime {
706        self.inner.recent() - self.ref_time
707    }
708}
709
710/// We cannot build a default RobotClock on no-std because we don't know how to find a reference clock.
711/// Use RobotClock::new_with_rtc instead on no-std.
712#[cfg(feature = "std")]
713impl Default for RobotClock {
714    fn default() -> Self {
715        Self::new()
716    }
717}
718
719/// A trait to provide a clock to the runtime.
720pub trait ClockProvider {
721    fn get_clock(&self) -> RobotClock;
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727    use approx::assert_relative_eq;
728
729    #[test]
730    fn test_cuduration_comparison_operators() {
731        let a = CuDuration(100);
732        let b = CuDuration(200);
733
734        assert!(a < b);
735        assert!(b > a);
736        assert_ne!(a, b);
737        assert_eq!(a, CuDuration(100));
738    }
739
740    #[test]
741    fn test_cuduration_arithmetic_operations() {
742        let a = CuDuration(100);
743        let b = CuDuration(50);
744
745        assert_eq!(a + b, CuDuration(150));
746        assert_eq!(a - b, CuDuration(50));
747        assert_eq!(a * 2u32, CuDuration(200));
748        assert_eq!(a / 2u32, CuDuration(50));
749    }
750
751    #[test]
752    fn test_robot_clock_monotonic() {
753        let clock = RobotClock::new();
754        let t1 = clock.now();
755        let t2 = clock.now();
756        assert!(t2 >= t1);
757    }
758
759    #[test]
760    fn test_robot_clock_mock() {
761        let (clock, mock) = RobotClock::mock();
762        let t1 = clock.now();
763        mock.increment(CuDuration::from_millis(100));
764        let t2 = clock.now();
765        assert!(t2 > t1);
766        assert_eq!(t2 - t1, CuDuration(100_000_000)); // 100ms in nanoseconds
767    }
768
769    #[test]
770    fn test_robot_clock_clone_consistency() {
771        let (clock1, mock) = RobotClock::mock();
772        let clock2 = clock1.clone();
773
774        mock.set_value(1_000_000_000); // 1 second
775        assert_eq!(clock1.now(), clock2.now());
776    }
777
778    #[test]
779    fn test_from_ref_time() {
780        let tolerance_ms = 10f64;
781        let clock = RobotClock::from_ref_time(1_000_000_000);
782        assert_relative_eq!(
783            clock.now().as_millis() as f64,
784            CuDuration::from_secs(1).as_millis() as f64,
785            epsilon = tolerance_ms
786        );
787    }
788
789    #[test]
790    fn longest_duration() {
791        let maxcu = CuDuration(u64::MAX);
792        assert_eq!(maxcu.as_nanos(), u64::MAX);
793        let s = maxcu.as_secs();
794        let y = s / 60 / 60 / 24 / 365;
795        assert!(y >= 584); // 584 years of robot uptime, we should be good.
796    }
797
798    #[test]
799    fn test_some_time_arithmetics() {
800        let a: CuDuration = 10.into();
801        let b: CuDuration = 20.into();
802        let c = a + b;
803        assert_eq!(c.0, 30);
804        let d = b - a;
805        assert_eq!(d.0, 10);
806    }
807
808    #[test]
809    fn test_build_range_from_slice() {
810        let range = CuTimeRange::from(&[20.into(), 10.into(), 30.into()][..]);
811        assert_eq!(range.start, 10.into());
812        assert_eq!(range.end, 30.into());
813    }
814
815    #[test]
816    fn test_time_range_operations() {
817        // Test creating a time range and checking its properties
818        let start = CuTime::from(100u64);
819        let end = CuTime::from(200u64);
820        let range = CuTimeRange { start, end };
821
822        assert_eq!(range.start, start);
823        assert_eq!(range.end, end);
824
825        // Test creating from a slice
826        let times = [
827            CuTime::from(150u64),
828            CuTime::from(120u64),
829            CuTime::from(180u64),
830        ];
831        let range_from_slice = CuTimeRange::from(&times[..]);
832
833        // Range should capture min and max values
834        assert_eq!(range_from_slice.start, CuTime::from(120u64));
835        assert_eq!(range_from_slice.end, CuTime::from(180u64));
836    }
837
838    #[test]
839    fn test_partial_time_range() {
840        // Test creating a partial time range with defined start/end
841        let start = CuTime::from(100u64);
842        let end = CuTime::from(200u64);
843
844        let partial_range = PartialCuTimeRange {
845            start: OptionCuTime::from(start),
846            end: OptionCuTime::from(end),
847        };
848
849        // Test converting to Option
850        let opt_start: Option<CuTime> = partial_range.start.into();
851        let opt_end: Option<CuTime> = partial_range.end.into();
852
853        assert_eq!(opt_start, Some(start));
854        assert_eq!(opt_end, Some(end));
855
856        // Test partial range with undefined values
857        let partial_undefined = PartialCuTimeRange::default();
858        assert!(partial_undefined.start.is_none());
859        assert!(partial_undefined.end.is_none());
860    }
861
862    #[test]
863    fn test_tov_conversions() {
864        // Test different Time of Validity (Tov) variants
865        let time = CuTime::from(100u64);
866
867        // Test conversion from CuTime
868        let tov_time: Tov = time.into();
869        assert!(matches!(tov_time, Tov::Time(_)));
870
871        if let Tov::Time(t) = tov_time {
872            assert_eq!(t, time);
873        }
874
875        // Test conversion from Option<CuTime>
876        let some_time = Some(time);
877        let tov_some: Tov = some_time.into();
878        assert!(matches!(tov_some, Tov::Time(_)));
879
880        let none_time: Option<CuDuration> = None;
881        let tov_none: Tov = none_time.into();
882        assert!(matches!(tov_none, Tov::None));
883
884        // Test range
885        let start = CuTime::from(100u64);
886        let end = CuTime::from(200u64);
887        let range = CuTimeRange { start, end };
888        let tov_range = Tov::Range(range);
889
890        assert!(matches!(tov_range, Tov::Range(_)));
891    }
892
893    #[cfg(feature = "std")]
894    #[test]
895    fn test_cuduration_display() {
896        // Test the display implementation for different magnitudes
897        let nano = CuDuration(42);
898        assert_eq!(nano.to_string(), "42 ns");
899
900        let micro = CuDuration(42_000);
901        assert_eq!(micro.to_string(), "42.000 µs");
902
903        let milli = CuDuration(42_000_000);
904        assert_eq!(milli.to_string(), "42.000 ms");
905
906        let sec = CuDuration(1_500_000_000);
907        assert_eq!(sec.to_string(), "1.500 s");
908
909        let min = CuDuration(90_000_000_000);
910        assert_eq!(min.to_string(), "1.500 m");
911
912        let hour = CuDuration(3_600_000_000_000);
913        assert_eq!(hour.to_string(), "1.000 h");
914
915        let day = CuDuration(86_400_000_000_000);
916        assert_eq!(day.to_string(), "1.000 d");
917    }
918
919    #[test]
920    fn test_robot_clock_precision() {
921        // Test that RobotClock::now() and RobotClock::recent() return different values
922        // and that recent() is always <= now()
923        let clock = RobotClock::new();
924
925        // We can't guarantee the exact values, but we can check relationships
926        let recent = clock.recent();
927        let now = clock.now();
928
929        // recent() should be less than or equal to now()
930        assert!(recent <= now);
931
932        // Test precision of from_ref_time
933        let ref_time_ns = 1_000_000_000; // 1 second
934        let clock = RobotClock::from_ref_time(ref_time_ns);
935
936        // Clock should start at approximately ref_time_ns
937        let now = clock.now();
938        let now_ns: u64 = now.into();
939
940        // Allow reasonable tolerance for clock initialization time
941        let tolerance_ns = 50_000_000; // 50ms tolerance
942        assert!(now_ns >= ref_time_ns);
943        assert!(now_ns < ref_time_ns + tolerance_ns);
944    }
945
946    #[test]
947    fn test_mock_clock_advanced_operations() {
948        // Test more complex operations with the mock clock
949        let (clock, mock) = RobotClock::mock();
950
951        // Test initial state
952        assert_eq!(clock.now(), CuDuration(0));
953
954        // Test increment
955        mock.increment(CuDuration::from_secs(10));
956        assert_eq!(clock.now(), CuDuration::from_secs(10));
957
958        // Test decrement (unusual but supported)
959        mock.decrement(CuDuration::from_secs(5));
960        assert_eq!(clock.now(), CuDuration::from_secs(5));
961
962        // Test setting absolute value
963        mock.set_value(30_000_000_000); // 30 seconds in ns
964        assert_eq!(clock.now(), CuDuration::from_secs(30));
965
966        // Test that getting the time from the mock directly works
967        assert_eq!(mock.now(), CuDuration::from_secs(30));
968        assert_eq!(mock.value(), 30_000_000_000);
969    }
970
971    #[test]
972    fn test_cuduration_min_max() {
973        // Test MIN and MAX constants
974        assert_eq!(CuDuration::MIN, CuDuration(0));
975
976        // Test min/max methods
977        let a = CuDuration(100);
978        let b = CuDuration(200);
979
980        assert_eq!(a.min(b), a);
981        assert_eq!(a.max(b), b);
982        assert_eq!(b.min(a), a);
983        assert_eq!(b.max(a), b);
984
985        // Edge cases
986        assert_eq!(a.min(a), a);
987        assert_eq!(a.max(a), a);
988
989        // Test with MIN/MAX constants
990        assert_eq!(a.min(CuDuration::MIN), CuDuration::MIN);
991        assert_eq!(a.max(CuDuration::MAX), CuDuration::MAX);
992    }
993
994    #[test]
995    fn test_clock_provider_trait() {
996        // Test implementing the ClockProvider trait
997        struct TestClockProvider {
998            clock: RobotClock,
999        }
1000
1001        impl ClockProvider for TestClockProvider {
1002            fn get_clock(&self) -> RobotClock {
1003                self.clock.clone()
1004            }
1005        }
1006
1007        // Create a provider with a mock clock
1008        let (clock, mock) = RobotClock::mock();
1009        let provider = TestClockProvider { clock };
1010
1011        // Test that provider returns a clock synchronized with the original
1012        let provider_clock = provider.get_clock();
1013        assert_eq!(provider_clock.now(), CuDuration(0));
1014
1015        // Advance the mock clock and check that the provider's clock also advances
1016        mock.increment(CuDuration::from_secs(5));
1017        assert_eq!(provider_clock.now(), CuDuration::from_secs(5));
1018    }
1019}