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
//!
//! # simpler_timer
//! 
//! A simple timer mechanism to track arbitrary timeouts.
//! It doesn't do anything fancy, e.g. no callbacks upon expiry, just give it a Duration
//! and poll if the timer is expired. Timers can be reset and reused
//! for periodic contexts, such as a simple time based control loop.
//! 
//! # Example
//! 
//! ```
//! use std::time::Duration;
//! 
//! // 100ms timer
//! let tick = Timer::with_duration(Duration::from_millis(100));
//! // 1 sec timer
//! let end = Timer::with_duration(Duration::from_secs(1));
//! 
//! loop {
//!     if tick.expired() {
//!         // do something interesting
//!         println!("tick");
//!         tick.reset();
//!     }
//! 
//!     if end.expired() { 
//!         // don't reset, let's get out of here
//!         break; 
//!     }
//! }
//! 
//! println!("total time: {}ms", end.elapsed().as_millis());
//! 
//! ```
//!

//! 
use std::cell::Cell;
use std::time::{Duration, Instant};

/// Timer provides extremely basic timing abilities
#[derive(Debug, Clone)]
pub struct Timer {
    instant: Cell<Instant>,
    duration: Duration,
}


impl Timer {
    /// Creates a new timer of zero `Duration`.
    /// 
    /// Similar to `std::time::Instant` as this is really only useful 
    /// for getting `elapsed` time since `reset`
    pub fn new() -> Timer {
        Timer {
            instant: Cell::new(Instant::now()),
            duration: Duration::from_secs(0),
        }
    }

    /// Creates a new timer with `duration` length
    pub fn with_duration(duration: Duration) -> Timer {
        let mut timer = Timer::new();
        timer.duration = duration;
        timer
    }

    /// Resets the timer.
    /// 
    /// # Note
    /// The decision was made intentionally to only require a `&self` for 
    /// resetting a timer so that another object can own a `Timer` and not require
    /// `&mut self` of the object owning the timer.   
    /// 
    /// `elapsed()` will start over at 0 after a `reset()`
    pub fn reset(&self) {
        self.instant.set(Instant::now());
    }

    /// Check if the timer is expired
    /// 
    /// `expired` = `elapsed` >= `duration`
    pub fn expired(&self) -> bool {
        self.instant.get().elapsed() >= self.duration
    }

    /// Return a `Duration` of the configured time of the Timer
    pub fn duration(&self) -> Duration {
        self.duration
    }

    /// Block execution until the timer expires. 
    /// 
    /// - If the timer is already expired, this returns immediately
    pub fn wait(&self) {
        if let Some(duration) = self.duration.checked_sub(self.instant.get().elapsed()) {
            std::thread::sleep(duration);
        }
    }

    /// Get `Duration` of time elapsed since `Timer` `reset`
    /// 
    /// # Note
    /// A newly constructed timer is considered to be `reset`
    pub fn elapsed(&self) -> Duration {
        self.instant.get().elapsed()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[ignore]
    fn wait_test() {
        let timer = Timer::with_duration(Duration::from_millis(500));
        timer.wait();
        assert!(timer.elapsed().as_millis() >= 500);
    }

    // This test is really poor as it relies on actual time and not a mocked time, so results are unpredictable
    #[test]
    #[ignore]
    fn wait_should_account_for_elapsed_time() {
        let timer = Timer::with_duration(Duration::from_millis(50));
        std::thread::sleep(Duration::from_millis(25));
        let pre_wait = timer.elapsed();
        timer.wait();

        let diff = timer.elapsed() - pre_wait;
        let diff = diff.as_millis();
        assert!(diff < 50);
        assert!(diff >= 25);
    }

}