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_now(&self) -> std::time::Duration {
35        use std::time::{Duration, SystemTime};
36        match self {
37            ScheduleAt::Time(time) => {
38                let now = SystemTime::now();
39                let time = SystemTime::from(*time);
40                time.duration_since(now).unwrap_or(Duration::ZERO)
41            }
42            // TODO(correctness): Determine useful behavior on negative intervals,
43            // as that's the case where `to_duration` fails.
44            // Currently, we use the magnitude / absolute value,
45            // which seems at least less stupid than clamping to zero.
46            ScheduleAt::Interval(dur) => dur.to_duration_abs(),
47        }
48    }
49
50    /// Get the special `AlgebraicType` for `ScheduleAt`.
51    pub fn get_type() -> AlgebraicType {
52        AlgebraicType::sum([
53            ("Interval", AlgebraicType::time_duration()),
54            ("Time", AlgebraicType::timestamp()),
55        ])
56    }
57}
58
59impl From<TimeDuration> for ScheduleAt {
60    fn from(value: TimeDuration) -> Self {
61        ScheduleAt::Interval(value)
62    }
63}
64
65impl From<std::time::Duration> for ScheduleAt {
66    fn from(value: std::time::Duration) -> Self {
67        ScheduleAt::Interval(TimeDuration::from_duration(value))
68    }
69}
70
71impl From<std::time::SystemTime> for ScheduleAt {
72    fn from(value: std::time::SystemTime) -> Self {
73        Timestamp::from(value).into()
74    }
75}
76
77impl From<crate::Timestamp> for ScheduleAt {
78    fn from(value: crate::Timestamp) -> Self {
79        ScheduleAt::Time(value)
80    }
81}
82
83impl TryFrom<AlgebraicValue> for ScheduleAt {
84    type Error = ValueDeserializeError;
85    fn try_from(value: AlgebraicValue) -> Result<Self, Self::Error> {
86        ScheduleAt::deserialize(ValueDeserializer::new(value))
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use spacetimedb_sats::bsatn;
94
95    #[test]
96    fn test_bsatn_roundtrip() {
97        let schedule_at = ScheduleAt::Interval(TimeDuration::from_micros(10000));
98        let ser = bsatn::to_vec(&schedule_at).unwrap();
99        let de = bsatn::from_slice(&ser).unwrap();
100        assert_eq!(schedule_at, de);
101    }
102
103    #[test]
104    fn schedule_at_is_special() {
105        assert!(ScheduleAt::get_type().is_special());
106    }
107}