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