Skip to main content

lmflow/
timestamp.rs

1//! Timestamps: the ordering and lifetime markers carried by every packet in the stream.
2//!
3//! The `i64` value space is partitioned into sentinel values around a legal data range:
4//! ```text
5//!  MIN      MIN+1       MIN+2      MIN+3  ...  MAX-3       MAX-2        MAX-1              MAX
6//!  Unset  Unstarted   PreStream    Min          Max     PostStream  OneOverPostStream    Done
7//!         └────────── never appears in a stream ─────────┘         └─── never in a stream ───┘
8//!                                 └─── legal data range ──┘
9//! ```
10//! - `PreStream` / `PostStream` — the special single-packet slots at the head and tail of a stream.
11//! - `Done` — the port is closed and no further data will arrive.
12//! - `Unset` — no value assigned (the default).
13
14use std::fmt;
15
16/// A timestamp with sentinel semantics. The inner `i64` is public so it converts directly to and
17/// from the C ABI representation.
18#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
19pub struct Timestamp(pub i64);
20
21impl Default for Timestamp {
22    /// Defaults to `Unset`, matching the C ABI's `LMFLOW_TS_UNSET`.
23    fn default() -> Self {
24        Self::unset()
25    }
26}
27
28impl Timestamp {
29    pub const fn unset() -> Self {
30        Timestamp(i64::MIN)
31    }
32    pub const fn unstarted() -> Self {
33        Timestamp(i64::MIN + 1)
34    }
35    pub const fn pre_stream() -> Self {
36        Timestamp(i64::MIN + 2)
37    }
38    pub const fn min() -> Self {
39        Timestamp(i64::MIN + 3)
40    }
41    pub const fn max() -> Self {
42        Timestamp(i64::MAX - 3)
43    }
44    pub const fn post_stream() -> Self {
45        Timestamp(i64::MAX - 2)
46    }
47    pub const fn one_over_post_stream() -> Self {
48        Timestamp(i64::MAX - 1)
49    }
50    pub const fn done() -> Self {
51        Timestamp(i64::MAX)
52    }
53
54    /// Whether this falls inside the ordinary data range `[Min, Max]`.
55    pub fn is_range_value(self) -> bool {
56        self >= Self::min() && self <= Self::max()
57    }
58
59    /// Whether this is allowed as the timestamp of a packet in a stream — which includes
60    /// `PreStream` and `PostStream`.
61    pub fn is_allowed_in_stream(self) -> bool {
62        self >= Self::pre_stream() && self <= Self::post_stream()
63    }
64
65    /// The next timestamp after this one that may legally appear in a stream.
66    ///
67    /// Nothing may follow `PreStream`, so that case returns `OneOverPostStream`.
68    pub fn next_allowed_in_stream(self) -> Self {
69        if self >= Self::max() || self == Self::pre_stream() {
70            Self::one_over_post_stream()
71        } else if self < Self::min() {
72            Self::min()
73        } else {
74            Timestamp(self.0 + 1)
75        }
76    }
77
78    pub fn has_next_allowed_in_stream(self) -> bool {
79        self < Self::max() && self != Self::pre_stream()
80    }
81
82    pub fn previous_allowed_in_stream(self) -> Self {
83        if self <= Self::min() || self == Self::post_stream() {
84            Self::unstarted()
85        } else if self > Self::max() {
86            Self::max()
87        } else {
88            Timestamp(self.0 - 1)
89        }
90    }
91}
92
93/// Addition and subtraction saturate: arithmetic near the sentinel values neither panics on
94/// overflow nor wraps around.
95impl std::ops::Add<i64> for Timestamp {
96    type Output = Timestamp;
97    fn add(self, rhs: i64) -> Timestamp {
98        Timestamp(self.0.saturating_add(rhs))
99    }
100}
101
102impl std::ops::Sub<i64> for Timestamp {
103    type Output = Timestamp;
104    fn sub(self, rhs: i64) -> Timestamp {
105        Timestamp(self.0.saturating_sub(rhs))
106    }
107}
108
109impl fmt::Display for Timestamp {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        let name = match *self {
112            t if t == Self::unset() => "Unset",
113            t if t == Self::unstarted() => "Unstarted",
114            t if t == Self::pre_stream() => "PreStream",
115            t if t == Self::post_stream() => "PostStream",
116            t if t == Self::one_over_post_stream() => "OneOverPostStream",
117            t if t == Self::done() => "Done",
118            _ => return write!(f, "{}", self.0),
119        };
120        f.write_str(name)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127
128    #[test]
129    fn sentinels_are_ordered() {
130        assert!(Timestamp::unset() < Timestamp::unstarted());
131        assert!(Timestamp::unstarted() < Timestamp::pre_stream());
132        assert!(Timestamp::pre_stream() < Timestamp::min());
133        assert!(Timestamp::min() < Timestamp::max());
134        assert!(Timestamp::max() < Timestamp::post_stream());
135        assert!(Timestamp::post_stream() < Timestamp::one_over_post_stream());
136        assert!(Timestamp::one_over_post_stream() < Timestamp::done());
137    }
138
139    #[test]
140    fn default_is_unset() {
141        assert_eq!(Timestamp::default(), Timestamp::unset());
142    }
143
144    #[test]
145    fn allowed_in_stream_range() {
146        assert!(Timestamp::pre_stream().is_allowed_in_stream());
147        assert!(Timestamp(0).is_allowed_in_stream());
148        assert!(Timestamp::post_stream().is_allowed_in_stream());
149        assert!(!Timestamp::unset().is_allowed_in_stream());
150        assert!(!Timestamp::unstarted().is_allowed_in_stream());
151        assert!(!Timestamp::one_over_post_stream().is_allowed_in_stream());
152        assert!(!Timestamp::done().is_allowed_in_stream());
153    }
154
155    #[test]
156    fn range_value_only_covers_data_interval() {
157        assert!(Timestamp(0).is_range_value());
158        assert!(Timestamp::min().is_range_value());
159        assert!(Timestamp::max().is_range_value());
160        assert!(!Timestamp::pre_stream().is_range_value());
161        assert!(!Timestamp::post_stream().is_range_value());
162    }
163
164    #[test]
165    fn next_allowed_in_stream_cases() {
166        assert_eq!(Timestamp(5).next_allowed_in_stream(), Timestamp(6));
167        // PreStream 之后不允许再有包
168        assert_eq!(
169            Timestamp::pre_stream().next_allowed_in_stream(),
170            Timestamp::one_over_post_stream()
171        );
172        assert_eq!(
173            Timestamp::max().next_allowed_in_stream(),
174            Timestamp::one_over_post_stream()
175        );
176        assert_eq!(
177            Timestamp::post_stream().next_allowed_in_stream(),
178            Timestamp::one_over_post_stream()
179        );
180        // 低于 Min 的(非 PreStream)哨兵推进到 Min
181        assert_eq!(
182            Timestamp::unstarted().next_allowed_in_stream(),
183            Timestamp::min()
184        );
185        assert_eq!(
186            Timestamp::unset().next_allowed_in_stream(),
187            Timestamp::min()
188        );
189    }
190
191    #[test]
192    fn previous_allowed_in_stream_cases() {
193        assert_eq!(Timestamp(5).previous_allowed_in_stream(), Timestamp(4));
194        assert_eq!(
195            Timestamp::min().previous_allowed_in_stream(),
196            Timestamp::unstarted()
197        );
198        assert_eq!(
199            Timestamp::post_stream().previous_allowed_in_stream(),
200            Timestamp::unstarted()
201        );
202        assert_eq!(
203            Timestamp::done().previous_allowed_in_stream(),
204            Timestamp::max()
205        );
206        assert_eq!(
207            Timestamp::one_over_post_stream().previous_allowed_in_stream(),
208            Timestamp::max()
209        );
210    }
211
212    #[test]
213    fn has_next_allowed_in_stream() {
214        assert!(Timestamp(0).has_next_allowed_in_stream());
215        assert!(!Timestamp::pre_stream().has_next_allowed_in_stream());
216        assert!(!Timestamp::max().has_next_allowed_in_stream());
217        assert!(!Timestamp::done().has_next_allowed_in_stream());
218    }
219
220    #[test]
221    fn arithmetic_saturates_instead_of_overflowing() {
222        assert_eq!(Timestamp::done() + 1, Timestamp::done());
223        assert_eq!(Timestamp::unset() - 1, Timestamp::unset());
224        assert_eq!(Timestamp(10) + 5, Timestamp(15));
225        assert_eq!(Timestamp(10) - 5, Timestamp(5));
226    }
227
228    #[test]
229    fn display_names_sentinels() {
230        assert_eq!(Timestamp::done().to_string(), "Done");
231        assert_eq!(Timestamp::pre_stream().to_string(), "PreStream");
232        assert_eq!(Timestamp(7).to_string(), "7");
233    }
234}