spacetimedb_lib/
scheduler.rs

1use std::fmt::Debug;
2
3use spacetimedb_lib::{TimeDuration, Timestamp};
4use spacetimedb_sats::{
5    algebraic_value::de::{ValueDeserializeError, ValueDeserializer},
6    de::Deserialize,
7    impl_st,
8    ser::Serialize,
9    sum_type::{SCHEDULE_AT_INTERVAL_TAG, SCHEDULE_AT_TIME_TAG},
10    AlgebraicType, AlgebraicValue,
11};
12
13/// When a scheduled reducer should execute,
14/// either at a specific point in time,
15/// or at regular intervals for repeating schedules.
16///
17/// Stored in reducer-scheduling tables as a column.
18///
19/// This is a special type.
20#[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
21pub enum ScheduleAt {
22    /// A regular interval at which the repeated reducer is scheduled.
23    /// Value is a [`TimeDuration`], which has nanosecond precision.
24    Interval(TimeDuration),
25    /// A specific time to which the reducer is scheduled.
26    Time(Timestamp),
27}
28impl_st!([] ScheduleAt, ScheduleAt::get_type());
29
30impl ScheduleAt {
31    /// Converts the `ScheduleAt` to a `std::time::Duration` from now.
32    ///
33    /// Returns [`std::time::Duration::ZERO`] if `self` represents a time in the past.
34    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
35    pub fn to_duration_from(&self, from: Timestamp) -> std::time::Duration {
36        use std::time::Duration;
37        match self {
38            ScheduleAt::Time(time) => time.duration_since(from).unwrap_or(Duration::ZERO),
39            // TODO(correctness): Determine useful behavior on negative intervals,
40            // as that's the case where `to_duration` fails.
41            // Currently, we use the magnitude / absolute value,
42            // which seems at least less stupid than clamping to zero.
43            ScheduleAt::Interval(dur) => dur.to_duration_abs(),
44        }
45    }
46
47    /// Converts the `ScheduleAt` to a `Timestamp`.
48    ///
49    /// If `self` is an interval, returns `from + dur`.
50    #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
51    pub fn to_timestamp_from(&self, from: Timestamp) -> Timestamp {
52        match *self {
53            ScheduleAt::Time(time) => time,
54            ScheduleAt::Interval(dur) => from + dur.abs(),
55        }
56    }
57
58    /// Get the special `AlgebraicType` for `ScheduleAt`.
59    pub fn get_type() -> AlgebraicType {
60        AlgebraicType::sum([
61            (SCHEDULE_AT_INTERVAL_TAG, AlgebraicType::time_duration()),
62            (SCHEDULE_AT_TIME_TAG, AlgebraicType::timestamp()),
63        ])
64    }
65}
66
67impl From<TimeDuration> for ScheduleAt {
68    fn from(value: TimeDuration) -> Self {
69        ScheduleAt::Interval(value)
70    }
71}
72
73impl From<std::time::Duration> for ScheduleAt {
74    fn from(value: std::time::Duration) -> Self {
75        ScheduleAt::Interval(TimeDuration::from_duration(value))
76    }
77}
78
79impl From<std::time::SystemTime> for ScheduleAt {
80    fn from(value: std::time::SystemTime) -> Self {
81        Timestamp::from(value).into()
82    }
83}
84
85impl From<crate::Timestamp> for ScheduleAt {
86    fn from(value: crate::Timestamp) -> Self {
87        ScheduleAt::Time(value)
88    }
89}
90
91impl TryFrom<AlgebraicValue> for ScheduleAt {
92    type Error = ValueDeserializeError;
93    fn try_from(value: AlgebraicValue) -> Result<Self, Self::Error> {
94        ScheduleAt::deserialize(ValueDeserializer::new(value))
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use spacetimedb_sats::bsatn;
102
103    #[test]
104    fn test_bsatn_roundtrip() {
105        let schedule_at = ScheduleAt::Interval(TimeDuration::from_micros(10000));
106        let ser = bsatn::to_vec(&schedule_at).unwrap();
107        let de = bsatn::from_slice(&ser).unwrap();
108        assert_eq!(schedule_at, de);
109    }
110
111    #[test]
112    fn schedule_at_is_special() {
113        assert!(ScheduleAt::get_type().is_special());
114    }
115}