cu29_clock/
lib.rs

1#[cfg(test)]
2#[macro_use]
3extern crate approx;
4use bincode::de::BorrowDecoder;
5use bincode::de::Decoder;
6use bincode::enc::Encoder;
7use bincode::error::{DecodeError, EncodeError};
8use bincode::BorrowDecode;
9use bincode::{Decode, Encode};
10use core::ops::{Add, Sub};
11pub use quanta::Instant;
12use quanta::{Clock, Mock};
13use serde::{Deserialize, Serialize};
14use std::convert::Into;
15use std::fmt::{Display, Formatter};
16use std::ops::{AddAssign, Div, Mul, SubAssign};
17use std::sync::Arc;
18use std::time::Duration;
19
20/// For Robot times, the underlying type is a u64 representing nanoseconds.
21/// It is always positive to simplify the reasoning on the user side.
22#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, Default)]
23pub struct CuDuration(pub u64);
24
25impl CuDuration {
26    // Lowest value a CuDuration can have.
27    pub const MIN: CuDuration = CuDuration(0u64);
28    // Highest value a CuDuration can have reserving the max value for None.
29    pub const MAX: CuDuration = CuDuration(NONE_VALUE - 1);
30
31    pub fn max(&self, p0: CuDuration) -> CuDuration {
32        if self.0 > p0.0 {
33            CuDuration(self.0)
34        } else {
35            p0
36        }
37    }
38
39    pub fn min(&self, p0: CuDuration) -> CuDuration {
40        if self.0 < p0.0 {
41            CuDuration(self.0)
42        } else {
43            p0
44        }
45    }
46
47    pub fn as_nanos(&self) -> u64 {
48        self.0
49    }
50}
51
52/// bridge the API with standard Durations.
53impl From<Duration> for CuDuration {
54    fn from(duration: Duration) -> Self {
55        CuDuration(duration.as_nanos() as u64)
56    }
57}
58
59impl From<CuDuration> for Duration {
60    fn from(val: CuDuration) -> Self {
61        Duration::from_nanos(val.0)
62    }
63}
64
65impl From<u64> for CuDuration {
66    fn from(duration: u64) -> Self {
67        CuDuration(duration)
68    }
69}
70
71impl From<CuDuration> for u64 {
72    fn from(val: CuDuration) -> Self {
73        val.0
74    }
75}
76
77impl Sub for CuDuration {
78    type Output = Self;
79
80    fn sub(self, rhs: Self) -> Self::Output {
81        CuDuration(self.0 - rhs.0)
82    }
83}
84
85impl Add for CuDuration {
86    type Output = Self;
87
88    fn add(self, rhs: Self) -> Self::Output {
89        CuDuration(self.0 + rhs.0)
90    }
91}
92
93impl AddAssign for CuDuration {
94    fn add_assign(&mut self, rhs: Self) {
95        self.0 += rhs.0
96    }
97}
98
99impl SubAssign for CuDuration {
100    fn sub_assign(&mut self, rhs: Self) {
101        self.0 -= rhs.0
102    }
103}
104
105// a way to divide a duration by a scalar.
106// useful to compute averages for example.
107impl<T> Div<T> for CuDuration
108where
109    T: Into<u64>,
110{
111    type Output = Self;
112    fn div(self, rhs: T) -> Self {
113        CuDuration(self.0 / rhs.into())
114    }
115}
116//
117// a way to multiply a duration by a scalar.
118// useful to compute offsets for example.
119// CuDuration * scalar
120impl<T> Mul<T> for CuDuration
121where
122    T: Into<u64>,
123{
124    type Output = CuDuration;
125
126    fn mul(self, rhs: T) -> CuDuration {
127        CuDuration(self.0 * rhs.into())
128    }
129}
130
131// u64 * CuDuration
132impl Mul<CuDuration> for u64 {
133    type Output = CuDuration;
134
135    fn mul(self, rhs: CuDuration) -> CuDuration {
136        CuDuration(self * rhs.0)
137    }
138}
139
140// u32 * CuDuration
141impl Mul<CuDuration> for u32 {
142    type Output = CuDuration;
143
144    fn mul(self, rhs: CuDuration) -> CuDuration {
145        CuDuration(self as u64 * rhs.0)
146    }
147}
148
149// i32 * CuDuration
150impl Mul<CuDuration> for i32 {
151    type Output = CuDuration;
152
153    fn mul(self, rhs: CuDuration) -> CuDuration {
154        CuDuration(self as u64 * rhs.0)
155    }
156}
157
158impl Encode for CuDuration {
159    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
160        self.0.encode(encoder)
161    }
162}
163
164impl Decode for CuDuration {
165    fn decode<D: Decoder>(decoder: &mut D) -> Result<Self, DecodeError> {
166        Ok(CuDuration(u64::decode(decoder)?))
167    }
168}
169
170impl<'de> BorrowDecode<'de> for CuDuration {
171    fn borrow_decode<D: BorrowDecoder<'de>>(decoder: &mut D) -> Result<Self, DecodeError> {
172        Ok(CuDuration(u64::decode(decoder)?))
173    }
174}
175
176impl Display for CuDuration {
177    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
178        let nanos = self.0;
179        if nanos >= 86_400_000_000_000 {
180            write!(f, "{:.3} d", nanos as f64 / 86_400_000_000_000.0)
181        } else if nanos >= 3_600_000_000_000 {
182            write!(f, "{:.3} h", nanos as f64 / 3_600_000_000_000.0)
183        } else if nanos >= 60_000_000_000 {
184            write!(f, "{:.3} m", nanos as f64 / 60_000_000_000.0)
185        } else if nanos >= 1_000_000_000 {
186            write!(f, "{:.3} s", nanos as f64 / 1_000_000_000.0)
187        } else if nanos >= 1_000_000 {
188            write!(f, "{:.3} ms", nanos as f64 / 1_000_000.0)
189        } else if nanos >= 1_000 {
190            write!(f, "{:.3} µs", nanos as f64 / 1_000.0)
191        } else {
192            write!(f, "{nanos} ns")
193        }
194    }
195}
196
197/// A robot time is just a duration from a fixed point in time.
198pub type CuTime = CuDuration;
199
200/// Homebrewed `Option<CuDuration>` to avoid using 128bits just to represent an Option.
201#[derive(Copy, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
202pub struct OptionCuTime(CuTime);
203
204const NONE_VALUE: u64 = 0xFFFFFFFFFFFFFFFF;
205
206impl OptionCuTime {
207    #[inline]
208    pub fn is_none(&self) -> bool {
209        self.0 .0 == NONE_VALUE
210    }
211
212    #[inline]
213    pub fn none() -> Self {
214        OptionCuTime(CuDuration(NONE_VALUE))
215    }
216
217    #[inline]
218    pub fn unwrap(self) -> CuTime {
219        if self.is_none() {
220            panic!("called `OptionCuTime::unwrap()` on a `None` value");
221        }
222        self.0
223    }
224}
225
226impl Display for OptionCuTime {
227    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
228        if self.is_none() {
229            write!(f, "None")
230        } else {
231            write!(f, "{}", self.0)
232        }
233    }
234}
235
236impl Default for OptionCuTime {
237    fn default() -> Self {
238        Self::none()
239    }
240}
241
242impl From<Option<CuTime>> for OptionCuTime {
243    #[inline]
244    fn from(duration: Option<CuTime>) -> Self {
245        match duration {
246            Some(duration) => OptionCuTime(duration),
247            None => OptionCuTime(CuDuration(NONE_VALUE)),
248        }
249    }
250}
251
252impl From<OptionCuTime> for Option<CuTime> {
253    #[inline]
254    fn from(val: OptionCuTime) -> Self {
255        if val.0 .0 == NONE_VALUE {
256            None
257        } else {
258            Some(val.0)
259        }
260    }
261}
262
263impl From<CuTime> for OptionCuTime {
264    #[inline]
265    fn from(val: CuTime) -> Self {
266        Some(val).into()
267    }
268}
269
270/// Represents a time range.
271#[derive(Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize, PartialEq)]
272pub struct CuTimeRange {
273    pub start: CuTime,
274    pub end: CuTime,
275}
276
277/// Builds a time range from a slice of CuTime.
278/// This is an O(n) operation.
279impl From<&[CuTime]> for CuTimeRange {
280    fn from(slice: &[CuTime]) -> Self {
281        CuTimeRange {
282            start: *slice.iter().min().expect("Empty slice"),
283            end: *slice.iter().max().expect("Empty slice"),
284        }
285    }
286}
287
288/// Represents a time range with possible undefined start or end or both.
289#[derive(Copy, Clone, Debug, Encode, Decode, Serialize, Deserialize)]
290pub struct PartialCuTimeRange {
291    pub start: OptionCuTime,
292    pub end: OptionCuTime,
293}
294
295impl Default for PartialCuTimeRange {
296    fn default() -> Self {
297        PartialCuTimeRange {
298            start: OptionCuTime::none(),
299            end: OptionCuTime::none(),
300        }
301    }
302}
303
304/// The time of validity of a message can be more than one time but can be a time range of Tovs.
305/// For example a sub scan for a lidar, a set of images etc... can have a range of validity.
306#[derive(Default, Clone, Debug, PartialEq, Encode, Decode, Serialize, Deserialize)]
307pub enum Tov {
308    #[default]
309    None,
310    Time(CuTime),
311    Range(CuTimeRange),
312}
313
314impl From<Option<CuDuration>> for Tov {
315    fn from(duration: Option<CuDuration>) -> Self {
316        match duration {
317            Some(duration) => Tov::Time(duration),
318            None => Tov::None,
319        }
320    }
321}
322
323impl From<CuDuration> for Tov {
324    fn from(duration: CuDuration) -> Self {
325        Tov::Time(duration)
326    }
327}
328
329/// A running Robot clock.
330/// The clock is a monotonic clock that starts at an arbitrary reference time.
331/// It is clone resilient, ie a clone will be the same clock, even when mocked.
332#[derive(Clone, Debug)]
333pub struct RobotClock {
334    inner: Clock,      // This is a wrapper on quanta::Clock today.
335    ref_time: Instant, // The reference instant on which this clock is based.
336}
337
338/// A mock clock that can be controlled by the user.
339#[derive(Debug, Clone)]
340pub struct RobotClockMock(Arc<Mock>); // wraps the Mock from quanta today.
341
342impl RobotClockMock {
343    pub fn increment(&self, amount: Duration) {
344        self.0.increment(amount);
345    }
346
347    /// Decrements the time by the given amount.
348    /// Be careful this brakes the monotonicity of the clock.
349    pub fn decrement(&self, amount: Duration) {
350        self.0.decrement(amount);
351    }
352
353    /// Gets the current value of time.
354    pub fn value(&self) -> u64 {
355        self.0.value()
356    }
357
358    /// A convenient way to get the current time from the mocking side.
359    pub fn now(&self) -> CuTime {
360        self.0.value().into()
361    }
362
363    /// Sets the absolute value of the time.
364    pub fn set_value(&self, value: u64) {
365        let v = self.0.value();
366        // had to work around the quata API here.
367        if v < value {
368            self.increment(Duration::from_nanos(value) - Duration::from_nanos(v));
369        } else {
370            self.decrement(Duration::from_nanos(v) - Duration::from_nanos(value));
371        }
372    }
373}
374
375impl RobotClock {
376    /// Creates a RobotClock using now as its reference time.
377    /// It will start a 0ns incrementing monotonically.
378    pub fn new() -> Self {
379        let clock = Clock::new();
380        let ref_time = clock.now();
381        RobotClock {
382            inner: clock,
383            ref_time,
384        }
385    }
386
387    /// Builds a monotonic clock starting at the given reference time.
388    pub fn from_ref_time(ref_time_ns: u64) -> Self {
389        let clock = Clock::new();
390        let ref_time = clock.now() - Duration::from_nanos(ref_time_ns);
391        RobotClock {
392            inner: Clock::new(),
393            ref_time,
394        }
395    }
396
397    /// Build a fake clock with a reference time of 0.
398    /// The RobotMock interface enables you to control all the clones of the clock given.
399    pub fn mock() -> (Self, RobotClockMock) {
400        let (clock, mock) = Clock::mock();
401        let ref_time = clock.now();
402        (
403            RobotClock {
404                inner: clock,
405                ref_time,
406            },
407            RobotClockMock(mock),
408        )
409    }
410
411    // Now returns the time that passed since the reference time, usually the start time.
412    // It is a monotonically increasing value.
413    #[inline]
414    pub fn now(&self) -> CuTime {
415        // TODO: this could be further optimized to avoid this constant conversion from 2 fields to one under the hood.
416        // Let's say this is the default implementation.
417        (self.inner.now() - self.ref_time).into()
418    }
419
420    // A less precise but quicker time
421    #[inline]
422    pub fn recent(&self) -> CuTime {
423        (self.inner.recent() - self.ref_time).into()
424    }
425}
426
427impl Default for RobotClock {
428    fn default() -> Self {
429        Self::new()
430    }
431}
432
433/// A trait to provide a clock to the runtime.
434pub trait ClockProvider {
435    fn get_clock(&self) -> RobotClock;
436}
437
438#[cfg(test)]
439mod tests {
440    use super::*;
441
442    #[test]
443    fn test_mock() {
444        let (clock, mock) = RobotClock::mock();
445        assert_eq!(clock.now(), Duration::from_secs(0).into());
446        mock.increment(Duration::from_secs(1));
447        assert_eq!(clock.now(), Duration::from_secs(1).into());
448    }
449
450    #[test]
451    fn test_mock_clone() {
452        let (clock, mock) = RobotClock::mock();
453        assert_eq!(clock.now(), Duration::from_secs(0).into());
454        let clock_clone = clock.clone();
455        mock.increment(Duration::from_secs(1));
456        assert_eq!(clock_clone.now(), Duration::from_secs(1).into());
457    }
458
459    #[test]
460    fn test_from_ref_time() {
461        let tolerance_ms = 10;
462        let clock = RobotClock::from_ref_time(1_000_000_000);
463        assert_relative_eq!(
464            <CuDuration as Into<Duration>>::into(clock.now()).as_millis() as f64,
465            Duration::from_secs(1).as_millis() as f64,
466            epsilon = tolerance_ms as f64
467        );
468    }
469
470    #[test]
471    fn longest_duration() {
472        let maxcu = CuDuration(u64::MAX);
473        let maxd: Duration = maxcu.into();
474        assert_eq!(maxd.as_nanos(), u64::MAX as u128);
475        let s = maxd.as_secs();
476        let y = s / 60 / 60 / 24 / 365;
477        assert!(y >= 584); // 584 years of robot uptime, we should be good.
478    }
479
480    #[test]
481    fn test_some_time_arithmetics() {
482        let a: CuDuration = 10.into();
483        let b: CuDuration = 20.into();
484        let c = a + b;
485        assert_eq!(c.0, 30);
486        let d = b - a;
487        assert_eq!(d.0, 10);
488    }
489
490    #[test]
491    fn test_build_range_from_slice() {
492        let range = CuTimeRange::from(&[20.into(), 10.into(), 30.into()][..]);
493        assert_eq!(range.start, 10.into());
494        assert_eq!(range.end, 30.into());
495    }
496}