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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
// libsw: stopwatch library
// copyright (C) 2022  Nissa <and-nissa@protonmail.com>
// licensed under MIT OR GPL-3.0-or-later

use core::ops;
use core::time::Duration;
use std::time::Instant;

/// Stopwatch abstraction
///
/// Measures and accumulates time between starts and stops.
///
/// # Notes
///
/// [`Stopwatch`] implements [`Default`], returning a stopped stopwatch with
/// zero time elapsed.
///
/// Also, it implements [`Add`](ops::Add), [`Sub`](ops::Sub),
/// [`AddAssign`](ops::AddAssign), and [`SubAssign`](ops::SubAssign). These
/// methods will add or subtract the right-hand-side duration from the
/// stopwatch's elapsed time.
///
/// # Panics
///
/// None of the methods in [`Stopwatch`] panic or call functions which are
/// documented to panic.
#[derive(Clone, Copy, Debug)]
pub struct Stopwatch {
    elapsed: Duration,
    start: Option<Instant>,
}

impl Stopwatch {
    /// Create a [`Stopwatch`] with the given elapsed time. If `running`, start
    /// the stopwatch.
    #[inline]
    #[must_use]
    pub fn new(elapsed: Duration, running: bool) -> Self {
        Self {
            elapsed,
            start: if running { Some(Instant::now()) } else { None },
        }
    }

    /// Start measuring the time elapsed.
    ///
    /// # Errors
    ///
    /// Returns [`Error::AlreadyStarted`] if the stopwatch is running.
    pub fn start(&mut self) -> Result<(), Error> {
        if self.is_running() {
            Err(Error::AlreadyStarted)
        } else {
            self.start = Some(Instant::now());
            Ok(())
        }
    }

    /// Stop measuring the time elapsed since the last start.
    ///
    /// # Errors
    ///
    /// Returns [`Error::AlreadyStopped`] if the stopwatch is not running.
    pub fn stop(&mut self) -> Result<(), Error> {
        if let Some(start) = self.start {
            *self += Instant::now().saturating_duration_since(start);
            self.start = None;
            Ok(())
        } else {
            Err(Error::AlreadyStopped)
        }
    }

    /// Toggle whether the stopwatch is running or stopped.
    ///
    /// If stopped, then start, and if running, then stop.
    #[inline]
    pub fn toggle(&mut self) {
        if self.is_running() {
            let _ = self.stop();
        } else {
            let _ = self.start();
        }
    }

    /// Stop and reset the elapsed time to zero.
    #[inline]
    pub fn reset(&mut self) {
        *self = Self::default();
    }

    /// Stop and set the total elapsed time to `new`.
    #[inline]
    pub fn set(&mut self, new: Duration) {
        self.elapsed = new;
        self.start = None;
    }

    /// Return the total time elapsed.
    #[must_use]
    pub fn elapsed(&self) -> Duration {
        if let Some(start) = self.start {
            self.elapsed
                .saturating_add(Instant::now().saturating_duration_since(start))
        } else {
            self.elapsed
        }
    }

    /// Return whether the stopwatch is running.
    #[inline]
    #[must_use]
    pub const fn is_running(&self) -> bool {
        self.start.is_some()
    }

    /// Return whether the stopwatch is stopped.
    #[inline]
    #[must_use]
    pub const fn is_stopped(&self) -> bool {
        self.start.is_none()
    }

    /// Sync changes in the elapsed time, effectively toggling the stopwatch
    /// twice.
    #[inline]
    fn sync_elapsed(&mut self) {
        if let Some(start) = self.start {
            let now = Instant::now();
            *self += now.saturating_duration_since(start);
            self.start = Some(now);
        }
    }
}

impl Default for Stopwatch {
    #[inline]
    fn default() -> Self {
        Self::new(Duration::ZERO, false)
    }
}

impl ops::Add<Duration> for Stopwatch {
    type Output = Self;

    #[inline]
    fn add(mut self, rhs: Duration) -> Self::Output {
        self += rhs;
        self
    }
}

impl ops::Sub<Duration> for Stopwatch {
    type Output = Self;

    #[inline]
    fn sub(mut self, rhs: Duration) -> Self::Output {
        self -= rhs;
        self
    }
}

impl ops::AddAssign<Duration> for Stopwatch {
    #[inline]
    fn add_assign(&mut self, rhs: Duration) {
        self.elapsed = self.elapsed.saturating_add(rhs);
    }
}

impl ops::SubAssign<Duration> for Stopwatch {
    #[inline]
    fn sub_assign(&mut self, rhs: Duration) {
        self.sync_elapsed();
        self.elapsed = self.elapsed.saturating_sub(rhs);
    }
}

/// Errors associated with [`Stopwatch`]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Error {
    /// Called [`Stopwatch::start`] while running
    AlreadyStarted,
    /// Called [`Stopwatch::stop`] while stopped
    AlreadyStopped,
}