re_log_types/index/
time_int.rs

1use crate::{Duration, NonMinI64, TryFromIntError};
2
3/// A 64-bit number describing either nanoseconds, sequence numbers or fully static data.
4///
5/// Must be matched with a [`crate::TimeType`] to know what.
6///
7/// Used both for time points and durations.
8#[derive(Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
9#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
10pub struct TimeInt(Option<NonMinI64>);
11
12static_assertions::assert_eq_size!(TimeInt, i64);
13static_assertions::assert_eq_align!(TimeInt, i64);
14
15impl re_byte_size::SizeBytes for TimeInt {
16    #[inline]
17    fn heap_size_bytes(&self) -> u64 {
18        0
19    }
20}
21
22impl std::fmt::Debug for TimeInt {
23    #[inline]
24    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25        match self.0 {
26            Some(NonMinI64::MIN) => f
27                .debug_tuple("TimeInt::MIN")
28                .field(&NonMinI64::MIN)
29                .finish(),
30            Some(NonMinI64::MAX) => f
31                .debug_tuple("TimeInt::MAX")
32                .field(&NonMinI64::MAX)
33                .finish(),
34            Some(t) => f.write_fmt(format_args!("TimeInt({})", re_format::format_int(t.get()))),
35            None => f.debug_tuple("TimeInt::STATIC").finish(),
36        }
37    }
38}
39
40impl TimeInt {
41    /// Special value used to represent static data.
42    ///
43    /// It is illegal to create a [`TimeInt`] with that value in a temporal context.
44    ///
45    /// SDK users cannot log data at that timestamp explicitly, the only way to do so is to use
46    /// the static APIs.
47    pub const STATIC: Self = Self(None);
48
49    /// Value used to represent the minimal temporal value a [`TimeInt`] can hold.
50    ///
51    /// This is _not_ `i64::MIN`, as that is a special value reserved as a marker for static
52    /// data (see [`Self::STATIC`]).
53    pub const MIN: Self = Self(Some(NonMinI64::MIN));
54
55    /// Value used to represent the maximum temporal value a [`TimeInt`] can hold.
56    pub const MAX: Self = Self(Some(NonMinI64::MAX));
57
58    pub const ZERO: Self = Self(Some(NonMinI64::ZERO));
59
60    pub const ONE: Self = Self(Some(NonMinI64::ONE));
61
62    #[inline]
63    pub fn is_static(self) -> bool {
64        self == Self::STATIC
65    }
66
67    /// Creates a new temporal [`TimeInt`].
68    ///
69    /// If `time` is `i64::MIN`, this will return [`TimeInt::MIN`].
70    ///
71    /// This can't return [`TimeInt::STATIC`], ever.
72    #[inline]
73    pub fn new_temporal(time: i64) -> Self {
74        NonMinI64::new(time).map_or(Self::MIN, |t| Self(Some(t)))
75    }
76
77    /// For time timelines.
78    #[inline]
79    pub fn from_nanos(nanos: NonMinI64) -> Self {
80        Self(Some(nanos))
81    }
82
83    /// For time timelines.
84    #[inline]
85    pub fn from_millis(millis: NonMinI64) -> Self {
86        Self::new_temporal(millis.get().saturating_mul(1_000_000))
87    }
88
89    /// For time timelines.
90    #[inline]
91    pub fn from_secs(seconds: f64) -> Self {
92        Self::new_temporal((seconds * 1e9).round() as _)
93    }
94
95    /// For sequence timelines.
96    #[inline]
97    pub fn from_sequence(sequence: NonMinI64) -> Self {
98        Self(Some(sequence))
99    }
100
101    /// Clamp to valid non-static range.
102    #[inline]
103    pub fn saturated_temporal_i64(value: impl Into<i64>) -> Self {
104        Self(Some(NonMinI64::saturating_from_i64(value)))
105    }
106
107    /// Clamp to valid non-static range.
108    #[inline]
109    pub fn saturated_temporal(value: impl TryInto<Self>) -> Self {
110        value.try_into().unwrap_or(Self::MIN).max(Self::MIN)
111    }
112
113    /// Returns `i64::MIN` for [`Self::STATIC`].
114    #[inline]
115    pub const fn as_i64(self) -> i64 {
116        match self.0 {
117            Some(t) => t.get(),
118            None => i64::MIN,
119        }
120    }
121
122    /// Returns `f64::MIN` for [`Self::STATIC`].
123    #[inline]
124    pub const fn as_f64(self) -> f64 {
125        match self.0 {
126            Some(t) => t.get() as _,
127            None => f64::MIN,
128        }
129    }
130
131    /// Always returns [`Self::STATIC`] for [`Self::STATIC`].
132    #[inline]
133    #[must_use]
134    pub fn inc(self) -> Self {
135        match self.0 {
136            Some(t) => Self::new_temporal(t.get().saturating_add(1)),
137            None => self,
138        }
139    }
140
141    /// Always returns [`Self::STATIC`] for [`Self::STATIC`].
142    #[inline]
143    #[must_use]
144    pub fn dec(self) -> Self {
145        match self.0 {
146            Some(t) => Self::new_temporal(t.get().saturating_sub(1)),
147            None => self,
148        }
149    }
150}
151
152impl TryFrom<i64> for TimeInt {
153    type Error = TryFromIntError;
154
155    #[inline]
156    fn try_from(t: i64) -> Result<Self, Self::Error> {
157        let Some(t) = NonMinI64::new(t) else {
158            return Err(TryFromIntError);
159        };
160        Ok(Self(Some(t)))
161    }
162}
163
164impl From<NonMinI64> for TimeInt {
165    #[inline]
166    fn from(seq: NonMinI64) -> Self {
167        Self(Some(seq))
168    }
169}
170
171impl From<TimeInt> for NonMinI64 {
172    fn from(value: TimeInt) -> Self {
173        match value.0 {
174            Some(value) => value,
175            None => Self::MIN,
176        }
177    }
178}
179
180// TODO(#9534): refactor this mess
181// impl TryFrom<TimeInt> for NonMinI64 {
182//     type Error = TryFromIntError;
183
184//     #[inline]
185//     fn try_from(t: TimeInt) -> Result<Self, Self::Error> {
186//         Self::new(t.as_i64()).ok_or(TryFromIntError)
187//     }
188// }
189
190impl From<TimeInt> for Duration {
191    #[inline]
192    fn from(int: TimeInt) -> Self {
193        Self::from_nanos(int.as_i64())
194    }
195}
196
197impl From<TimeInt> for re_types_core::datatypes::TimeInt {
198    #[inline]
199    fn from(time: TimeInt) -> Self {
200        Self(time.as_i64())
201    }
202}
203
204impl From<re_types_core::datatypes::TimeInt> for TimeInt {
205    #[inline]
206    fn from(time: re_types_core::datatypes::TimeInt) -> Self {
207        Self::new_temporal(time.0)
208    }
209}
210
211impl std::ops::Add for TimeInt {
212    type Output = Self;
213
214    #[inline]
215    fn add(self, rhs: Self) -> Self::Output {
216        match (self.0, rhs.0) {
217            // temporal + temporal = temporal
218            (Some(lhs), Some(rhs)) => Self(Some(
219                NonMinI64::new(lhs.get().saturating_add(rhs.get())).unwrap_or(NonMinI64::MIN),
220            )),
221            // static + anything = static
222            _ => Self(None),
223        }
224    }
225}
226
227impl std::ops::Sub for TimeInt {
228    type Output = Self;
229
230    #[inline]
231    fn sub(self, rhs: Self) -> Self::Output {
232        match (self.0, rhs.0) {
233            // temporal + temporal = temporal
234            (Some(lhs), Some(rhs)) => Self(Some(
235                NonMinI64::new(lhs.get().saturating_sub(rhs.get())).unwrap_or(NonMinI64::MIN),
236            )),
237            // static - anything = static
238            _ => Self(None),
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn saturated_temporal() {
249        assert_eq!(TimeInt::saturated_temporal_i64(i64::MIN), TimeInt::MIN);
250        assert_eq!(TimeInt::saturated_temporal_i64(i64::MIN + 1), TimeInt::MIN);
251        assert_eq!(TimeInt::saturated_temporal_i64(i64::MAX), TimeInt::MAX);
252        assert_eq!(
253            TimeInt::saturated_temporal_i64(i64::MAX - 1),
254            TimeInt::new_temporal(i64::MAX - 1)
255        );
256    }
257}