1#![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
28use 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#[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 pub const fn from_nanos(nanos: u64) -> Self {
62 Self(nanos)
63 }
64
65 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#[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 pub const MIN: CuDuration = CuDuration(0u64);
122 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
174pub 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#[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
259impl<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
272impl<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
287impl 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
297impl 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
307impl 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#[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#[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#[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
706impl 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#[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#[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#[derive(Clone, Debug)]
788struct InternalClock {
789 mock_state: Option<Arc<AtomicU64>>,
790 frequency: (u64, u64),
792}
793
794#[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 self.now()
868 }
869}
870
871#[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#[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 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 pub fn value(&self) -> u64 {
906 let Self(mock_state) = self;
907 mock_state.load(Ordering::Relaxed)
908 }
909
910 pub fn now(&self) -> CuTime {
915 let Self(mock_state) = self;
916 CuTime(mock_state.load(Ordering::Relaxed))
917 }
918
919 pub fn raw_now(&self) -> CuInstant {
921 CuInstant(self.0.load(Ordering::Relaxed))
922 }
923
924 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 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 #[cfg(feature = "std")]
975 pub fn new() -> Self {
976 Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), 0)
977 }
978
979 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 #[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 #[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 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 #[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 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 pub fn mock() -> (Self, RobotClockMock) {
1049 let (inner, state) = InternalClock::mock();
1050 (Self::from_inner(inner, 0), RobotClockMock(state))
1051 }
1052
1053 #[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 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 #[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 pub fn raw_now(&self) -> CuInstant {
1126 CuInstant(self.inner.now())
1127 }
1128
1129 #[inline(always)]
1131 pub fn busy_wait_for(&self, duration: CuDuration) {
1132 self.busy_wait_until(self.raw_now() + duration);
1133 }
1134
1135 #[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 #[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#[cfg(feature = "std")]
1172impl Default for RobotClock {
1173 fn default() -> Self {
1174 Self::new()
1175 }
1176}
1177
1178pub 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)); }
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); 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); }
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 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 let times = [
1451 CuTime::from(150u64),
1452 CuTime::from(120u64),
1453 CuTime::from(180u64),
1454 ];
1455 let range_from_slice = CuTimeRange::from(×[..]);
1456
1457 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 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 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 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 let time = CuTime::from(100u64);
1490
1491 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 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 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 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 let clock = RobotClock::new();
1549
1550 let recent = clock.recent();
1552 let now = clock.now();
1553
1554 assert!(recent <= now);
1556
1557 let ref_time_ns = 1_000_000_000; let clock = RobotClock::from_ref_time(ref_time_ns);
1560
1561 let now = clock.now();
1563 let now_ns: u64 = now.into();
1564
1565 let tolerance_ns = 50_000_000; 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 let (clock, mock) = RobotClock::mock();
1575
1576 assert_eq!(clock.now(), CuTime::from(0));
1578
1579 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 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 mock.set_value(30_000_000_000); assert_eq!(
1596 clock.now(),
1597 CuTime::from_nanos(CuDuration::from_secs(30).as_nanos())
1598 );
1599
1600 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 assert_eq!(CuDuration::MIN, CuDuration(0));
1612
1613 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 assert_eq!(a.min(a), a);
1624 assert_eq!(a.max(a), a);
1625
1626 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 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 let (clock, mock) = RobotClock::mock();
1646 let provider = TestClockProvider { clock };
1647
1648 let provider_clock = provider.get_clock();
1650 assert_eq!(provider_clock.now(), CuTime::from(0));
1651
1652 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}