#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
#[cfg(test)]
extern crate approx;
mod calibration;
mod origin;
mod raw_counter;
pub mod sync;
pub use origin::{ClockEpoch, ClockOrigin, ClockSource};
pub use raw_counter::*;
#[cfg(feature = "reflect")]
use bevy_reflect::Reflect;
use bincode::BorrowDecode;
use bincode::de::BorrowDecoder;
use bincode::de::Decoder;
use bincode::enc::Encoder;
use bincode::error::{DecodeError, EncodeError};
use bincode::{Decode, Encode};
use core::ops::{Add, Sub};
use serde::{Deserialize, Serialize};
use portable_atomic::{AtomicU64, Ordering};
use alloc::string::ToString;
use alloc::sync::Arc;
use alloc::vec::Vec;
use core::fmt::{Display, Formatter};
use core::ops::{AddAssign, Div, Mul, SubAssign};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[repr(transparent)]
pub struct CuInstant(u64);
impl CuInstant {
pub const fn from_nanos(nanos: u64) -> Self {
Self(nanos)
}
pub const fn as_nanos(&self) -> u64 {
self.0
}
}
impl Sub for CuInstant {
type Output = CuDuration;
fn sub(self, other: Self) -> Self::Output {
CuDuration(self.0.saturating_sub(other.0))
}
}
impl Sub<CuDuration> for CuInstant {
type Output = Self;
fn sub(self, duration: CuDuration) -> Self::Output {
Self(self.0.saturating_sub(duration.as_nanos()))
}
}
impl Add<CuDuration> for CuInstant {
type Output = Self;
fn add(self, duration: CuDuration) -> Self::Output {
Self(self.0.saturating_add(duration.as_nanos()))
}
}
impl Encode for CuInstant {
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
self.0.encode(encoder)
}
}
impl<Context> Decode<Context> for CuInstant {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(Self(u64::decode(decoder)?))
}
}
impl<'de, Context> BorrowDecode<'de, Context> for CuInstant {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(Self(u64::decode(decoder)?))
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct CuDuration(pub u64);
impl CuDuration {
pub const MIN: CuDuration = CuDuration(0u64);
pub const MAX: CuDuration = CuDuration(NONE_VALUE - 1);
pub fn max(self, other: CuDuration) -> CuDuration {
let Self(lhs) = self;
let Self(rhs) = other;
CuDuration(lhs.max(rhs))
}
pub fn min(self, other: CuDuration) -> CuDuration {
let Self(lhs) = self;
let Self(rhs) = other;
CuDuration(lhs.min(rhs))
}
pub fn as_nanos(&self) -> u64 {
let Self(nanos) = self;
*nanos
}
pub fn as_micros(&self) -> u64 {
let Self(nanos) = self;
nanos / 1_000
}
pub fn as_millis(&self) -> u64 {
let Self(nanos) = self;
nanos / 1_000_000
}
pub fn as_secs(&self) -> u64 {
let Self(nanos) = self;
nanos / 1_000_000_000
}
pub fn from_nanos(nanos: u64) -> Self {
CuDuration(nanos)
}
pub fn from_micros(micros: u64) -> Self {
CuDuration(micros * 1_000)
}
pub fn from_millis(millis: u64) -> Self {
CuDuration(millis * 1_000_000)
}
pub fn from_secs(secs: u64) -> Self {
CuDuration(secs * 1_000_000_000)
}
}
pub trait SaturatingSub {
fn saturating_sub(self, other: Self) -> Self;
}
impl SaturatingSub for CuDuration {
fn saturating_sub(self, other: Self) -> Self {
let Self(lhs) = self;
let Self(rhs) = other;
CuDuration(lhs.saturating_sub(rhs))
}
}
#[cfg(feature = "std")]
impl From<std::time::Duration> for CuDuration {
fn from(duration: std::time::Duration) -> Self {
CuDuration(duration.as_nanos() as u64)
}
}
#[cfg(not(feature = "std"))]
impl From<core::time::Duration> for CuDuration {
fn from(duration: core::time::Duration) -> Self {
CuDuration(duration.as_nanos() as u64)
}
}
#[cfg(feature = "std")]
impl From<CuDuration> for std::time::Duration {
fn from(val: CuDuration) -> Self {
let CuDuration(nanos) = val;
std::time::Duration::from_nanos(nanos)
}
}
impl From<u64> for CuDuration {
fn from(duration: u64) -> Self {
CuDuration(duration)
}
}
impl From<CuDuration> for u64 {
fn from(val: CuDuration) -> Self {
let CuDuration(nanos) = val;
nanos
}
}
impl Sub for CuDuration {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
let CuDuration(lhs) = self;
let CuDuration(rhs) = rhs;
CuDuration(lhs - rhs)
}
}
impl Add for CuDuration {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let CuDuration(lhs) = self;
let CuDuration(rhs) = rhs;
CuDuration(lhs + rhs)
}
}
impl AddAssign for CuDuration {
fn add_assign(&mut self, rhs: Self) {
let CuDuration(lhs) = self;
let CuDuration(rhs) = rhs;
*lhs += rhs;
}
}
impl SubAssign for CuDuration {
fn sub_assign(&mut self, rhs: Self) {
let CuDuration(lhs) = self;
let CuDuration(rhs) = rhs;
*lhs -= rhs;
}
}
impl<T> Div<T> for CuDuration
where
T: Into<u64>,
{
type Output = Self;
fn div(self, rhs: T) -> Self {
let CuDuration(lhs) = self;
CuDuration(lhs / rhs.into())
}
}
impl<T> Mul<T> for CuDuration
where
T: Into<u64>,
{
type Output = CuDuration;
fn mul(self, rhs: T) -> CuDuration {
let CuDuration(lhs) = self;
CuDuration(lhs * rhs.into())
}
}
impl Mul<CuDuration> for u64 {
type Output = CuDuration;
fn mul(self, rhs: CuDuration) -> CuDuration {
let CuDuration(nanos) = rhs;
CuDuration(self * nanos)
}
}
impl Mul<CuDuration> for u32 {
type Output = CuDuration;
fn mul(self, rhs: CuDuration) -> CuDuration {
let CuDuration(nanos) = rhs;
CuDuration(self as u64 * nanos)
}
}
impl Mul<CuDuration> for i32 {
type Output = CuDuration;
fn mul(self, rhs: CuDuration) -> CuDuration {
let CuDuration(nanos) = rhs;
CuDuration(self as u64 * nanos)
}
}
impl Encode for CuDuration {
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
let CuDuration(nanos) = self;
nanos.encode(encoder)
}
}
impl<Context> Decode<Context> for CuDuration {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(CuDuration(u64::decode(decoder)?))
}
}
impl<'de, Context> BorrowDecode<'de, Context> for CuDuration {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(CuDuration(u64::decode(decoder)?))
}
}
impl Display for CuDuration {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let Self(nanos) = *self;
if nanos >= 86_400_000_000_000 {
write!(f, "{:.3} d", nanos as f64 / 86_400_000_000_000.0)
} else if nanos >= 3_600_000_000_000 {
write!(f, "{:.3} h", nanos as f64 / 3_600_000_000_000.0)
} else if nanos >= 60_000_000_000 {
write!(f, "{:.3} m", nanos as f64 / 60_000_000_000.0)
} else if nanos >= 1_000_000_000 {
write!(f, "{:.3} s", nanos as f64 / 1_000_000_000.0)
} else if nanos >= 1_000_000 {
write!(f, "{:.3} ms", nanos as f64 / 1_000_000.0)
} else if nanos >= 1_000 {
write!(f, "{:.3} µs", nanos as f64 / 1_000.0)
} else {
write!(f, "{nanos} ns")
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[repr(transparent)]
pub struct CuTime(pub u64);
impl CuTime {
pub const MIN: CuTime = CuTime(0u64);
pub const MAX: CuTime = CuTime(NONE_VALUE - 1);
pub fn max(self, other: CuTime) -> CuTime {
let Self(lhs) = self;
let Self(rhs) = other;
CuTime(lhs.max(rhs))
}
pub fn min(self, other: CuTime) -> CuTime {
let Self(lhs) = self;
let Self(rhs) = other;
CuTime(lhs.min(rhs))
}
pub fn as_nanos(&self) -> u64 {
let Self(nanos) = self;
*nanos
}
pub fn as_micros(&self) -> u64 {
self.as_nanos() / 1_000
}
pub fn as_millis(&self) -> u64 {
self.as_nanos() / 1_000_000
}
pub fn as_secs(&self) -> u64 {
self.as_nanos() / 1_000_000_000
}
pub fn from_nanos(nanos: u64) -> Self {
CuTime(nanos)
}
pub fn from_micros(micros: u64) -> Self {
CuTime::from(CuDuration::from_micros(micros))
}
pub fn from_millis(millis: u64) -> Self {
CuTime::from(CuDuration::from_millis(millis))
}
pub fn from_secs(secs: u64) -> Self {
CuTime::from(CuDuration::from_secs(secs))
}
}
impl SaturatingSub for CuTime {
fn saturating_sub(self, other: Self) -> Self {
let Self(lhs) = self;
let Self(rhs) = other;
CuTime(lhs.saturating_sub(rhs))
}
}
#[cfg(feature = "std")]
impl From<std::time::Duration> for CuTime {
fn from(duration: std::time::Duration) -> Self {
CuTime::from(CuDuration::from(duration))
}
}
#[cfg(not(feature = "std"))]
impl From<core::time::Duration> for CuTime {
fn from(duration: core::time::Duration) -> Self {
CuTime::from(CuDuration::from(duration))
}
}
#[cfg(feature = "std")]
impl From<CuTime> for std::time::Duration {
fn from(val: CuTime) -> Self {
std::time::Duration::from_nanos(val.as_nanos())
}
}
impl From<u64> for CuTime {
fn from(time: u64) -> Self {
CuTime(time)
}
}
impl From<CuTime> for u64 {
fn from(val: CuTime) -> Self {
let CuTime(nanos) = val;
nanos
}
}
impl From<CuDuration> for CuTime {
fn from(duration: CuDuration) -> Self {
CuTime(duration.as_nanos())
}
}
impl From<CuTime> for CuDuration {
fn from(time: CuTime) -> Self {
CuDuration(time.as_nanos())
}
}
impl Add for CuTime {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
CuTime(self.as_nanos().saturating_add(rhs.as_nanos()))
}
}
impl Add<CuDuration> for CuTime {
type Output = Self;
fn add(self, rhs: CuDuration) -> Self::Output {
CuTime(self.as_nanos().saturating_add(rhs.as_nanos()))
}
}
impl AddAssign<CuDuration> for CuTime {
fn add_assign(&mut self, rhs: CuDuration) {
*self = *self + rhs;
}
}
impl AddAssign for CuTime {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
}
}
impl Sub<CuDuration> for CuTime {
type Output = Self;
fn sub(self, rhs: CuDuration) -> Self::Output {
CuTime(self.as_nanos().saturating_sub(rhs.as_nanos()))
}
}
impl SubAssign<CuDuration> for CuTime {
fn sub_assign(&mut self, rhs: CuDuration) {
*self = *self - rhs;
}
}
impl SubAssign for CuTime {
fn sub_assign(&mut self, rhs: Self) {
*self = CuTime(self.as_nanos().saturating_sub(rhs.as_nanos()));
}
}
impl Sub for CuTime {
type Output = CuDuration;
fn sub(self, rhs: Self) -> Self::Output {
CuDuration(self.as_nanos().saturating_sub(rhs.as_nanos()))
}
}
impl<T> Div<T> for CuTime
where
T: Into<u64>,
{
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
CuTime(self.as_nanos() / rhs.into())
}
}
impl<T> Mul<T> for CuTime
where
T: Into<u64>,
{
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
CuTime(self.as_nanos() * rhs.into())
}
}
impl Mul<CuTime> for u64 {
type Output = CuTime;
fn mul(self, rhs: CuTime) -> Self::Output {
CuTime(self * rhs.as_nanos())
}
}
impl Mul<CuTime> for u32 {
type Output = CuTime;
fn mul(self, rhs: CuTime) -> Self::Output {
CuTime(self as u64 * rhs.as_nanos())
}
}
impl Mul<CuTime> for i32 {
type Output = CuTime;
fn mul(self, rhs: CuTime) -> Self::Output {
CuTime(self as u64 * rhs.as_nanos())
}
}
impl Encode for CuTime {
fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
self.as_nanos().encode(encoder)
}
}
impl<Context> Decode<Context> for CuTime {
fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(CuTime(u64::decode(decoder)?))
}
}
impl<'de, Context> BorrowDecode<'de, Context> for CuTime {
fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
Ok(CuTime(u64::decode(decoder)?))
}
}
impl Display for CuTime {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
CuDuration(self.as_nanos()).fmt(f)
}
}
#[derive(Copy, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct OptionCuTime(CuTime);
const NONE_VALUE: u64 = 0xFFFFFFFFFFFFFFFF;
impl OptionCuTime {
pub const NONE_SENTINEL_NANOS: u64 = NONE_VALUE;
#[inline]
pub fn is_none(&self) -> bool {
let Self(CuTime(nanos)) = self;
*nanos == NONE_VALUE
}
#[inline]
pub const fn none() -> Self {
OptionCuTime(CuTime(NONE_VALUE))
}
#[inline]
pub fn unwrap(self) -> CuTime {
if self.is_none() {
panic!("called `OptionCuTime::unwrap()` on a `None` value");
}
self.0
}
}
impl Display for OptionCuTime {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
if self.is_none() {
write!(f, "None")
} else {
write!(f, "{}", self.0)
}
}
}
impl Default for OptionCuTime {
fn default() -> Self {
Self::none()
}
}
impl From<Option<CuTime>> for OptionCuTime {
#[inline]
fn from(duration: Option<CuTime>) -> Self {
match duration {
Some(duration) => OptionCuTime(duration),
None => OptionCuTime(CuTime(NONE_VALUE)),
}
}
}
impl From<OptionCuTime> for Option<CuTime> {
#[inline]
fn from(val: OptionCuTime) -> Self {
let OptionCuTime(CuTime(nanos)) = val;
if nanos == NONE_VALUE {
None
} else {
Some(CuTime(nanos))
}
}
}
impl From<CuTime> for OptionCuTime {
#[inline]
fn from(val: CuTime) -> Self {
Some(val).into()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ClockDebugScalarKind {
Time,
OptionalTime,
Duration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ClockDebugScalarRegistration {
pub type_path: &'static str,
pub kind: ClockDebugScalarKind,
}
pub fn debug_scalar_registrations() -> Vec<ClockDebugScalarRegistration> {
alloc::vec![
ClockDebugScalarRegistration {
type_path: core::any::type_name::<CuDuration>(),
kind: ClockDebugScalarKind::Duration,
},
ClockDebugScalarRegistration {
type_path: core::any::type_name::<CuTime>(),
kind: ClockDebugScalarKind::Time,
},
ClockDebugScalarRegistration {
type_path: core::any::type_name::<OptionCuTime>(),
kind: ClockDebugScalarKind::OptionalTime,
},
]
}
#[derive(Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct CuTimeRange {
pub start: CuTime,
pub end: CuTime,
}
impl From<&[CuTime]> for CuTimeRange {
fn from(slice: &[CuTime]) -> Self {
CuTimeRange {
start: *slice.iter().min().expect("Empty slice"),
end: *slice.iter().max().expect("Empty slice"),
}
}
}
#[derive(Default, Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub struct PartialCuTimeRange {
pub start: OptionCuTime,
pub end: OptionCuTime,
}
impl Display for PartialCuTimeRange {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
let start = if self.start.is_none() {
"…"
} else {
&self.start.to_string()
};
let end = if self.end.is_none() {
"…"
} else {
&self.end.to_string()
};
write!(f, "[{start} – {end}]")
}
}
#[derive(Default, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize, Copy)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
pub enum Tov {
#[default]
None,
Time(CuTime),
Range(CuTimeRange),
}
impl Display for Tov {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
match self {
Tov::None => write!(f, "None"),
Tov::Time(t) => write!(f, "{t}"),
Tov::Range(r) => write!(f, "[{} – {}]", r.start, r.end),
}
}
}
impl From<Option<CuDuration>> for Tov {
fn from(duration: Option<CuDuration>) -> Self {
duration.map(CuTime::from).map_or(Tov::None, Tov::Time)
}
}
impl From<Option<CuTime>> for Tov {
fn from(time: Option<CuTime>) -> Self {
time.map_or(Tov::None, Tov::Time)
}
}
impl From<CuDuration> for Tov {
fn from(duration: CuDuration) -> Self {
Tov::Time(duration.into())
}
}
impl From<CuTime> for Tov {
fn from(time: CuTime) -> Self {
Tov::Time(time)
}
}
#[derive(Clone, Debug)]
struct InternalClock {
mock_state: Option<Arc<AtomicU64>>,
frequency: (u64, u64),
}
#[cfg(all(
feature = "std",
not(all(target_arch = "wasm32", target_os = "unknown"))
))]
#[inline(always)]
fn read_rtc_ns() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
}
#[cfg(all(feature = "std", target_arch = "wasm32", target_os = "unknown"))]
#[inline(always)]
fn read_rtc_ns() -> u64 {
read_raw_counter()
}
#[cfg(all(
feature = "std",
not(all(target_arch = "wasm32", target_os = "unknown"))
))]
#[inline(always)]
fn sleep_ns(ns: u64) {
std::thread::sleep(std::time::Duration::from_nanos(ns));
}
#[cfg(all(feature = "std", target_arch = "wasm32", target_os = "unknown"))]
#[inline(always)]
fn sleep_ns(ns: u64) {
let start = read_raw_counter();
while read_raw_counter().saturating_sub(start) < ns {
core::hint::spin_loop();
}
}
impl InternalClock {
fn new(
read_rtc_ns: impl Fn() -> u64 + Send + Sync + 'static,
sleep_ns: impl Fn(u64) + Send + Sync + 'static,
) -> Self {
initialize();
let calibration = calibration::measure(read_raw_counter, read_rtc_ns, sleep_ns)
.expect("cu29_clock calibration failed: raw counter did not advance");
InternalClock {
mock_state: None,
frequency: (calibration.counter, calibration.frequency_hz),
}
}
fn mock() -> (Self, Arc<AtomicU64>) {
let mock_state = Arc::new(AtomicU64::new(0));
let clock = InternalClock {
mock_state: Some(Arc::clone(&mock_state)),
frequency: (0, 1_000_000_000),
};
(clock, mock_state)
}
fn now(&self) -> u64 {
if let Some(ref mock_state) = self.mock_state {
return mock_state.load(Ordering::Relaxed);
}
let (anchor, hz) = self.frequency;
let ticks = read_raw_counter().saturating_sub(anchor);
(u128::from(ticks) * 1_000_000_000 / u128::from(hz)).min(u128::from(u64::MAX)) as u64
}
fn recent(&self) -> u64 {
self.now()
}
}
#[derive(Clone, Debug)]
pub struct RobotClock {
inner: InternalClock,
#[cfg(not(feature = "clock-sync"))]
raw_anchor: u64,
#[cfg(not(feature = "clock-sync"))]
time_anchor: u64,
#[cfg(not(feature = "clock-sync"))]
origin: ClockOrigin,
#[cfg(feature = "clock-sync")]
mapping: Arc<sync::SharedClock>,
}
#[derive(Debug, Clone)]
pub struct RobotClockMock(Arc<AtomicU64>);
impl RobotClockMock {
pub fn increment(&self, amount: CuDuration) {
let Self(mock_state) = self;
mock_state.fetch_add(amount.as_nanos(), Ordering::Relaxed);
}
pub fn decrement(&self, amount: CuDuration) {
let Self(mock_state) = self;
mock_state.fetch_sub(amount.as_nanos(), Ordering::Relaxed);
}
pub fn value(&self) -> u64 {
let Self(mock_state) = self;
mock_state.load(Ordering::Relaxed)
}
pub fn now(&self) -> CuTime {
let Self(mock_state) = self;
CuTime(mock_state.load(Ordering::Relaxed))
}
pub fn raw_now(&self) -> CuInstant {
CuInstant(self.0.load(Ordering::Relaxed))
}
pub fn set_value(&self, value: u64) {
let Self(mock_state) = self;
mock_state.store(value, Ordering::Relaxed);
}
}
impl RobotClock {
pub fn is_mock(&self) -> bool {
self.inner.mock_state.is_some()
}
fn from_inner(inner: InternalClock, initial: u64) -> Self {
let raw = inner.now();
let origin = ClockOrigin {
source: if inner.mock_state.is_some() {
ClockSource::Mock
} else {
ClockSource::LocalMonotonic
},
epoch: if initial == 0 {
ClockEpoch::Local
} else {
ClockEpoch::Unknown
},
};
Self::from_inner_with_origin(inner, raw, initial, origin)
}
fn from_inner_with_origin(
inner: InternalClock,
raw: u64,
initial: u64,
origin: ClockOrigin,
) -> Self {
Self {
inner,
#[cfg(not(feature = "clock-sync"))]
raw_anchor: raw,
#[cfg(not(feature = "clock-sync"))]
time_anchor: initial,
#[cfg(not(feature = "clock-sync"))]
origin,
#[cfg(feature = "clock-sync")]
mapping: Arc::new(sync::SharedClock::new(raw, initial, origin)),
}
}
#[cfg(feature = "std")]
pub fn new() -> Self {
Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), 0)
}
pub fn new_with_rtc(
read_rtc_ns: impl Fn() -> u64 + Send + Sync + 'static,
sleep_ns: impl Fn(u64) + Send + Sync + 'static,
) -> Self {
Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), 0)
}
#[cfg(feature = "clock-sync")]
pub fn new_with_frequency(hz: u64) -> Result<Self, sync::SyncError> {
if hz == 0 {
return Err(sync::SyncError::InvalidConfig);
}
let anchor = read_raw_counter();
Ok(Self::from_inner(
InternalClock {
mock_state: None,
frequency: (anchor, hz),
},
0,
))
}
#[cfg(feature = "std")]
pub fn from_ref_time(ref_time_ns: u64) -> Self {
Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), ref_time_ns)
}
pub fn from_ref_time_with_rtc(
read_rtc_ns: fn() -> u64,
sleep_ns: fn(u64),
ref_time_ns: u64,
) -> Self {
Self::from_inner(InternalClock::new(read_rtc_ns, sleep_ns), ref_time_ns)
}
#[cfg(feature = "std")]
pub fn from_ref_time_with_origin(ref_time_ns: u64, origin: ClockOrigin) -> Self {
let inner = InternalClock::new(read_rtc_ns, sleep_ns);
let raw = inner.now();
Self::from_inner_with_origin(inner, raw, ref_time_ns, origin)
}
pub fn from_ref_time_with_rtc_and_origin(
read_rtc_ns: fn() -> u64,
sleep_ns: fn(u64),
ref_time_ns: u64,
origin: ClockOrigin,
) -> Self {
let inner = InternalClock::new(read_rtc_ns, sleep_ns);
let raw = inner.now();
Self::from_inner_with_origin(inner, raw, ref_time_ns, origin)
}
pub fn mock() -> (Self, RobotClockMock) {
let (inner, state) = InternalClock::mock();
(Self::from_inner(inner, 0), RobotClockMock(state))
}
#[inline]
pub fn now(&self) -> CuTime {
#[cfg(not(feature = "clock-sync"))]
{
CuTime(
self.time_anchor
.saturating_add(self.inner.now().saturating_sub(self.raw_anchor)),
)
}
#[cfg(feature = "clock-sync")]
{
self.mapping.now(self.raw_now())
}
}
pub fn origin(&self) -> ClockOrigin {
#[cfg(feature = "clock-sync")]
{
self.mapping.origin()
}
#[cfg(not(feature = "clock-sync"))]
{
self.origin
}
}
#[inline]
pub fn recent(&self) -> CuTime {
#[cfg(not(feature = "clock-sync"))]
{
CuTime(
self.time_anchor
.saturating_add(self.inner.recent().saturating_sub(self.raw_anchor)),
)
}
#[cfg(feature = "clock-sync")]
{
self.mapping.now(CuInstant(self.inner.recent()))
}
}
pub fn raw_now(&self) -> CuInstant {
CuInstant(self.inner.now())
}
#[inline(always)]
pub fn busy_wait_for(&self, duration: CuDuration) {
self.busy_wait_until(self.raw_now() + duration);
}
#[inline(always)]
pub fn busy_wait_until(&self, time: CuInstant) {
while self.raw_now() < time {
core::hint::spin_loop();
}
}
#[inline]
pub fn sync_status(&self) -> Option<sync::SyncStatus> {
#[cfg(feature = "clock-sync")]
{
self.mapping.read().status(self.raw_now())
}
#[cfg(not(feature = "clock-sync"))]
{
None
}
}
}
#[cfg(feature = "std")]
impl Default for RobotClock {
fn default() -> Self {
Self::new()
}
}
pub trait ClockProvider {
fn get_clock(&self) -> RobotClock;
}
impl bincode::ValueDecode for CuTime {
const DECODE: &'static bincode::ValueDecodeSpec = <u64 as bincode::ValueDecode>::DECODE;
const METADATA: &'static [bincode::value_decode::ValueMetadata] =
&[bincode::value_decode::ValueMetadata::Quantity(
bincode::value_decode::QuantityMetadata::time(
bincode::value_decode::TimeStorageUnit::Nanosecond,
),
)];
}
impl bincode::ValueDecode for CuInstant {
const DECODE: &'static bincode::ValueDecodeSpec = <u64 as bincode::ValueDecode>::DECODE;
const METADATA: &'static [bincode::value_decode::ValueMetadata] =
&[bincode::value_decode::ValueMetadata::Quantity(
bincode::value_decode::QuantityMetadata::time(
bincode::value_decode::TimeStorageUnit::Nanosecond,
),
)];
}
impl bincode::ValueDecode for CuDuration {
const DECODE: &'static bincode::ValueDecodeSpec = <u64 as bincode::ValueDecode>::DECODE;
const METADATA: &'static [bincode::value_decode::ValueMetadata] =
&[bincode::value_decode::ValueMetadata::Quantity(
bincode::value_decode::QuantityMetadata::time(
bincode::value_decode::TimeStorageUnit::Nanosecond,
),
)];
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "std")]
use approx::assert_relative_eq;
#[test]
fn test_unconfigured_sync_status_is_available_without_discipline() {
let (clock, mock) = RobotClock::mock();
let clone = clock.clone();
for value in [0, 1_000_000_000, 10_000_000_000] {
mock.set_value(value);
let quality: Option<sync::SyncStatus> = clock.sync_status();
assert_eq!(quality, None);
assert_eq!(clone.sync_status(), None);
assert_eq!(clock.origin(), clone.origin());
assert_eq!(clock.origin().epoch, ClockEpoch::Local);
assert_eq!(clock.origin().source, ClockSource::Mock);
assert_eq!(clock.now(), CuTime(value));
}
}
#[cfg(feature = "std")]
#[test]
fn test_named_reference_epoch_is_shared_without_conversion() {
let origin = ClockOrigin {
source: ClockSource::Gnss,
epoch: ClockEpoch::Gps,
};
let initial = 1_400_000_000_000_000_000;
let clock = RobotClock::from_ref_time_with_origin(initial, origin);
assert_eq!(clock.origin(), origin);
assert_eq!(clock.clone().origin(), origin);
assert!(clock.now().0 >= initial);
assert!(clock.now().0 < initial + 1_000_000_000);
assert_eq!(clock.sync_status(), None);
}
#[test]
fn test_raw_instant_arithmetic_and_mock_readings() {
let (clock, mock) = RobotClock::mock();
let start = clock.raw_now();
let duration = CuDuration::from_millis(1);
mock.increment(duration);
assert_eq!(clock.raw_now(), start + duration);
assert_eq!(mock.raw_now(), clock.raw_now());
assert_eq!(clock.raw_now() - start, duration);
assert_eq!(clock.raw_now() - duration, start);
assert_eq!(start - clock.raw_now(), CuDuration(0));
assert_eq!(start - duration, start);
let last = CuInstant::from_nanos(u64::MAX);
assert_eq!(last + duration, last);
assert_eq!(
core::mem::size_of::<CuInstant>(),
core::mem::size_of::<u64>()
);
}
#[test]
fn test_raw_observation_preserves_timestamp_encoding() {
let domain = sync::ClockDomain {
id: 1,
identity: *b"testroot",
session: 2,
};
for nanos in [0, 250, 1_000_000_000, CuTime::MAX.as_nanos()] {
let instant = CuInstant::from_nanos(nanos);
let encoded = bincode::encode_to_vec(instant, bincode::config::standard()).unwrap();
assert_eq!(
encoded,
bincode::encode_to_vec(CuTime(nanos), bincode::config::standard()).unwrap()
);
let (decoded, consumed): (CuInstant, _) =
bincode::decode_from_slice(&encoded, bincode::config::standard()).unwrap();
assert_eq!(decoded, instant);
assert_eq!(consumed, encoded.len());
let observation = sync::ClockObservation {
raw_local: instant,
parent_ns: 10_000_000_000,
origin: ClockOrigin::default(),
uncertainty: CuDuration(100),
domain,
};
let reference_fields = (
CuTime(nanos),
observation.parent_ns,
observation.origin,
observation.uncertainty,
domain,
);
assert_eq!(
bincode::encode_to_vec(observation, bincode::config::standard()).unwrap(),
bincode::encode_to_vec(reference_fields, bincode::config::standard()).unwrap()
);
}
}
#[test]
fn test_cuduration_comparison_operators() {
let a = CuDuration(100);
let b = CuDuration(200);
assert!(a < b);
assert!(b > a);
assert_ne!(a, b);
assert_eq!(a, CuDuration(100));
}
#[test]
fn test_cuduration_arithmetic_operations() {
let a = CuDuration(100);
let b = CuDuration(50);
assert_eq!(a + b, CuDuration(150));
assert_eq!(a - b, CuDuration(50));
assert_eq!(a * 2u32, CuDuration(200));
assert_eq!(a / 2u32, CuDuration(50));
}
#[cfg(feature = "std")]
#[test]
fn test_robot_clock_monotonic() {
let clock = RobotClock::new();
let t1 = clock.now();
let t2 = clock.now();
assert!(t2 >= t1);
}
#[test]
fn test_robot_clock_mock() {
let (clock, mock) = RobotClock::mock();
let t1 = clock.now();
mock.increment(CuDuration::from_millis(100));
let t2 = clock.now();
assert!(t2 > t1);
assert_eq!(t2 - t1, CuDuration(100_000_000)); }
#[test]
fn test_robot_clock_clone_consistency() {
let (clock1, mock) = RobotClock::mock();
let clock2 = clock1.clone();
mock.set_value(1_000_000_000); assert_eq!(clock1.now(), clock2.now());
}
#[test]
fn test_raw_mock_clock_shares_control_with_clones() {
let (clock, mock) = RobotClock::mock();
let clone = clock.clone();
mock.set_value(1_000_000_000);
assert_eq!(clock.raw_now(), CuInstant::from_nanos(1_000_000_000));
assert_eq!(clone.raw_now(), clock.raw_now());
mock.decrement(CuDuration(10));
assert_eq!(clock.raw_now(), CuInstant::from_nanos(999_999_990));
}
#[cfg(feature = "std")]
#[test]
fn test_custom_rtc_does_not_recalibrate_existing_clones() {
let clock = RobotClock::new();
let clone = clock.clone();
let before = clock.now();
let raw_before = clock.raw_now();
let rtc = Arc::new(AtomicU64::new(0));
let custom = RobotClock::new_with_rtc(
move || rtc.fetch_add(10_000_000, Ordering::Relaxed),
sleep_ns,
);
assert!(clock.now() >= before);
assert!(clone.raw_now() >= raw_before);
assert!(clone.now().0 - before.0 < 1_000_000_000);
assert_eq!(clock.inner.frequency, clone.inner.frequency);
assert_ne!(clock.inner.frequency, custom.inner.frequency);
}
#[cfg(feature = "std")]
#[test]
fn test_busy_wait_uses_raw_timeline_with_reference_epoch() {
let clock = RobotClock::from_ref_time(5_000_000_000);
let duration = CuDuration(100_000);
let deadline = clock.raw_now() + duration;
clock.busy_wait_until(deadline);
assert!(clock.raw_now() >= deadline);
let before = clock.raw_now();
clock.busy_wait_for(duration);
assert!(clock.raw_now() - before >= duration);
}
#[cfg(feature = "std")]
#[test]
fn test_from_ref_time() {
let tolerance_ms = 10f64;
let clock = RobotClock::from_ref_time(1_000_000_000);
assert_relative_eq!(
clock.now().as_millis() as f64,
CuDuration::from_secs(1).as_millis() as f64,
epsilon = tolerance_ms
);
}
#[test]
fn longest_duration() {
let maxcu = CuDuration(u64::MAX);
assert_eq!(maxcu.as_nanos(), u64::MAX);
let s = maxcu.as_secs();
let y = s / 60 / 60 / 24 / 365;
assert!(y >= 584); }
#[test]
fn test_some_time_arithmetics() {
let a: CuDuration = 10.into();
let b: CuDuration = 20.into();
let c = a + b;
assert_eq!(c.0, 30);
let d = b - a;
assert_eq!(d.0, 10);
}
#[test]
fn test_build_range_from_slice() {
let range = CuTimeRange::from(&[20.into(), 10.into(), 30.into()][..]);
assert_eq!(range.start, 10.into());
assert_eq!(range.end, 30.into());
}
#[test]
fn test_time_range_operations() {
let start = CuTime::from(100u64);
let end = CuTime::from(200u64);
let range = CuTimeRange { start, end };
assert_eq!(range.start, start);
assert_eq!(range.end, end);
let times = [
CuTime::from(150u64),
CuTime::from(120u64),
CuTime::from(180u64),
];
let range_from_slice = CuTimeRange::from(×[..]);
assert_eq!(range_from_slice.start, CuTime::from(120u64));
assert_eq!(range_from_slice.end, CuTime::from(180u64));
}
#[test]
fn test_partial_time_range() {
let start = CuTime::from(100u64);
let end = CuTime::from(200u64);
let partial_range = PartialCuTimeRange {
start: OptionCuTime::from(start),
end: OptionCuTime::from(end),
};
let opt_start: Option<CuTime> = partial_range.start.into();
let opt_end: Option<CuTime> = partial_range.end.into();
assert_eq!(opt_start, Some(start));
assert_eq!(opt_end, Some(end));
let partial_undefined = PartialCuTimeRange::default();
assert!(partial_undefined.start.is_none());
assert!(partial_undefined.end.is_none());
}
#[test]
fn test_tov_conversions() {
let time = CuTime::from(100u64);
let tov_time: Tov = time.into();
assert!(matches!(tov_time, Tov::Time(_)));
if let Tov::Time(t) = tov_time {
assert_eq!(t, time);
}
let some_time = Some(time);
let tov_some: Tov = some_time.into();
assert!(matches!(tov_some, Tov::Time(_)));
let none_time: Option<CuDuration> = None;
let tov_none: Tov = none_time.into();
assert!(matches!(tov_none, Tov::None));
let start = CuTime::from(100u64);
let end = CuTime::from(200u64);
let range = CuTimeRange { start, end };
let tov_range = Tov::Range(range);
assert!(matches!(tov_range, Tov::Range(_)));
}
#[cfg(feature = "std")]
#[test]
fn test_cuduration_display() {
let nano = CuDuration(42);
assert_eq!(nano.to_string(), "42 ns");
let micro = CuDuration(42_000);
assert_eq!(micro.to_string(), "42.000 µs");
let milli = CuDuration(42_000_000);
assert_eq!(milli.to_string(), "42.000 ms");
let sec = CuDuration(1_500_000_000);
assert_eq!(sec.to_string(), "1.500 s");
let min = CuDuration(90_000_000_000);
assert_eq!(min.to_string(), "1.500 m");
let hour = CuDuration(3_600_000_000_000);
assert_eq!(hour.to_string(), "1.000 h");
let day = CuDuration(86_400_000_000_000);
assert_eq!(day.to_string(), "1.000 d");
}
#[cfg(feature = "std")]
#[test]
fn test_robot_clock_precision() {
let clock = RobotClock::new();
let recent = clock.recent();
let now = clock.now();
assert!(recent <= now);
let ref_time_ns = 1_000_000_000; let clock = RobotClock::from_ref_time(ref_time_ns);
let now = clock.now();
let now_ns: u64 = now.into();
let tolerance_ns = 50_000_000; assert!(now_ns >= ref_time_ns);
assert!(now_ns < ref_time_ns + tolerance_ns);
}
#[test]
fn test_mock_clock_advanced_operations() {
let (clock, mock) = RobotClock::mock();
assert_eq!(clock.now(), CuTime::from(0));
mock.increment(CuDuration::from_secs(10));
assert_eq!(
clock.now(),
CuTime::from_nanos(CuDuration::from_secs(10).as_nanos())
);
mock.decrement(CuDuration::from_secs(5));
assert_eq!(
clock.now(),
CuTime::from_nanos(CuDuration::from_secs(5).as_nanos())
);
mock.set_value(30_000_000_000); assert_eq!(
clock.now(),
CuTime::from_nanos(CuDuration::from_secs(30).as_nanos())
);
assert_eq!(
mock.now(),
CuTime::from_nanos(CuDuration::from_secs(30).as_nanos())
);
assert_eq!(mock.value(), 30_000_000_000);
}
#[test]
fn test_cuduration_min_max() {
assert_eq!(CuDuration::MIN, CuDuration(0));
let a = CuDuration(100);
let b = CuDuration(200);
assert_eq!(a.min(b), a);
assert_eq!(a.max(b), b);
assert_eq!(b.min(a), a);
assert_eq!(b.max(a), b);
assert_eq!(a.min(a), a);
assert_eq!(a.max(a), a);
assert_eq!(a.min(CuDuration::MIN), CuDuration::MIN);
assert_eq!(a.max(CuDuration::MAX), CuDuration::MAX);
}
#[test]
fn test_clock_provider_trait() {
struct TestClockProvider {
clock: RobotClock,
}
impl ClockProvider for TestClockProvider {
fn get_clock(&self) -> RobotClock {
self.clock.clone()
}
}
let (clock, mock) = RobotClock::mock();
let provider = TestClockProvider { clock };
let provider_clock = provider.get_clock();
assert_eq!(provider_clock.now(), CuTime::from(0));
mock.increment(CuDuration::from_secs(5));
assert_eq!(
provider_clock.now(),
CuTime::from_nanos(CuDuration::from_secs(5).as_nanos())
);
}
}