time.rsannotatedtime.rssource104 lines · 3.1 KB · raw

Time as the ports see it: an instant in milliseconds and a UTC day. A port never reads a clock itself (there is none to read in a Worker's pure code); it is handed the moment.

4use ::log::trace;
5use serde::{Deserialize, Serialize};

An instant, in milliseconds since the Unix epoch.

8#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
9#[serde(transparent)]
10pub struct Millis(u64);
12impl Millis {
13    pub const fn new(ms: u64) -> Self {
14        Self(ms)
15    }
16
17    pub const fn get(self) -> u64 {
18        self.0
19    }
20
21    pub const fn saturating_add(self, ms: u64) -> Self {
22        Self(self.0.saturating_add(ms))
23    }
24}

One UTC day, counted from the epoch. The natural voice's daily allowance is spent against one of these, so two backends in different time zones still agree on when "today" ends.

28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
29#[serde(transparent)]
30pub struct Day(u64);
32const DAY_MS: u64 = 86_400_000;
33
34impl Day {
35    pub const fn containing(at: Millis) -> Self {
36        Self(at.get() / DAY_MS)
37    }

The day's number since the epoch.

40    pub const fn number(self) -> u64 {
41        self.0
42    }

The moment the following day begins: when a spent daily allowance frees up.

45    pub const fn ends_at(self) -> Millis {
46        Millis::new(self.0.saturating_add(1).saturating_mul(DAY_MS))
47    }
48}

What time it is now. Sync because every backend can answer from memory (Date.now(), the system clock) and a port that had to await the time would make every decision a suspension point.

52pub trait Clock {
53    fn now(&self) -> Millis;
54}

A clock that only moves when told to, for the in-memory adapter and for tests.

57#[derive(Debug, Default)]
58pub struct ManualClock(std::sync::atomic::AtomicU64);
60impl ManualClock {
61    pub fn at(ms: u64) -> Self {
62        Self(std::sync::atomic::AtomicU64::new(ms))
63    }
64
65    pub fn set(&self, at: Millis) {
66        trace!("manual clock set to {} ms", at.get());
67        self.0.store(at.get(), std::sync::atomic::Ordering::SeqCst);
68    }
69
70    pub fn advance(&self, ms: u64) {
71        trace!("manual clock advances {ms} ms");
72        self.0.fetch_add(ms, std::sync::atomic::Ordering::SeqCst);
73    }
74}
75
76impl Clock for ManualClock {
77    fn now(&self) -> Millis {
78        Millis::new(self.0.load(std::sync::atomic::Ordering::SeqCst))
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn a_day_ends_when_the_next_one_begins() {
88        let d = Day::containing(Millis::new(DAY_MS * 5 + 17));
89        assert_eq!(d.number(), 5);
90        assert_eq!(d.ends_at(), Millis::new(DAY_MS * 6));
91        assert_eq!(Day::containing(d.ends_at()).number(), 6, "the next day starts exactly there");
92        assert_eq!(Day::containing(Millis::new(d.ends_at().get() - 1)), d);
93    }
94
95    #[test]
96    fn the_manual_clock_moves_only_when_told() {
97        let c = ManualClock::at(10);
98        assert_eq!(c.now(), Millis::new(10));
99        c.advance(5);
100        assert_eq!(c.now().get(), 15);
101        c.set(Millis::new(3));
102        assert_eq!(c.now().get(), 3);
103    }
104}