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
//! This crate provides a utility for counting updates, for instance frame rates. //! The struct [`UpdateRateCounter`](./struct.UpdateRateCounter.html) has a method, `.update()`, which is meant to be called //! every time your system updates (e.g. every frame, every physics update, etc). //! //! # Examples //! The one important thing to remember is to call your Counter's `update()` at the beginning of your cycles. //! //! ``` //! use update_rate::UpdateRateCounter; //! //! // Create a new UpdateRateCounter with a sample rate of 10 updates //! let mut c = UpdateRateCounter::new(10); //! //! for _ in 1..11 { //! c.update(); //! // Rate should be 100 Hz with 10 ms/update //! std::thread::sleep(std::time::Duration::from_millis(10)); //! } //! //! let difference = 100.0 - c.rate(); //! println!("Rate was {}", c.rate()); //! assert!(difference < 10.0, "Counter rate should be closer to actual rate."); //! ``` use std::time::{Duration, Instant}; /// A generic non-rolling update counter, suitable for rapidly-changing rate counting, such as FPS counters in games. /// # Usage /// Call `.update()` every time your system starts a new update/cycle; for instance, /// an FPS counter would call this at the beginning of every frame. /// The sample rate (set with `set_sample_rate()` and in the first argument to `new()`) /// governs how many `.update()` calls are required before a meaningful result is produced. pub struct UpdateRateCounter { updates_since_clear: u64, time_at_last_clear: Instant, rate: f64, sample_rate: u64 } impl UpdateRateCounter { /// Create a new UpdateRateCounter which calculates the update rate every `sample_rate` cycles. /// Until that many cycles occur, `rate()` will return a useless value, typically 0.0. /// /// If this isn't acceptable, one strategy is to start the sample rate at 0 and /// keep ramping it up until it reaches your target sample rate; however, the data near the beginning will be utter trash. pub fn new(sample_rate: u64) -> Self { UpdateRateCounter { updates_since_clear: 0, time_at_last_clear: Instant::now(), rate: 0.0, sample_rate: sample_rate } } /// Return the current rate at which the UpdateRateCounter is measuring, in updates. pub fn sample_rate(&self) -> u64 { self.sample_rate } /// Set the number of updates which the UpdateRateCounter waits before updating its status. pub fn set_sample_rate(&mut self, sample_rate: u64) { self.sample_rate = sample_rate } /// Call this at the beginning of each cycle of the periodic activity being measured. pub fn update(&mut self) { self.updates_since_clear += 1; if self.updates_since_clear >= self.sample_rate { let elapsed = self.time_at_last_clear.elapsed(); // Compose a f64 of the amount of time elapsed since the last update; that's seconds plus nanos let real_time_since_clear = elapsed.as_secs() as f64 + elapsed.subsec_nanos() as f64 * (1.0/1000000000.0); // The rate is the number of updates, over the amount of time it's taken to do them self.rate = self.updates_since_clear as f64 / real_time_since_clear; // Reset the structure self.updates_since_clear = 0; self.time_at_last_clear = Instant::now(); } } /// Return the last calculated rate of operation, in Hertz (updates per second). pub fn rate(&self) -> f64 { self.rate } /// Return the number of cycles since the rate was last recalculated. pub fn rate_age_cycles(&self) -> u64 { self.updates_since_clear } /// Return the amount of time since the rate was last recalculated. This requires examining the system clock /// and is thus relatively expensive. pub fn rate_age_duration(&self) -> Duration { self.time_at_last_clear.elapsed() } } #[cfg(test)] mod tests { use super::*; #[test] fn test_update_rate_counter() { let mut c = UpdateRateCounter::new(10); assert!(c.rate() == 0.0, "Counter should have no data before it gets enough samples."); for i in 1..11 { // Rate should be 100 Hz with 10 ms/update std::thread::sleep(std::time::Duration::from_millis(10)); c.update(); assert!(c.rate_age_cycles() == i % 10, // Mod 10 because rate_age_cycles will go back to 0 at sample_rate which is 10 "Rate age not in sync with cycle loop! {} loops but ras = {} ", i, c.rate_age_cycles()); } let difference = 100.0 - c.rate(); println!("Rate was {}", c.rate()); assert!(difference < 10.0, "Counter rate should be closer to actual rate."); } }