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
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{Duration, Instant as StdInstant};

/// When using a virtual clock, this defines how many nanoseconds we pretend are passing for each
/// basic block.
const NANOSECONDS_PER_BASIC_BLOCK: u64 = 10;

#[derive(Debug)]
pub struct Instant {
    kind: InstantKind,
}

#[derive(Debug)]
enum InstantKind {
    Host(StdInstant),
    Virtual { nanoseconds: u64 },
}

impl Instant {
    pub fn checked_add(&self, duration: Duration) -> Option<Instant> {
        match self.kind {
            InstantKind::Host(instant) =>
                instant.checked_add(duration).map(|i| Instant { kind: InstantKind::Host(i) }),
            InstantKind::Virtual { nanoseconds } =>
                u128::from(nanoseconds)
                    .checked_add(duration.as_nanos())
                    .and_then(|n| u64::try_from(n).ok())
                    .map(|nanoseconds| Instant { kind: InstantKind::Virtual { nanoseconds } }),
        }
    }

    pub fn duration_since(&self, earlier: Instant) -> Duration {
        match (&self.kind, earlier.kind) {
            (InstantKind::Host(instant), InstantKind::Host(earlier)) =>
                instant.duration_since(earlier),
            (
                InstantKind::Virtual { nanoseconds },
                InstantKind::Virtual { nanoseconds: earlier },
            ) => Duration::from_nanos(nanoseconds.saturating_sub(earlier)),
            _ => panic!("all `Instant` must be of the same kind"),
        }
    }
}

/// A monotone clock used for `Instant` simulation.
#[derive(Debug)]
pub struct Clock {
    kind: ClockKind,
}

#[derive(Debug)]
enum ClockKind {
    Host {
        /// The "time anchor" for this machine's monotone clock.
        time_anchor: StdInstant,
    },
    Virtual {
        /// The "current virtual time".
        nanoseconds: AtomicU64,
    },
}

impl Clock {
    /// Create a new clock based on the availability of communication with the host.
    pub fn new(communicate: bool) -> Self {
        let kind = if communicate {
            ClockKind::Host { time_anchor: StdInstant::now() }
        } else {
            ClockKind::Virtual { nanoseconds: 0.into() }
        };

        Self { kind }
    }

    /// Let the time pass for a small interval.
    pub fn tick(&self) {
        match &self.kind {
            ClockKind::Host { .. } => {
                // Time will pass without us doing anything.
            }
            ClockKind::Virtual { nanoseconds } => {
                nanoseconds.fetch_add(NANOSECONDS_PER_BASIC_BLOCK, Ordering::SeqCst);
            }
        }
    }

    /// Sleep for the desired duration.
    pub fn sleep(&self, duration: Duration) {
        match &self.kind {
            ClockKind::Host { .. } => std::thread::sleep(duration),
            ClockKind::Virtual { nanoseconds } => {
                // Just pretend that we have slept for some time.
                nanoseconds.fetch_add(duration.as_nanos().try_into().unwrap(), Ordering::SeqCst);
            }
        }
    }

    /// Return the `anchor` instant, to convert between monotone instants and durations relative to the anchor.
    pub fn anchor(&self) -> Instant {
        match &self.kind {
            ClockKind::Host { time_anchor } => Instant { kind: InstantKind::Host(*time_anchor) },
            ClockKind::Virtual { .. } => Instant { kind: InstantKind::Virtual { nanoseconds: 0 } },
        }
    }

    pub fn now(&self) -> Instant {
        match &self.kind {
            ClockKind::Host { .. } => Instant { kind: InstantKind::Host(StdInstant::now()) },
            ClockKind::Virtual { nanoseconds } =>
                Instant {
                    kind: InstantKind::Virtual { nanoseconds: nanoseconds.load(Ordering::SeqCst) },
                },
        }
    }
}