Skip to main content

braid_types/
timestamp.rs

1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use std::fmt::{Debug, Formatter};
5
6use crate::*;
7
8use chrono::Utc;
9
10/// Trait for timestamp sources.
11pub trait Source {}
12
13/// Triggerbox timestamp source.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct Triggerbox; // Actually, this is not neccesarily from triggerbox. TODO: should rename to "CleverComputation" or something.
16impl Source for Triggerbox {}
17
18/// Host clock timestamp source.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct HostClock;
21impl Source for HostClock {}
22
23/// A type that represents a timestamp but is serialized to an f64.
24// TODO: rename from 'Local' because actually the f64 stamp is UTC.
25#[derive(Clone, PartialEq, Eq)]
26pub struct FlydraFloatTimestampLocal<S> {
27    value_f64: NotNan<f64>,
28    source: std::marker::PhantomData<S>,
29}
30
31impl<S> Debug for FlydraFloatTimestampLocal<S> {
32    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
33        let dt: chrono::DateTime<Utc> = self.into();
34        write!(f, "FlydraFloatTimestampLocal {{ {dt:?} }}")
35    }
36}
37
38impl<S, TZ: chrono::TimeZone> From<&chrono::DateTime<TZ>> for FlydraFloatTimestampLocal<S> {
39    fn from(orig: &chrono::DateTime<TZ>) -> Self {
40        FlydraFloatTimestampLocal::from_dt(orig)
41    }
42}
43
44impl<S, TZ: chrono::TimeZone> From<chrono::DateTime<TZ>> for FlydraFloatTimestampLocal<S> {
45    fn from(val: chrono::DateTime<TZ>) -> FlydraFloatTimestampLocal<S> {
46        FlydraFloatTimestampLocal::from_dt(&val)
47    }
48}
49
50impl TryFrom<PtpStamp> for FlydraFloatTimestampLocal<Triggerbox> {
51    type Error = &'static str;
52    fn try_from(
53        val: PtpStamp,
54    ) -> std::result::Result<FlydraFloatTimestampLocal<Triggerbox>, &'static str> {
55        let dt: chrono::DateTime<chrono::Utc> = val.try_into()?;
56        Ok(FlydraFloatTimestampLocal::from_dt(&dt))
57    }
58}
59
60impl<'a, S> From<&'a FlydraFloatTimestampLocal<S>> for chrono::DateTime<Utc> {
61    fn from(orig: &'a FlydraFloatTimestampLocal<S>) -> chrono::DateTime<Utc> {
62        strand_datetime_conversion::f64_to_datetime(orig.value_f64.into_inner())
63    }
64}
65
66impl<S> From<FlydraFloatTimestampLocal<S>> for chrono::DateTime<Utc> {
67    fn from(orig: FlydraFloatTimestampLocal<S>) -> chrono::DateTime<Utc> {
68        From::from(&orig)
69    }
70}
71
72assert_impl_all!(FlydraFloatTimestampLocal<Triggerbox>: PartialEq);
73
74impl<S> FlydraFloatTimestampLocal<S> {
75    /// Create a timestamp from a chrono DateTime.
76    pub fn from_dt<TZ: chrono::TimeZone>(dt: &chrono::DateTime<TZ>) -> Self {
77        let value_f64 = strand_datetime_conversion::datetime_to_f64(dt);
78        let value_f64 = value_f64.try_into().unwrap();
79        let source = std::marker::PhantomData;
80        Self { value_f64, source }
81    }
82
83    /// Create a timestamp from an f64 value.
84    pub fn from_f64(value_f64: f64) -> Self {
85        assert!(
86            !value_f64.is_nan(),
87            "cannot convert NaN to FlydraFloatTimestampLocal"
88        );
89        Self::from_notnan_f64(value_f64.try_into().unwrap())
90    }
91
92    /// Create a timestamp from a NotNan f64 value.
93    pub fn from_notnan_f64(value_f64: NotNan<f64>) -> Self {
94        let source = std::marker::PhantomData;
95        Self { value_f64, source }
96    }
97
98    #[inline(always)]
99    /// Get the timestamp as an f64 value.
100    pub fn as_f64(&self) -> f64 {
101        self.value_f64.into()
102    }
103}
104
105/// Compute the trigger time for a particular frame.
106///
107/// Requires both a clock model (general for all cameras) and a frame offset
108/// (which maps the particular frame numbers for a given camera into a
109/// synchronized frame number).
110#[inline]
111pub fn triggerbox_time(
112    clock_model: Option<&ClockModel>,
113    frame_offset: Option<u64>,
114    frame: usize,
115) -> Option<FlydraFloatTimestampLocal<Triggerbox>> {
116    let frame: u64 = frame.try_into().unwrap();
117    if let Some(frame_offset) = frame_offset
118        && let Some(cm) = clock_model
119    {
120        let ts: f64 = ((frame - frame_offset) as f64) * cm.gain + cm.offset;
121        let ts = FlydraFloatTimestampLocal::<Triggerbox>::from_f64(ts);
122        return Some(ts);
123    }
124    None
125}
126
127#[test]
128#[should_panic]
129fn test_nan_handling() {
130    let _ts = FlydraFloatTimestampLocal::<Triggerbox>::from_f64(f64::NAN);
131}
132
133/// Ensure that conversion with particular floating point representation remains
134/// fixed. This is important for backwards compatibility of saved data.
135#[test]
136fn ensure_conversion() {
137    use chrono::{DateTime, Utc};
138    let t1 = DateTime::<Utc>::from_timestamp(60, 123_456_789).unwrap();
139    let t2 = FlydraFloatTimestampLocal::<HostClock>::from(t1);
140    let t3 = t2.value_f64.into_inner();
141    assert!((t3 - 60.123456789).abs() < 1e-10);
142    let t4: DateTime<Utc> = (&t2).into();
143    assert_eq!(t1, t4);
144}