ticktock/
lib.rs

1//! Timing module for frame-based applications
2//!
3//! Contains methods for slowing down to a fixed framerate, as well as
4//! measuring actual frames per second.
5//!
6//! An example game loop:
7//!
8//! ```ignore
9//! use std::time;
10//! use ticktock::{Clock, Timer};
11//!
12//! let now = time::Instant::now();
13//!
14//! // initialize game
15//! // ...
16//!
17//! // show some fps measurements every 5 seconds
18//! let mut fps_counter = Timer::apply(|delta_t, prev_tick| (delta_t, *prev_tick), 0)
19//!     .every(time::Duration::from_secs(5))
20//!     .start(now);
21//!
22//! // run with a constant framerate of 30 fps
23//! for (tick, now) in Clock::framerate(30.0).iter() {
24//!     // this loop will run approx. every 33.3333 ms
25//!
26//!     // update, render, etc
27//!     // ...
28//!
29//!     // update or display fps count
30//!     if let Some((delta_t, prev_tick)) = fps_counter.update(now) {
31//!         fps_counter.set_value(tick);
32//!
33//!         let fps = (tick - prev_tick) as f64 / delta_t.as_secs_f64();
34//!         println!("FPS: {}", fps);
35//!     }
36//!     break; // ignore, for doctests
37//! }
38//! ```
39
40pub mod clock;
41pub mod delay;
42pub mod throttled_io;
43pub mod timer;
44
45pub use crate::clock::Clock;
46pub use crate::timer::Timer;
47
48/// Iterator attempt
49///
50/// Given an iterator of outcomes, iterates returning either
51///
52/// * the first successful outcome
53/// * the last unsuccessful outcome
54/// * `None` if the iterator was empty
55///
56/// `Result` is often used as an outcome, e.g. when trying to reconnect multiple times:
57///
58/// ```rust
59/// use std::net::TcpStream;
60/// use std::time::Duration;
61/// use ticktock::delay::Delay;
62/// use ticktock::Attempt;
63///
64/// const RETRY_DELAY: Duration = Duration::from_millis(250);
65///
66/// // attempt to connect to localhost:12348 three times, before giving up.
67/// // in total, 500 ms of delay will be inserted
68/// let conn = Delay::new(RETRY_DELAY)
69///     .map(|_| TcpStream::connect("localhost:12348"))
70///     .take(3)
71///     .attempt()
72///     .unwrap();
73///
74/// # // our test will fail, because there is noting listening at 12348
75/// # assert!(conn.is_err());
76/// ```
77///
78/// `Option` is also a valid outcome:
79///
80/// ```ignore
81/// let credentials = vec![("Bob", "secret"), ("Jeff", "hunter2"), ("John", "swordfish")];
82///
83/// fn try_login(username: &str, password: &str) -> Option<(String, String)> { ... }
84///
85/// // brute-force our way in
86/// let valid_credentials: Option<(String, String)> = credentials
87///                                                       .map(|(u, p)| try_login(u, p))
88///                                                       .attempt()
89///                                                       .unwrap();
90/// ```
91
92// note: this could probably be expressed more cleanly by using associated types
93// (i.e. `type Outcome = ...`), but a bug in the rust compiler at the time of this writing
94// did not allow for it https://github.com/rust-lang/rust/issues/20400
95
96pub trait Attempt<O> {
97    /// Consumes until the successful outcome is encountered. In case of failure, returns the last
98    /// unsuccessful outcome.
99    fn attempt(self) -> Option<O>;
100}
101
102impl<T, E, I> Attempt<Result<T, E>> for I
103where
104    I: Iterator<Item = Result<T, E>>,
105{
106    fn attempt(self) -> Option<Result<T, E>> {
107        let mut rv = None;
108
109        for res in self {
110            rv = Some(res);
111
112            // do not keep going if we got an Ok
113            if let Some(Ok(_)) = rv {
114                break;
115            }
116        }
117
118        rv
119    }
120}
121
122impl<T, I> Attempt<Option<T>> for I
123where
124    I: Iterator<Item = Option<T>>,
125{
126    fn attempt(self) -> Option<Option<T>> {
127        let mut rv = None;
128
129        for res in self {
130            rv = Some(res);
131
132            // do not keep going if we got an Ok
133            if let Some(Some(_)) = rv {
134                break;
135            }
136        }
137
138        rv
139    }
140}
141
142#[cfg(test)]
143mod test {
144    use super::Attempt;
145
146    #[test]
147    fn attempt_works_on_ok_results() {
148        let rs = vec![Err(1), Err(2), Err(3), Ok(4), Ok(5)];
149
150        assert_eq!(Some(Ok(4)), rs.into_iter().attempt());
151    }
152
153    #[test]
154    fn attempt_works_on_err_results() {
155        let rs: Vec<Result<(), _>> = vec![Err(1), Err(2), Err(3)];
156
157        assert_eq!(Some(Err(3)), rs.into_iter().attempt());
158    }
159
160    #[test]
161    fn attempt_works_on_empty_result_vecs() {
162        let rs: Vec<Result<(), ()>> = Vec::new();
163
164        assert_eq!(None, rs.into_iter().attempt());
165    }
166
167    #[test]
168    fn attempt_works_on_some_options() {
169        let rs = vec![None, None, None, Some(4), Some(5)];
170
171        assert_eq!(Some(Some(4)), rs.into_iter().attempt());
172    }
173
174    #[test]
175    fn attempt_works_on_none_options() {
176        let rs: Vec<Option<()>> = vec![None, None, None];
177
178        assert_eq!(Some(None), rs.into_iter().attempt());
179    }
180
181    #[test]
182    fn attempt_works_on_empty_option_vecs() {
183        let rs: Vec<Option<()>> = Vec::new();
184
185        assert_eq!(None, rs.into_iter().attempt());
186    }
187}