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