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