cu29_clock/
lib.rs

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