1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
//! Implement a fast 'timestamp' for determining when an event last
//! happened.

use std::sync::atomic::{AtomicU64, Ordering};

/// An object for determining whether an event happened,
/// and if yes, when it happened.
///
/// Every `Timestamp` has internal mutability.  A timestamp can move
/// forward in time, but never backwards.
///
/// Internally, it uses the `coarsetime` crate to represent times in a way
/// that lets us do atomic updates.
#[derive(Default, Debug)]
pub(crate) struct OptTimestamp {
    /// A timestamp (from `coarsetime`) describing when this timestamp
    /// was last updated.
    ///
    /// I'd rather just use [`coarsetime::Instant`], but that doesn't have
    /// an atomic form.
    latest: AtomicU64,
}
impl OptTimestamp {
    /// Construct a new timestamp that has never been updated.
    pub(crate) const fn new() -> Self {
        OptTimestamp {
            latest: AtomicU64::new(0),
        }
    }

    /// Update this timestamp to (at least) the current time.
    pub(crate) fn update(&self) {
        // TODO: Do we want to use 'Instant::recent() instead,' and
        // add an updater thread?
        self.update_to(coarsetime::Instant::now());
    }

    /// If the timestamp is currently unset, then set it to the current time.
    /// Otherwise leave it alone.
    pub(crate) fn update_if_none(&self) {
        let now = coarsetime::Instant::now().as_ticks();

        let _ignore = self
            .latest
            .compare_exchange(0, now, Ordering::Relaxed, Ordering::Relaxed);
    }

    /// Clear the timestamp and make it not updated again.
    pub(crate) fn clear(&self) {
        self.latest.store(0, Ordering::Relaxed);
    }

    /// Return the time since `update` was last called.
    ///
    /// Return `None` if update was never called.
    pub(crate) fn time_since_update(&self) -> Option<coarsetime::Duration> {
        self.time_since_update_at(coarsetime::Instant::now())
    }

    /// Return the time between the time when `update` was last
    /// called, and the time `now`.
    ///
    /// Return `None` if `update` was never called, or `now` is before
    /// that time.
    #[inline]
    pub(crate) fn time_since_update_at(
        &self,
        now: coarsetime::Instant,
    ) -> Option<coarsetime::Duration> {
        let earlier = self.latest.load(Ordering::Relaxed);
        let now = now.as_ticks();
        if now >= earlier && earlier != 0 {
            Some(coarsetime::Duration::from_ticks(now - earlier))
        } else {
            None
        }
    }

    /// Update this timestamp to (at least) the time `now`.
    #[inline]
    pub(crate) fn update_to(&self, now: coarsetime::Instant) {
        self.latest.fetch_max(now.as_ticks(), Ordering::Relaxed);
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod test {

    use super::*;

    #[test]
    fn opt_timestamp() {
        use coarsetime::{Duration, Instant};

        let ts = OptTimestamp::new();
        assert!(ts.time_since_update().is_none());

        let zero = Duration::from_secs(0);
        let one_sec = Duration::from_secs(1);

        let first = Instant::now();
        let in_a_bit = first + one_sec * 10;
        let even_later = first + one_sec * 25;

        assert!(ts.time_since_update_at(first).is_none());

        ts.update_to(first);
        assert_eq!(ts.time_since_update_at(first), Some(zero));
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(one_sec * 10));

        ts.update_to(in_a_bit);
        assert!(ts.time_since_update_at(first).is_none());
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(zero));
        assert_eq!(ts.time_since_update_at(even_later), Some(one_sec * 15));

        // Make sure we can't move backwards.
        ts.update_to(first);
        assert!(ts.time_since_update_at(first).is_none());
        assert_eq!(ts.time_since_update_at(in_a_bit), Some(zero));
        assert_eq!(ts.time_since_update_at(even_later), Some(one_sec * 15));

        ts.clear();
        assert!(ts.time_since_update_at(first).is_none());
        assert!(ts.time_since_update_at(in_a_bit).is_none());
        assert!(ts.time_since_update_at(even_later).is_none());
    }

    #[test]
    fn update_if_none() {
        let ts = OptTimestamp::new();
        assert!(ts.time_since_update().is_none());

        // Calling "update_if_none" on a None OptTimestamp should set it.
        let time1 = coarsetime::Instant::now();
        ts.update_if_none();
        let d = ts.time_since_update();
        let time2 = coarsetime::Instant::now();
        assert!(d.is_some());
        assert!(d.unwrap() <= time2 - time1);

        std::thread::sleep(std::time::Duration::from_millis(100));
        // Calling "update_if_none" on a Some OptTimestamp doesn't change it.
        let time3 = coarsetime::Instant::now();
        // If coarsetime doesn't register this, then the rest of our test won't work.
        assert!(time3 > time2);
        ts.update_if_none();
        let d2 = ts.time_since_update();
        assert!(d2.is_some());
        assert!(d2.unwrap() > d.unwrap());
    }
}