Skip to main content

cu29_clock/
lib.rs

1//! Monotonic robot time, controlled test clocks, and optional reference discipline.
2
3#![cfg_attr(not(feature = "std"), no_std)]
4
5extern crate alloc;
6#[cfg(test)]
7extern crate approx;
8
9mod calibration;
10mod origin;
11mod raw_counter;
12pub mod sync;
13pub use origin::{ClockEpoch, ClockOrigin, ClockSource};
14
15pub use raw_counter::*;
16
17#[cfg(feature = "reflect")]
18use bevy_reflect::Reflect;
19use bincode::BorrowDecode;
20use bincode::de::BorrowDecoder;
21use bincode::de::Decoder;
22use bincode::enc::Encoder;
23use bincode::error::{DecodeError, EncodeError};
24use bincode::{Decode, Encode};
25use core::ops::{Add, Sub};
26use serde::{Deserialize, Serialize};
27
28// We use this to be able to support embedded 32bit platforms
29use portable_atomic::{AtomicU64, Ordering};
30
31use alloc::string::ToString;
32use alloc::sync::Arc;
33use alloc::vec::Vec;
34use core::fmt::{Display, Formatter};
35use core::ops::{AddAssign, Div, Mul, SubAssign};
36
37/// An instant on one robot clock's undisciplined local timeline, in nanoseconds.
38///
39/// Obtain readings through [`RobotClock::raw_now`]. Compare instants only within
40/// that clock and its clones, which share an origin and counter calibration.
41/// Execution timestamps use [`CuTime`]; converting an instant to execution time
42/// requires the clock's mapping.
43///
44/// ```rust
45/// use cu29_clock::{CuDuration, RobotClock};
46///
47/// let (clock, control) = RobotClock::mock();
48/// let start = clock.raw_now();
49/// control.increment(CuDuration::from_millis(1));
50/// let deadline = start + CuDuration::from_millis(1);
51/// assert_eq!(clock.raw_now(), deadline);
52/// assert_eq!(deadline - start, CuDuration::from_millis(1));
53/// ```
54#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
55#[cfg_attr(feature = "reflect", derive(Reflect))]
56#[repr(transparent)]
57pub struct CuInstant(u64);
58
59impl CuInstant {
60    /// Reconstructs an instant from a raw reading in the same clock's timeline.
61    pub const fn from_nanos(nanos: u64) -> Self {
62        Self(nanos)
63    }
64
65    /// Returns nanoseconds on the undisciplined local timeline.
66    pub const fn as_nanos(&self) -> u64 {
67        self.0
68    }
69}
70
71impl Sub for CuInstant {
72    type Output = CuDuration;
73
74    fn sub(self, other: Self) -> Self::Output {
75        CuDuration(self.0.saturating_sub(other.0))
76    }
77}
78
79impl Sub<CuDuration> for CuInstant {
80    type Output = Self;
81
82    fn sub(self, duration: CuDuration) -> Self::Output {
83        Self(self.0.saturating_sub(duration.as_nanos()))
84    }
85}
86
87impl Add<CuDuration> for CuInstant {
88    type Output = Self;
89
90    fn add(self, duration: CuDuration) -> Self::Output {
91        Self(self.0.saturating_add(duration.as_nanos()))
92    }
93}
94
95impl Encode for CuInstant {
96    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
97        self.0.encode(encoder)
98    }
99}
100
101impl<Context> Decode<Context> for CuInstant {
102    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
103        Ok(Self(u64::decode(decoder)?))
104    }
105}
106
107impl<'de, Context> BorrowDecode<'de, Context> for CuInstant {
108    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
109        Ok(Self(u64::decode(decoder)?))
110    }
111}
112
113/// For Robot times, the underlying type is a u64 representing nanoseconds.
114/// It is always positive to simplify the reasoning on the user side.
115#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
116#[cfg_attr(feature = "reflect", derive(Reflect))]
117pub struct CuDuration(pub u64);
118
119impl CuDuration {
120    // Lowest value a CuDuration can have.
121    pub const MIN: CuDuration = CuDuration(0u64);
122    // Highest value a CuDuration can have reserving the max value for None.
123    pub const MAX: CuDuration = CuDuration(NONE_VALUE - 1);
124
125    pub fn max(self, other: CuDuration) -> CuDuration {
126        let Self(lhs) = self;
127        let Self(rhs) = other;
128        CuDuration(lhs.max(rhs))
129    }
130
131    pub fn min(self, other: CuDuration) -> CuDuration {
132        let Self(lhs) = self;
133        let Self(rhs) = other;
134        CuDuration(lhs.min(rhs))
135    }
136
137    pub fn as_nanos(&self) -> u64 {
138        let Self(nanos) = self;
139        *nanos
140    }
141
142    pub fn as_micros(&self) -> u64 {
143        let Self(nanos) = self;
144        nanos / 1_000
145    }
146
147    pub fn as_millis(&self) -> u64 {
148        let Self(nanos) = self;
149        nanos / 1_000_000
150    }
151
152    pub fn as_secs(&self) -> u64 {
153        let Self(nanos) = self;
154        nanos / 1_000_000_000
155    }
156
157    pub fn from_nanos(nanos: u64) -> Self {
158        CuDuration(nanos)
159    }
160
161    pub fn from_micros(micros: u64) -> Self {
162        CuDuration(micros * 1_000)
163    }
164
165    pub fn from_millis(millis: u64) -> Self {
166        CuDuration(millis * 1_000_000)
167    }
168
169    pub fn from_secs(secs: u64) -> Self {
170        CuDuration(secs * 1_000_000_000)
171    }
172}
173
174/// Saturating subtraction for time and duration types.
175pub trait SaturatingSub {
176    fn saturating_sub(self, other: Self) -> Self;
177}
178
179impl SaturatingSub for CuDuration {
180    fn saturating_sub(self, other: Self) -> Self {
181        let Self(lhs) = self;
182        let Self(rhs) = other;
183        CuDuration(lhs.saturating_sub(rhs))
184    }
185}
186
187/// bridge the API with standard Durations.
188#[cfg(feature = "std")]
189impl From<std::time::Duration> for CuDuration {
190    fn from(duration: std::time::Duration) -> Self {
191        CuDuration(duration.as_nanos() as u64)
192    }
193}
194
195#[cfg(not(feature = "std"))]
196impl From<core::time::Duration> for CuDuration {
197    fn from(duration: core::time::Duration) -> Self {
198        CuDuration(duration.as_nanos() as u64)
199    }
200}
201
202#[cfg(feature = "std")]
203impl From<CuDuration> for std::time::Duration {
204    fn from(val: CuDuration) -> Self {
205        let CuDuration(nanos) = val;
206        std::time::Duration::from_nanos(nanos)
207    }
208}
209
210impl From<u64> for CuDuration {
211    fn from(duration: u64) -> Self {
212        CuDuration(duration)
213    }
214}
215
216impl From<CuDuration> for u64 {
217    fn from(val: CuDuration) -> Self {
218        let CuDuration(nanos) = val;
219        nanos
220    }
221}
222
223impl Sub for CuDuration {
224    type Output = Self;
225
226    fn sub(self, rhs: Self) -> Self::Output {
227        let CuDuration(lhs) = self;
228        let CuDuration(rhs) = rhs;
229        CuDuration(lhs - rhs)
230    }
231}
232
233impl Add for CuDuration {
234    type Output = Self;
235
236    fn add(self, rhs: Self) -> Self::Output {
237        let CuDuration(lhs) = self;
238        let CuDuration(rhs) = rhs;
239        CuDuration(lhs + rhs)
240    }
241}
242
243impl AddAssign for CuDuration {
244    fn add_assign(&mut self, rhs: Self) {
245        let CuDuration(lhs) = self;
246        let CuDuration(rhs) = rhs;
247        *lhs += rhs;
248    }
249}
250
251impl SubAssign for CuDuration {
252    fn sub_assign(&mut self, rhs: Self) {
253        let CuDuration(lhs) = self;
254        let CuDuration(rhs) = rhs;
255        *lhs -= rhs;
256    }
257}
258
259// a way to divide a duration by a scalar.
260// useful to compute averages for example.
261impl<T> Div<T> for CuDuration
262where
263    T: Into<u64>,
264{
265    type Output = Self;
266    fn div(self, rhs: T) -> Self {
267        let CuDuration(lhs) = self;
268        CuDuration(lhs / rhs.into())
269    }
270}
271
272// a way to multiply a duration by a scalar.
273// useful to compute offsets for example.
274// CuDuration * scalar
275impl<T> Mul<T> for CuDuration
276where
277    T: Into<u64>,
278{
279    type Output = CuDuration;
280
281    fn mul(self, rhs: T) -> CuDuration {
282        let CuDuration(lhs) = self;
283        CuDuration(lhs * rhs.into())
284    }
285}
286
287// u64 * CuDuration
288impl Mul<CuDuration> for u64 {
289    type Output = CuDuration;
290
291    fn mul(self, rhs: CuDuration) -> CuDuration {
292        let CuDuration(nanos) = rhs;
293        CuDuration(self * nanos)
294    }
295}
296
297// u32 * CuDuration
298impl Mul<CuDuration> for u32 {
299    type Output = CuDuration;
300
301    fn mul(self, rhs: CuDuration) -> CuDuration {
302        let CuDuration(nanos) = rhs;
303        CuDuration(self as u64 * nanos)
304    }
305}
306
307// i32 * CuDuration
308impl Mul<CuDuration> for i32 {
309    type Output = CuDuration;
310
311    fn mul(self, rhs: CuDuration) -> CuDuration {
312        let CuDuration(nanos) = rhs;
313        CuDuration(self as u64 * nanos)
314    }
315}
316
317impl Encode for CuDuration {
318    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
319        let CuDuration(nanos) = self;
320        nanos.encode(encoder)
321    }
322}
323
324impl<Context> Decode<Context> for CuDuration {
325    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
326        Ok(CuDuration(u64::decode(decoder)?))
327    }
328}
329
330impl<'de, Context> BorrowDecode<'de, Context> for CuDuration {
331    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
332        Ok(CuDuration(u64::decode(decoder)?))
333    }
334}
335
336impl Display for CuDuration {
337    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
338        let Self(nanos) = *self;
339        if nanos >= 86_400_000_000_000 {
340            write!(f, "{:.3} d", nanos as f64 / 86_400_000_000_000.0)
341        } else if nanos >= 3_600_000_000_000 {
342            write!(f, "{:.3} h", nanos as f64 / 3_600_000_000_000.0)
343        } else if nanos >= 60_000_000_000 {
344            write!(f, "{:.3} m", nanos as f64 / 60_000_000_000.0)
345        } else if nanos >= 1_000_000_000 {
346            write!(f, "{:.3} s", nanos as f64 / 1_000_000_000.0)
347        } else if nanos >= 1_000_000 {
348            write!(f, "{:.3} ms", nanos as f64 / 1_000_000.0)
349        } else if nanos >= 1_000 {
350            write!(f, "{:.3} µs", nanos as f64 / 1_000.0)
351        } else {
352            write!(f, "{nanos} ns")
353        }
354    }
355}
356
357/// A robot time is a monotonic timestamp in nanoseconds from the robot clock epoch.
358#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
359#[cfg_attr(feature = "reflect", derive(Reflect))]
360#[repr(transparent)]
361pub struct CuTime(pub u64);
362
363impl CuTime {
364    pub const MIN: CuTime = CuTime(0u64);
365    pub const MAX: CuTime = CuTime(NONE_VALUE - 1);
366
367    pub fn max(self, other: CuTime) -> CuTime {
368        let Self(lhs) = self;
369        let Self(rhs) = other;
370        CuTime(lhs.max(rhs))
371    }
372
373    pub fn min(self, other: CuTime) -> CuTime {
374        let Self(lhs) = self;
375        let Self(rhs) = other;
376        CuTime(lhs.min(rhs))
377    }
378
379    pub fn as_nanos(&self) -> u64 {
380        let Self(nanos) = self;
381        *nanos
382    }
383
384    pub fn as_micros(&self) -> u64 {
385        self.as_nanos() / 1_000
386    }
387
388    pub fn as_millis(&self) -> u64 {
389        self.as_nanos() / 1_000_000
390    }
391
392    pub fn as_secs(&self) -> u64 {
393        self.as_nanos() / 1_000_000_000
394    }
395
396    pub fn from_nanos(nanos: u64) -> Self {
397        CuTime(nanos)
398    }
399
400    pub fn from_micros(micros: u64) -> Self {
401        CuTime::from(CuDuration::from_micros(micros))
402    }
403
404    pub fn from_millis(millis: u64) -> Self {
405        CuTime::from(CuDuration::from_millis(millis))
406    }
407
408    pub fn from_secs(secs: u64) -> Self {
409        CuTime::from(CuDuration::from_secs(secs))
410    }
411}
412
413impl SaturatingSub for CuTime {
414    fn saturating_sub(self, other: Self) -> Self {
415        let Self(lhs) = self;
416        let Self(rhs) = other;
417        CuTime(lhs.saturating_sub(rhs))
418    }
419}
420
421#[cfg(feature = "std")]
422impl From<std::time::Duration> for CuTime {
423    fn from(duration: std::time::Duration) -> Self {
424        CuTime::from(CuDuration::from(duration))
425    }
426}
427
428#[cfg(not(feature = "std"))]
429impl From<core::time::Duration> for CuTime {
430    fn from(duration: core::time::Duration) -> Self {
431        CuTime::from(CuDuration::from(duration))
432    }
433}
434
435#[cfg(feature = "std")]
436impl From<CuTime> for std::time::Duration {
437    fn from(val: CuTime) -> Self {
438        std::time::Duration::from_nanos(val.as_nanos())
439    }
440}
441
442impl From<u64> for CuTime {
443    fn from(time: u64) -> Self {
444        CuTime(time)
445    }
446}
447
448impl From<CuTime> for u64 {
449    fn from(val: CuTime) -> Self {
450        let CuTime(nanos) = val;
451        nanos
452    }
453}
454
455impl From<CuDuration> for CuTime {
456    fn from(duration: CuDuration) -> Self {
457        CuTime(duration.as_nanos())
458    }
459}
460
461impl From<CuTime> for CuDuration {
462    fn from(time: CuTime) -> Self {
463        CuDuration(time.as_nanos())
464    }
465}
466
467impl Add for CuTime {
468    type Output = Self;
469
470    fn add(self, rhs: Self) -> Self::Output {
471        CuTime(self.as_nanos().saturating_add(rhs.as_nanos()))
472    }
473}
474
475impl Add<CuDuration> for CuTime {
476    type Output = Self;
477
478    fn add(self, rhs: CuDuration) -> Self::Output {
479        CuTime(self.as_nanos().saturating_add(rhs.as_nanos()))
480    }
481}
482
483impl AddAssign<CuDuration> for CuTime {
484    fn add_assign(&mut self, rhs: CuDuration) {
485        *self = *self + rhs;
486    }
487}
488
489impl AddAssign for CuTime {
490    fn add_assign(&mut self, rhs: Self) {
491        *self = *self + rhs;
492    }
493}
494
495impl Sub<CuDuration> for CuTime {
496    type Output = Self;
497
498    fn sub(self, rhs: CuDuration) -> Self::Output {
499        CuTime(self.as_nanos().saturating_sub(rhs.as_nanos()))
500    }
501}
502
503impl SubAssign<CuDuration> for CuTime {
504    fn sub_assign(&mut self, rhs: CuDuration) {
505        *self = *self - rhs;
506    }
507}
508
509impl SubAssign for CuTime {
510    fn sub_assign(&mut self, rhs: Self) {
511        *self = CuTime(self.as_nanos().saturating_sub(rhs.as_nanos()));
512    }
513}
514
515impl Sub for CuTime {
516    type Output = CuDuration;
517
518    fn sub(self, rhs: Self) -> Self::Output {
519        CuDuration(self.as_nanos().saturating_sub(rhs.as_nanos()))
520    }
521}
522
523impl<T> Div<T> for CuTime
524where
525    T: Into<u64>,
526{
527    type Output = Self;
528
529    fn div(self, rhs: T) -> Self::Output {
530        CuTime(self.as_nanos() / rhs.into())
531    }
532}
533
534impl<T> Mul<T> for CuTime
535where
536    T: Into<u64>,
537{
538    type Output = Self;
539
540    fn mul(self, rhs: T) -> Self::Output {
541        CuTime(self.as_nanos() * rhs.into())
542    }
543}
544
545impl Mul<CuTime> for u64 {
546    type Output = CuTime;
547
548    fn mul(self, rhs: CuTime) -> Self::Output {
549        CuTime(self * rhs.as_nanos())
550    }
551}
552
553impl Mul<CuTime> for u32 {
554    type Output = CuTime;
555
556    fn mul(self, rhs: CuTime) -> Self::Output {
557        CuTime(self as u64 * rhs.as_nanos())
558    }
559}
560
561impl Mul<CuTime> for i32 {
562    type Output = CuTime;
563
564    fn mul(self, rhs: CuTime) -> Self::Output {
565        CuTime(self as u64 * rhs.as_nanos())
566    }
567}
568
569impl Encode for CuTime {
570    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
571        self.as_nanos().encode(encoder)
572    }
573}
574
575impl<Context> Decode<Context> for CuTime {
576    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
577        Ok(CuTime(u64::decode(decoder)?))
578    }
579}
580
581impl<'de, Context> BorrowDecode<'de, Context> for CuTime {
582    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
583        Ok(CuTime(u64::decode(decoder)?))
584    }
585}
586
587impl Display for CuTime {
588    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
589        CuDuration(self.as_nanos()).fmt(f)
590    }
591}
592
593/// Homebrewed `Option<CuDuration>` to avoid using 128bits just to represent an Option.
594#[derive(Copy, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
595#[cfg_attr(feature = "reflect", derive(Reflect))]
596pub struct OptionCuTime(CuTime);
597
598const NONE_VALUE: u64 = 0xFFFFFFFFFFFFFFFF;
599
600impl OptionCuTime {
601    pub const NONE_SENTINEL_NANOS: u64 = NONE_VALUE;
602
603    #[inline]
604    pub fn is_none(&self) -> bool {
605        let Self(CuTime(nanos)) = self;
606        *nanos == NONE_VALUE
607    }
608
609    #[inline]
610    pub const fn none() -> Self {
611        OptionCuTime(CuTime(NONE_VALUE))
612    }
613
614    #[inline]
615    pub fn unwrap(self) -> CuTime {
616        if self.is_none() {
617            panic!("called `OptionCuTime::unwrap()` on a `None` value");
618        }
619        self.0
620    }
621}
622
623impl Display for OptionCuTime {
624    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
625        if self.is_none() {
626            write!(f, "None")
627        } else {
628            write!(f, "{}", self.0)
629        }
630    }
631}
632
633impl Default for OptionCuTime {
634    fn default() -> Self {
635        Self::none()
636    }
637}
638
639impl From<Option<CuTime>> for OptionCuTime {
640    #[inline]
641    fn from(duration: Option<CuTime>) -> Self {
642        match duration {
643            Some(duration) => OptionCuTime(duration),
644            None => OptionCuTime(CuTime(NONE_VALUE)),
645        }
646    }
647}
648
649impl From<OptionCuTime> for Option<CuTime> {
650    #[inline]
651    fn from(val: OptionCuTime) -> Self {
652        let OptionCuTime(CuTime(nanos)) = val;
653        if nanos == NONE_VALUE {
654            None
655        } else {
656            Some(CuTime(nanos))
657        }
658    }
659}
660
661impl From<CuTime> for OptionCuTime {
662    #[inline]
663    fn from(val: CuTime) -> Self {
664        Some(val).into()
665    }
666}
667
668#[derive(Debug, Clone, Copy, PartialEq, Eq)]
669pub enum ClockDebugScalarKind {
670    Time,
671    OptionalTime,
672    Duration,
673}
674
675#[derive(Debug, Clone, Copy, PartialEq, Eq)]
676pub struct ClockDebugScalarRegistration {
677    pub type_path: &'static str,
678    pub kind: ClockDebugScalarKind,
679}
680
681pub fn debug_scalar_registrations() -> Vec<ClockDebugScalarRegistration> {
682    alloc::vec![
683        ClockDebugScalarRegistration {
684            type_path: core::any::type_name::<CuDuration>(),
685            kind: ClockDebugScalarKind::Duration,
686        },
687        ClockDebugScalarRegistration {
688            type_path: core::any::type_name::<CuTime>(),
689            kind: ClockDebugScalarKind::Time,
690        },
691        ClockDebugScalarRegistration {
692            type_path: core::any::type_name::<OptionCuTime>(),
693            kind: ClockDebugScalarKind::OptionalTime,
694        },
695    ]
696}
697
698/// Represents a time range.
699#[derive(Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize, PartialEq)]
700#[cfg_attr(feature = "reflect", derive(Reflect))]
701pub struct CuTimeRange {
702    pub start: CuTime,
703    pub end: CuTime,
704}
705
706/// Builds a time range from a slice of CuTime.
707/// This is an O(n) operation.
708impl From<&[CuTime]> for CuTimeRange {
709    fn from(slice: &[CuTime]) -> Self {
710        CuTimeRange {
711            start: *slice.iter().min().expect("Empty slice"),
712            end: *slice.iter().max().expect("Empty slice"),
713        }
714    }
715}
716
717/// Represents a time range with possible undefined start or end or both.
718#[derive(Default, Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize)]
719#[cfg_attr(feature = "reflect", derive(Reflect))]
720pub struct PartialCuTimeRange {
721    pub start: OptionCuTime,
722    pub end: OptionCuTime,
723}
724
725impl Display for PartialCuTimeRange {
726    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
727        let start = if self.start.is_none() {
728            "…"
729        } else {
730            &self.start.to_string()
731        };
732        let end = if self.end.is_none() {
733            "…"
734        } else {
735            &self.end.to_string()
736        };
737        write!(f, "[{start} – {end}]")
738    }
739}
740
741/// The time of validity of a message can be more than one time but can be a time range of Tovs.
742/// For example a sub scan for a lidar, a set of images etc... can have a range of validity.
743#[derive(Default, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize, Copy)]
744#[cfg_attr(feature = "reflect", derive(Reflect))]
745pub enum Tov {
746    #[default]
747    None,
748    Time(CuTime),
749    Range(CuTimeRange),
750}
751
752impl Display for Tov {
753    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
754        match self {
755            Tov::None => write!(f, "None"),
756            Tov::Time(t) => write!(f, "{t}"),
757            Tov::Range(r) => write!(f, "[{} – {}]", r.start, r.end),
758        }
759    }
760}
761
762impl From<Option<CuDuration>> for Tov {
763    fn from(duration: Option<CuDuration>) -> Self {
764        duration.map(CuTime::from).map_or(Tov::None, Tov::Time)
765    }
766}
767
768impl From<Option<CuTime>> for Tov {
769    fn from(time: Option<CuTime>) -> Self {
770        time.map_or(Tov::None, Tov::Time)
771    }
772}
773
774impl From<CuDuration> for Tov {
775    fn from(duration: CuDuration) -> Self {
776        Tov::Time(duration.into())
777    }
778}
779
780impl From<CuTime> for Tov {
781    fn from(time: CuTime) -> Self {
782        Tov::Time(time)
783    }
784}
785
786/// Internal clock implementation that provides high-precision timing
787#[derive(Clone, Debug)]
788struct InternalClock {
789    mock_state: Option<Arc<AtomicU64>>,
790    // Hardware-counter anchor and measured frequency in Hz, copied by clones.
791    frequency: (u64, u64),
792}
793
794// Implements the std version of the RTC clock
795#[cfg(all(
796    feature = "std",
797    not(all(target_arch = "wasm32", target_os = "unknown"))
798))]
799#[inline(always)]
800fn read_rtc_ns() -> u64 {
801    std::time::SystemTime::now()
802        .duration_since(std::time::UNIX_EPOCH)
803        .unwrap()
804        .as_nanos() as u64
805}
806
807#[cfg(all(feature = "std", target_arch = "wasm32", target_os = "unknown"))]
808#[inline(always)]
809fn read_rtc_ns() -> u64 {
810    read_raw_counter()
811}
812
813#[cfg(all(
814    feature = "std",
815    not(all(target_arch = "wasm32", target_os = "unknown"))
816))]
817#[inline(always)]
818fn sleep_ns(ns: u64) {
819    std::thread::sleep(std::time::Duration::from_nanos(ns));
820}
821
822#[cfg(all(feature = "std", target_arch = "wasm32", target_os = "unknown"))]
823#[inline(always)]
824fn sleep_ns(ns: u64) {
825    let start = read_raw_counter();
826    while read_raw_counter().saturating_sub(start) < ns {
827        core::hint::spin_loop();
828    }
829}
830
831impl InternalClock {
832    fn new(
833        read_rtc_ns: impl Fn() -> u64 + Send + Sync + 'static,
834        sleep_ns: impl Fn(u64) + Send + Sync + 'static,
835    ) -> Self {
836        initialize();
837
838        let calibration = calibration::measure(read_raw_counter, read_rtc_ns, sleep_ns)
839            .expect("cu29_clock calibration failed: raw counter did not advance");
840        InternalClock {
841            mock_state: None,
842            frequency: (calibration.counter, calibration.frequency_hz),
843        }
844    }
845
846    fn mock() -> (Self, Arc<AtomicU64>) {
847        let mock_state = Arc::new(AtomicU64::new(0));
848        let clock = InternalClock {
849            mock_state: Some(Arc::clone(&mock_state)),
850            frequency: (0, 1_000_000_000),
851        };
852        (clock, mock_state)
853    }
854
855    fn now(&self) -> u64 {
856        if let Some(ref mock_state) = self.mock_state {
857            return mock_state.load(Ordering::Relaxed);
858        }
859        let (anchor, hz) = self.frequency;
860        let ticks = read_raw_counter().saturating_sub(anchor);
861        (u128::from(ticks) * 1_000_000_000 / u128::from(hz)).min(u128::from(u64::MAX)) as u64
862    }
863
864    fn recent(&self) -> u64 {
865        // For simplicity, we use the same implementation as now()
866        // In a more sophisticated implementation, this could use a cached value
867        self.now()
868    }
869}
870
871/// A running Robot clock.
872/// The clock is a monotonic clock that starts at an arbitrary reference time.
873/// It is clone resilient, ie a clone will be the same clock, even when mocked.
874#[derive(Clone, Debug)]
875pub struct RobotClock {
876    inner: InternalClock,
877    #[cfg(not(feature = "clock-sync"))]
878    raw_anchor: u64,
879    #[cfg(not(feature = "clock-sync"))]
880    time_anchor: u64,
881    #[cfg(not(feature = "clock-sync"))]
882    origin: ClockOrigin,
883    #[cfg(feature = "clock-sync")]
884    mapping: Arc<sync::SharedClock>,
885}
886
887/// A mock clock that can be controlled by the user.
888#[derive(Debug, Clone)]
889pub struct RobotClockMock(Arc<AtomicU64>);
890
891impl RobotClockMock {
892    pub fn increment(&self, amount: CuDuration) {
893        let Self(mock_state) = self;
894        mock_state.fetch_add(amount.as_nanos(), Ordering::Relaxed);
895    }
896
897    /// Decrements the time by the given amount.
898    /// Be careful this breaks the monotonicity of the clock.
899    pub fn decrement(&self, amount: CuDuration) {
900        let Self(mock_state) = self;
901        mock_state.fetch_sub(amount.as_nanos(), Ordering::Relaxed);
902    }
903
904    /// Gets the current value of time.
905    pub fn value(&self) -> u64 {
906        let Self(mock_state) = self;
907        mock_state.load(Ordering::Relaxed)
908    }
909
910    /// Returns the controlled counter as a legacy [`CuTime`] value.
911    ///
912    /// For execution time, read [`RobotClock::now`] on the associated clock.
913    /// For raw instants, use [`Self::raw_now`].
914    pub fn now(&self) -> CuTime {
915        let Self(mock_state) = self;
916        CuTime(mock_state.load(Ordering::Relaxed))
917    }
918
919    /// Reads the controlled undisciplined timeline of the associated clock.
920    pub fn raw_now(&self) -> CuInstant {
921        CuInstant(self.0.load(Ordering::Relaxed))
922    }
923
924    /// Sets the absolute value of the time.
925    pub fn set_value(&self, value: u64) {
926        let Self(mock_state) = self;
927        mock_state.store(value, Ordering::Relaxed);
928    }
929}
930
931impl RobotClock {
932    /// Whether time is controlled explicitly by a mock handle.
933    pub fn is_mock(&self) -> bool {
934        self.inner.mock_state.is_some()
935    }
936
937    fn from_inner(inner: InternalClock, initial: u64) -> Self {
938        let raw = inner.now();
939        let origin = ClockOrigin {
940            source: if inner.mock_state.is_some() {
941                ClockSource::Mock
942            } else {
943                ClockSource::LocalMonotonic
944            },
945            epoch: if initial == 0 {
946                ClockEpoch::Local
947            } else {
948                ClockEpoch::Unknown
949            },
950        };
951        Self::from_inner_with_origin(inner, raw, initial, origin)
952    }
953
954    fn from_inner_with_origin(
955        inner: InternalClock,
956        raw: u64,
957        initial: u64,
958        origin: ClockOrigin,
959    ) -> Self {
960        Self {
961            inner,
962            #[cfg(not(feature = "clock-sync"))]
963            raw_anchor: raw,
964            #[cfg(not(feature = "clock-sync"))]
965            time_anchor: initial,
966            #[cfg(not(feature = "clock-sync"))]
967            origin,
968            #[cfg(feature = "clock-sync")]
969            mapping: Arc::new(sync::SharedClock::new(raw, initial, origin)),
970        }
971    }
972
973    /// Creates a clock with a local epoch using the host RTC for calibration.
974    #[cfg(feature = "std")]
975    pub fn new() -> Self {
976        Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), 0)
977    }
978
979    /// Calibrates the architecture counter against an application RTC.
980    pub fn new_with_rtc(
981        read_rtc_ns: impl Fn() -> u64 + Send + Sync + 'static,
982        sleep_ns: impl Fn(u64) + Send + Sync + 'static,
983    ) -> Self {
984        Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), 0)
985    }
986
987    /// Creates a clock from a known positive architecture-counter frequency.
988    ///
989    /// The BSP initializes its counter before calling this method.
990    /// Calibration is local to this clock and does not change existing clocks.
991    /// Cortex-M callers must retain the backend's single-reader contract and read
992    /// often enough to extend the hardware counter before it wraps.
993    #[cfg(feature = "clock-sync")]
994    pub fn new_with_frequency(hz: u64) -> Result<Self, sync::SyncError> {
995        if hz == 0 {
996            return Err(sync::SyncError::InvalidConfig);
997        }
998        let anchor = read_raw_counter();
999        Ok(Self::from_inner(
1000            InternalClock {
1001                mock_state: None,
1002                frequency: (anchor, hz),
1003            },
1004            0,
1005        ))
1006    }
1007
1008    /// Builds a monotonic clock starting at the requested reference time.
1009    #[cfg(feature = "std")]
1010    pub fn from_ref_time(ref_time_ns: u64) -> Self {
1011        Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), ref_time_ns)
1012    }
1013
1014    /// Builds a clock starting at the requested time using an application RTC.
1015    pub fn from_ref_time_with_rtc(
1016        read_rtc_ns: fn() -> u64,
1017        sleep_ns: fn(u64),
1018        ref_time_ns: u64,
1019    ) -> Self {
1020        Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), ref_time_ns)
1021    }
1022
1023    /// Builds a local monotonic clock at provider-selected reference nanoseconds.
1024    ///
1025    /// Metadata never transforms the timestamp or implies live synchronization.
1026    #[cfg(feature = "std")]
1027    pub fn from_ref_time_with_origin(ref_time_ns: u64, origin: ClockOrigin) -> Self {
1028        let inner = InternalClock::new(read_rtc_ns, sleep_ns);
1029        let raw = inner.now();
1030        Self::from_inner_with_origin(inner, raw, ref_time_ns, origin)
1031    }
1032
1033    /// Builds a clock at a named epoch using application RTC calibration.
1034    ///
1035    /// The RTC only calibrates elapsed time; `origin` describes the supplied epoch.
1036    pub fn from_ref_time_with_rtc_and_origin(
1037        read_rtc_ns: fn() -> u64,
1038        sleep_ns: fn(u64),
1039        ref_time_ns: u64,
1040        origin: ClockOrigin,
1041    ) -> Self {
1042        let inner = InternalClock::new(read_rtc_ns, sleep_ns);
1043        let raw = inner.now();
1044        Self::from_inner_with_origin(inner, raw, ref_time_ns, origin)
1045    }
1046
1047    /// Builds a clock controlled by a mock, shared by all clones.
1048    pub fn mock() -> (Self, RobotClockMock) {
1049        let (inner, state) = InternalClock::mock();
1050        (Self::from_inner(inner, 0), RobotClockMock(state))
1051    }
1052
1053    /// Reads execution time: local elapsed time, or the selected reference epoch.
1054    #[inline]
1055    pub fn now(&self) -> CuTime {
1056        #[cfg(not(feature = "clock-sync"))]
1057        {
1058            CuTime(
1059                self.time_anchor
1060                    .saturating_add(self.inner.now().saturating_sub(self.raw_anchor)),
1061            )
1062        }
1063        #[cfg(feature = "clock-sync")]
1064        {
1065            self.mapping.now(self.raw_now())
1066        }
1067    }
1068
1069    /// Describes the source and epoch of execution timestamps without converting them.
1070    ///
1071    /// Reference metadata is published with the execution mapping and shared by
1072    /// clones. Before initial acquisition, the local origin remains in effect.
1073    /// During resynchronization, the previous epoch remains in effect while the
1074    /// execution clock slews. Providers must preserve the epoch across resync.
1075    /// This accessor performs no counter read, allocation, or reference I/O.
1076    ///
1077    /// ```rust
1078    /// use cu29_clock::{ClockEpoch, ClockSource, RobotClock};
1079    /// let (clock, _) = RobotClock::mock();
1080    /// assert_eq!(clock.origin().source, ClockSource::Mock);
1081    /// assert_eq!(clock.origin().epoch, ClockEpoch::Local);
1082    /// ```
1083    pub fn origin(&self) -> ClockOrigin {
1084        #[cfg(feature = "clock-sync")]
1085        {
1086            self.mapping.origin()
1087        }
1088        #[cfg(not(feature = "clock-sync"))]
1089        {
1090            self.origin
1091        }
1092    }
1093
1094    /// Reads the same timeline through the backend's recent-time operation.
1095    #[inline]
1096    pub fn recent(&self) -> CuTime {
1097        #[cfg(not(feature = "clock-sync"))]
1098        {
1099            CuTime(
1100                self.time_anchor
1101                    .saturating_add(self.inner.recent().saturating_sub(self.raw_anchor)),
1102            )
1103        }
1104        #[cfg(feature = "clock-sync")]
1105        {
1106            self.mapping.now(CuInstant(self.inner.recent()))
1107        }
1108    }
1109
1110    /// Reads undisciplined local counter time in nanoseconds.
1111    ///
1112    /// Clones share the counter origin and calibration. Independently constructed
1113    /// clocks have their own origins; compare raw readings only within one clock
1114    /// and its clones. Reference capture and elapsed-time measurements use this
1115    /// timeline, which is unaffected by execution-clock synchronization.
1116    ///
1117    /// ```rust
1118    /// use cu29_clock::{CuDuration, RobotClock};
1119    ///
1120    /// let (clock, control) = RobotClock::mock();
1121    /// let start = clock.raw_now();
1122    /// control.increment(CuDuration::from_millis(1));
1123    /// assert_eq!(clock.raw_now() - start, CuDuration::from_millis(1));
1124    /// ```
1125    pub fn raw_now(&self) -> CuInstant {
1126        CuInstant(self.inner.now())
1127    }
1128
1129    /// Spins for a duration measured on this clock's undisciplined timeline.
1130    #[inline(always)]
1131    pub fn busy_wait_for(&self, duration: CuDuration) {
1132        self.busy_wait_until(self.raw_now() + duration);
1133    }
1134
1135    /// Spins until a raw deadline from this clock or one of its clones.
1136    #[inline(always)]
1137    pub fn busy_wait_until(&self, time: CuInstant) {
1138        while self.raw_now() < time {
1139            core::hint::spin_loop();
1140        }
1141    }
1142
1143    /// Current domain, uncertainty and synchronization health.
1144    ///
1145    /// Available with or without `clock-sync`. Returns `None` when synchronization
1146    /// is disabled or no discipline has been published. Without the feature this
1147    /// does not read the counter or require synchronization storage.
1148    ///
1149    /// ```rust
1150    /// use cu29_clock::{RobotClock, sync::SyncStatus};
1151    ///
1152    /// let (clock, _) = RobotClock::mock();
1153    /// let quality: Option<SyncStatus> = clock.sync_status();
1154    /// assert_eq!(quality, None);
1155    /// ```
1156    #[inline]
1157    pub fn sync_status(&self) -> Option<sync::SyncStatus> {
1158        #[cfg(feature = "clock-sync")]
1159        {
1160            self.mapping.read().status(self.raw_now())
1161        }
1162        #[cfg(not(feature = "clock-sync"))]
1163        {
1164            None
1165        }
1166    }
1167}
1168
1169/// We cannot build a default RobotClock on no-std because we don't know how to find a reference clock.
1170/// Use RobotClock::new_with_rtc instead on no-std.
1171#[cfg(feature = "std")]
1172impl Default for RobotClock {
1173    fn default() -> Self {
1174        Self::new()
1175    }
1176}
1177
1178/// A trait to provide a clock to the runtime.
1179pub trait ClockProvider {
1180    fn get_clock(&self) -> RobotClock;
1181}
1182
1183impl bincode::ValueDecode for CuTime {
1184    const DECODE: &'static bincode::ValueDecodeSpec = <u64 as bincode::ValueDecode>::DECODE;
1185    const METADATA: &'static [bincode::value_decode::ValueMetadata] =
1186        &[bincode::value_decode::ValueMetadata::Quantity(
1187            bincode::value_decode::QuantityMetadata::time(
1188                bincode::value_decode::TimeStorageUnit::Nanosecond,
1189            ),
1190        )];
1191}
1192
1193impl bincode::ValueDecode for CuInstant {
1194    const DECODE: &'static bincode::ValueDecodeSpec = <u64 as bincode::ValueDecode>::DECODE;
1195    const METADATA: &'static [bincode::value_decode::ValueMetadata] =
1196        &[bincode::value_decode::ValueMetadata::Quantity(
1197            bincode::value_decode::QuantityMetadata::time(
1198                bincode::value_decode::TimeStorageUnit::Nanosecond,
1199            ),
1200        )];
1201}
1202
1203impl bincode::ValueDecode for CuDuration {
1204    const DECODE: &'static bincode::ValueDecodeSpec = <u64 as bincode::ValueDecode>::DECODE;
1205    const METADATA: &'static [bincode::value_decode::ValueMetadata] =
1206        &[bincode::value_decode::ValueMetadata::Quantity(
1207            bincode::value_decode::QuantityMetadata::time(
1208                bincode::value_decode::TimeStorageUnit::Nanosecond,
1209            ),
1210        )];
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215    use super::*;
1216    #[cfg(feature = "std")]
1217    use approx::assert_relative_eq;
1218
1219    #[test]
1220    fn test_unconfigured_sync_status_is_available_without_discipline() {
1221        let (clock, mock) = RobotClock::mock();
1222        let clone = clock.clone();
1223        for value in [0, 1_000_000_000, 10_000_000_000] {
1224            mock.set_value(value);
1225            let quality: Option<sync::SyncStatus> = clock.sync_status();
1226            assert_eq!(quality, None);
1227            assert_eq!(clone.sync_status(), None);
1228            assert_eq!(clock.origin(), clone.origin());
1229            assert_eq!(clock.origin().epoch, ClockEpoch::Local);
1230            assert_eq!(clock.origin().source, ClockSource::Mock);
1231            assert_eq!(clock.now(), CuTime(value));
1232        }
1233    }
1234
1235    #[cfg(feature = "std")]
1236    #[test]
1237    fn test_named_reference_epoch_is_shared_without_conversion() {
1238        let origin = ClockOrigin {
1239            source: ClockSource::Gnss,
1240            epoch: ClockEpoch::Gps,
1241        };
1242        let initial = 1_400_000_000_000_000_000;
1243        let clock = RobotClock::from_ref_time_with_origin(initial, origin);
1244        assert_eq!(clock.origin(), origin);
1245        assert_eq!(clock.clone().origin(), origin);
1246        assert!(clock.now().0 >= initial);
1247        assert!(clock.now().0 < initial + 1_000_000_000);
1248        assert_eq!(clock.sync_status(), None);
1249    }
1250
1251    #[test]
1252    fn test_raw_instant_arithmetic_and_mock_readings() {
1253        let (clock, mock) = RobotClock::mock();
1254        let start = clock.raw_now();
1255        let duration = CuDuration::from_millis(1);
1256        mock.increment(duration);
1257        assert_eq!(clock.raw_now(), start + duration);
1258        assert_eq!(mock.raw_now(), clock.raw_now());
1259        assert_eq!(clock.raw_now() - start, duration);
1260        assert_eq!(clock.raw_now() - duration, start);
1261        assert_eq!(start - clock.raw_now(), CuDuration(0));
1262        assert_eq!(start - duration, start);
1263        let last = CuInstant::from_nanos(u64::MAX);
1264        assert_eq!(last + duration, last);
1265        assert_eq!(
1266            core::mem::size_of::<CuInstant>(),
1267            core::mem::size_of::<u64>()
1268        );
1269    }
1270
1271    #[test]
1272    fn test_raw_observation_preserves_timestamp_encoding() {
1273        let domain = sync::ClockDomain {
1274            id: 1,
1275            identity: *b"testroot",
1276            session: 2,
1277        };
1278        for nanos in [0, 250, 1_000_000_000, CuTime::MAX.as_nanos()] {
1279            let instant = CuInstant::from_nanos(nanos);
1280            let encoded = bincode::encode_to_vec(instant, bincode::config::standard()).unwrap();
1281            assert_eq!(
1282                encoded,
1283                bincode::encode_to_vec(CuTime(nanos), bincode::config::standard()).unwrap()
1284            );
1285            let (decoded, consumed): (CuInstant, _) =
1286                bincode::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
1287            assert_eq!(decoded, instant);
1288            assert_eq!(consumed, encoded.len());
1289            let observation = sync::ClockObservation {
1290                raw_local: instant,
1291                parent_ns: 10_000_000_000,
1292                origin: ClockOrigin::default(),
1293                uncertainty: CuDuration(100),
1294                domain,
1295            };
1296            let reference_fields = (
1297                CuTime(nanos),
1298                observation.parent_ns,
1299                observation.origin,
1300                observation.uncertainty,
1301                domain,
1302            );
1303            assert_eq!(
1304                bincode::encode_to_vec(observation, bincode::config::standard()).unwrap(),
1305                bincode::encode_to_vec(reference_fields, bincode::config::standard()).unwrap()
1306            );
1307        }
1308    }
1309
1310    #[test]
1311    fn test_cuduration_comparison_operators() {
1312        let a = CuDuration(100);
1313        let b = CuDuration(200);
1314
1315        assert!(a < b);
1316        assert!(b > a);
1317        assert_ne!(a, b);
1318        assert_eq!(a, CuDuration(100));
1319    }
1320
1321    #[test]
1322    fn test_cuduration_arithmetic_operations() {
1323        let a = CuDuration(100);
1324        let b = CuDuration(50);
1325
1326        assert_eq!(a + b, CuDuration(150));
1327        assert_eq!(a - b, CuDuration(50));
1328        assert_eq!(a * 2u32, CuDuration(200));
1329        assert_eq!(a / 2u32, CuDuration(50));
1330    }
1331
1332    #[cfg(feature = "std")]
1333    #[test]
1334    fn test_robot_clock_monotonic() {
1335        let clock = RobotClock::new();
1336        let t1 = clock.now();
1337        let t2 = clock.now();
1338        assert!(t2 >= t1);
1339    }
1340
1341    #[test]
1342    fn test_robot_clock_mock() {
1343        let (clock, mock) = RobotClock::mock();
1344        let t1 = clock.now();
1345        mock.increment(CuDuration::from_millis(100));
1346        let t2 = clock.now();
1347        assert!(t2 > t1);
1348        assert_eq!(t2 - t1, CuDuration(100_000_000)); // 100ms in nanoseconds
1349    }
1350
1351    #[test]
1352    fn test_robot_clock_clone_consistency() {
1353        let (clock1, mock) = RobotClock::mock();
1354        let clock2 = clock1.clone();
1355
1356        mock.set_value(1_000_000_000); // 1 second
1357        assert_eq!(clock1.now(), clock2.now());
1358    }
1359
1360    #[test]
1361    fn test_raw_mock_clock_shares_control_with_clones() {
1362        let (clock, mock) = RobotClock::mock();
1363        let clone = clock.clone();
1364        mock.set_value(1_000_000_000);
1365        assert_eq!(clock.raw_now(), CuInstant::from_nanos(1_000_000_000));
1366        assert_eq!(clone.raw_now(), clock.raw_now());
1367        mock.decrement(CuDuration(10));
1368        assert_eq!(clock.raw_now(), CuInstant::from_nanos(999_999_990));
1369    }
1370
1371    #[cfg(feature = "std")]
1372    #[test]
1373    fn test_custom_rtc_does_not_recalibrate_existing_clones() {
1374        let clock = RobotClock::new();
1375        let clone = clock.clone();
1376        let before = clock.now();
1377        let raw_before = clock.raw_now();
1378        let rtc = Arc::new(AtomicU64::new(0));
1379        let custom =
1380            RobotClock::new_with_rtc(move || rtc.fetch_add(10_000_000, Ordering::Relaxed), |_| {});
1381        assert!(clock.now() >= before);
1382        assert!(clone.raw_now() >= raw_before);
1383        assert!(clone.now().0 - before.0 < 1_000_000_000);
1384        assert_eq!(clock.inner.frequency, clone.inner.frequency);
1385        assert_ne!(clock.inner.frequency, custom.inner.frequency);
1386    }
1387
1388    #[cfg(feature = "std")]
1389    #[test]
1390    fn test_busy_wait_uses_raw_timeline_with_reference_epoch() {
1391        let clock = RobotClock::from_ref_time(5_000_000_000);
1392        let duration = CuDuration(100_000);
1393        let deadline = clock.raw_now() + duration;
1394        clock.busy_wait_until(deadline);
1395        assert!(clock.raw_now() >= deadline);
1396        let before = clock.raw_now();
1397        clock.busy_wait_for(duration);
1398        assert!(clock.raw_now() - before >= duration);
1399    }
1400
1401    #[cfg(feature = "std")]
1402    #[test]
1403    fn test_from_ref_time() {
1404        let tolerance_ms = 10f64;
1405        let clock = RobotClock::from_ref_time(1_000_000_000);
1406        assert_relative_eq!(
1407            clock.now().as_millis() as f64,
1408            CuDuration::from_secs(1).as_millis() as f64,
1409            epsilon = tolerance_ms
1410        );
1411    }
1412
1413    #[test]
1414    fn longest_duration() {
1415        let maxcu = CuDuration(u64::MAX);
1416        assert_eq!(maxcu.as_nanos(), u64::MAX);
1417        let s = maxcu.as_secs();
1418        let y = s / 60 / 60 / 24 / 365;
1419        assert!(y >= 584); // 584 years of robot uptime, we should be good.
1420    }
1421
1422    #[test]
1423    fn test_some_time_arithmetics() {
1424        let a: CuDuration = 10.into();
1425        let b: CuDuration = 20.into();
1426        let c = a + b;
1427        assert_eq!(c.0, 30);
1428        let d = b - a;
1429        assert_eq!(d.0, 10);
1430    }
1431
1432    #[test]
1433    fn test_build_range_from_slice() {
1434        let range = CuTimeRange::from(&[20.into(), 10.into(), 30.into()][..]);
1435        assert_eq!(range.start, 10.into());
1436        assert_eq!(range.end, 30.into());
1437    }
1438
1439    #[test]
1440    fn test_time_range_operations() {
1441        // Test creating a time range and checking its properties
1442        let start = CuTime::from(100u64);
1443        let end = CuTime::from(200u64);
1444        let range = CuTimeRange { start, end };
1445
1446        assert_eq!(range.start, start);
1447        assert_eq!(range.end, end);
1448
1449        // Test creating from a slice
1450        let times = [
1451            CuTime::from(150u64),
1452            CuTime::from(120u64),
1453            CuTime::from(180u64),
1454        ];
1455        let range_from_slice = CuTimeRange::from(&times[..]);
1456
1457        // Range should capture min and max values
1458        assert_eq!(range_from_slice.start, CuTime::from(120u64));
1459        assert_eq!(range_from_slice.end, CuTime::from(180u64));
1460    }
1461
1462    #[test]
1463    fn test_partial_time_range() {
1464        // Test creating a partial time range with defined start/end
1465        let start = CuTime::from(100u64);
1466        let end = CuTime::from(200u64);
1467
1468        let partial_range = PartialCuTimeRange {
1469            start: OptionCuTime::from(start),
1470            end: OptionCuTime::from(end),
1471        };
1472
1473        // Test converting to Option
1474        let opt_start: Option<CuTime> = partial_range.start.into();
1475        let opt_end: Option<CuTime> = partial_range.end.into();
1476
1477        assert_eq!(opt_start, Some(start));
1478        assert_eq!(opt_end, Some(end));
1479
1480        // Test partial range with undefined values
1481        let partial_undefined = PartialCuTimeRange::default();
1482        assert!(partial_undefined.start.is_none());
1483        assert!(partial_undefined.end.is_none());
1484    }
1485
1486    #[test]
1487    fn test_tov_conversions() {
1488        // Test different Time of Validity (Tov) variants
1489        let time = CuTime::from(100u64);
1490
1491        // Test conversion from CuTime
1492        let tov_time: Tov = time.into();
1493        assert!(matches!(tov_time, Tov::Time(_)));
1494
1495        if let Tov::Time(t) = tov_time {
1496            assert_eq!(t, time);
1497        }
1498
1499        // Test conversion from Option<CuTime>
1500        let some_time = Some(time);
1501        let tov_some: Tov = some_time.into();
1502        assert!(matches!(tov_some, Tov::Time(_)));
1503
1504        let none_time: Option<CuDuration> = None;
1505        let tov_none: Tov = none_time.into();
1506        assert!(matches!(tov_none, Tov::None));
1507
1508        // Test range
1509        let start = CuTime::from(100u64);
1510        let end = CuTime::from(200u64);
1511        let range = CuTimeRange { start, end };
1512        let tov_range = Tov::Range(range);
1513
1514        assert!(matches!(tov_range, Tov::Range(_)));
1515    }
1516
1517    #[cfg(feature = "std")]
1518    #[test]
1519    fn test_cuduration_display() {
1520        // Test the display implementation for different magnitudes
1521        let nano = CuDuration(42);
1522        assert_eq!(nano.to_string(), "42 ns");
1523
1524        let micro = CuDuration(42_000);
1525        assert_eq!(micro.to_string(), "42.000 µs");
1526
1527        let milli = CuDuration(42_000_000);
1528        assert_eq!(milli.to_string(), "42.000 ms");
1529
1530        let sec = CuDuration(1_500_000_000);
1531        assert_eq!(sec.to_string(), "1.500 s");
1532
1533        let min = CuDuration(90_000_000_000);
1534        assert_eq!(min.to_string(), "1.500 m");
1535
1536        let hour = CuDuration(3_600_000_000_000);
1537        assert_eq!(hour.to_string(), "1.000 h");
1538
1539        let day = CuDuration(86_400_000_000_000);
1540        assert_eq!(day.to_string(), "1.000 d");
1541    }
1542
1543    #[cfg(feature = "std")]
1544    #[test]
1545    fn test_robot_clock_precision() {
1546        // Test that RobotClock::now() and RobotClock::recent() return different values
1547        // and that recent() is always <= now()
1548        let clock = RobotClock::new();
1549
1550        // We can't guarantee the exact values, but we can check relationships
1551        let recent = clock.recent();
1552        let now = clock.now();
1553
1554        // recent() should be less than or equal to now()
1555        assert!(recent <= now);
1556
1557        // Test precision of from_ref_time
1558        let ref_time_ns = 1_000_000_000; // 1 second
1559        let clock = RobotClock::from_ref_time(ref_time_ns);
1560
1561        // Clock should start at approximately ref_time_ns
1562        let now = clock.now();
1563        let now_ns: u64 = now.into();
1564
1565        // Allow reasonable tolerance for clock initialization time
1566        let tolerance_ns = 50_000_000; // 50ms tolerance
1567        assert!(now_ns >= ref_time_ns);
1568        assert!(now_ns < ref_time_ns + tolerance_ns);
1569    }
1570
1571    #[test]
1572    fn test_mock_clock_advanced_operations() {
1573        // Test more complex operations with the mock clock
1574        let (clock, mock) = RobotClock::mock();
1575
1576        // Test initial state
1577        assert_eq!(clock.now(), CuTime::from(0));
1578
1579        // Test increment
1580        mock.increment(CuDuration::from_secs(10));
1581        assert_eq!(
1582            clock.now(),
1583            CuTime::from_nanos(CuDuration::from_secs(10).as_nanos())
1584        );
1585
1586        // Test decrement (unusual but supported)
1587        mock.decrement(CuDuration::from_secs(5));
1588        assert_eq!(
1589            clock.now(),
1590            CuTime::from_nanos(CuDuration::from_secs(5).as_nanos())
1591        );
1592
1593        // Test setting absolute value
1594        mock.set_value(30_000_000_000); // 30 seconds in ns
1595        assert_eq!(
1596            clock.now(),
1597            CuTime::from_nanos(CuDuration::from_secs(30).as_nanos())
1598        );
1599
1600        // Test that getting the time from the mock directly works
1601        assert_eq!(
1602            mock.now(),
1603            CuTime::from_nanos(CuDuration::from_secs(30).as_nanos())
1604        );
1605        assert_eq!(mock.value(), 30_000_000_000);
1606    }
1607
1608    #[test]
1609    fn test_cuduration_min_max() {
1610        // Test MIN and MAX constants
1611        assert_eq!(CuDuration::MIN, CuDuration(0));
1612
1613        // Test min/max methods
1614        let a = CuDuration(100);
1615        let b = CuDuration(200);
1616
1617        assert_eq!(a.min(b), a);
1618        assert_eq!(a.max(b), b);
1619        assert_eq!(b.min(a), a);
1620        assert_eq!(b.max(a), b);
1621
1622        // Edge cases
1623        assert_eq!(a.min(a), a);
1624        assert_eq!(a.max(a), a);
1625
1626        // Test with MIN/MAX constants
1627        assert_eq!(a.min(CuDuration::MIN), CuDuration::MIN);
1628        assert_eq!(a.max(CuDuration::MAX), CuDuration::MAX);
1629    }
1630
1631    #[test]
1632    fn test_clock_provider_trait() {
1633        // Test implementing the ClockProvider trait
1634        struct TestClockProvider {
1635            clock: RobotClock,
1636        }
1637
1638        impl ClockProvider for TestClockProvider {
1639            fn get_clock(&self) -> RobotClock {
1640                self.clock.clone()
1641            }
1642        }
1643
1644        // Create a provider with a mock clock
1645        let (clock, mock) = RobotClock::mock();
1646        let provider = TestClockProvider { clock };
1647
1648        // Test that provider returns a clock synchronized with the original
1649        let provider_clock = provider.get_clock();
1650        assert_eq!(provider_clock.now(), CuTime::from(0));
1651
1652        // Advance the mock clock and check that the provider's clock also advances
1653        mock.increment(CuDuration::from_secs(5));
1654        assert_eq!(
1655            provider_clock.now(),
1656            CuTime::from_nanos(CuDuration::from_secs(5).as_nanos())
1657        );
1658    }
1659}