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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
// espera::looper
//
//! Loop manager with support for multiple rates.
//
use crate::all::{Duration, EsperaResult, Instant, Rate, RateStats};
use ahash::AHashMap;
use sixbit::{DecodeSixbit, EncodeSixbit};
use std::thread::sleep;
/// The status of a given [`Looper`].
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LoopStatus {
Active,
Asleep,
}
/// A loop manager that supports multiple [`Rate`]s.
#[derive(Clone, Debug)]
pub struct Looper {
/// The loop status.
///
/// Forces to alternate between a single sleep period and an active period.
status: LoopStatus,
/// The root rate.
root_rate: Rate,
/// Stats for the root rate.
root_stats: RateStats,
/// Custom rates.
rates: AHashMap<u128, Rate>,
/// Stats for the custom rates.
stats: AHashMap<u128, RateStats>,
}
impl Default for Looper {
fn default() -> Self {
Self {
status: LoopStatus::Active,
root_rate: Rate::default(),
root_stats: RateStats::default(),
rates: AHashMap::new(),
stats: AHashMap::new(),
}
}
}
impl Looper {
/// Returns a new default looper.
pub fn new() -> Self {
Self::default()
}
}
impl Looper {
/// Returns the current loop state machine status.
pub fn status(&self) -> LoopStatus {
self.status
}
/// Takes a measure.
///
/// Returns the *now* instant and the *delta* since the last measure was taken.
///
/// # State machine rules
/// On LoopStatus match:
/// + `Asleep`:
/// - sets status to `Active`
/// - sets the last measure to *now*.
/// + `Active`:
/// - Returns `None`.
//
// RETHINK REMOVING the state machine…
//
pub fn measure(&mut self) -> Option<(Instant, Duration)> {
match self.status {
LoopStatus::Asleep => {
let (now, delta) = self.now_delta();
self.root_rate.set_last_tick(now);
self.root_rate.increment_ticks();
self.status = LoopStatus::Active;
/* root averages */
let ns: u64 = delta.whole_nanoseconds() as u64;
self.root_stats.add_ns(ns);
self.root_stats.update(self.root_rate.ticks());
Some((now, delta))
}
LoopStatus::Active => None,
}
}
/// Returns the current instant and the delta duration since last measure,
/// calculated using that instant.
#[inline]
pub fn now_delta(&self) -> (Instant, Duration) {
let now = Instant::now();
let delta = now - self.root_rate.last_tick();
(now, delta)
}
// MAYBE:WIP
// /// Returns the difference between the last tick and the ideal instant
// /// it should have been according to the real time.
// pub fn root_desviation(&self) -> Duration {
// self.root_rate.first_tick();
// }
/// Resets all the accumulated times and statistics.
// TODO
// MAYBE RENAME to reset_all?)
pub fn reset(&mut self) {
self.reset_root();
// self.reset_rates(); // TODO
}
/// Resets the accumulated times and statistics.
//
// RETHINK: generalize reset for each rate.
// MAYBE reset only the base, or all the rates? (separate function)
// WIP
#[inline]
pub fn reset_root(&mut self) {
self.status = LoopStatus::Asleep;
self.root_rate.reset();
self.root_stats.reset();
}
/// Resets the accumulated times and statistics for all rates.
// TODO
pub fn reset_rate(&mut self, _rate_name: &str) {
todo![]
}
/* rates */
/// Add new rate to the looper, with the specificied `duration` per tick,
/// and with optional `stats`.
///
/// If the `name` already exists, the previous rate will be returned,
/// and the new `rate` will take its place.
///
/// The rate's `name` must be a unique string with a maximum length of 21
/// ASCII alphanumeric + underscore characters ([A-Za-z0-9_]).
///
/// # Errors
/// Returns an error if the `name` is not valid.
///
/// # Examples
/// ```
/// use espera::all::{Looper, Rate};
///
/// let mut l = Looper::new();
/// assert![l.add_rate("ascii_name_max_length", Rate::with_tps(60.), false).is_ok()];
/// ```
#[inline(always)]
pub fn add_rate(&mut self, name: &str, rate: Rate, stats: bool) -> EsperaResult<Option<Rate>> {
let key = name.chars().encode_sixbit::<u128>()?;
if stats {
let _prev_stats = self.stats.insert(key, RateStats::new());
}
Ok(self.rates.insert(key, rate))
}
/// Returns a reference to the requested `name`d rate.
#[inline]
pub fn ref_rate(&self, name: &str) -> Option<&Rate> {
if let Ok(key) = name.chars().encode_sixbit::<u128>() {
self.rates.get(&key)
} else {
None
}
}
/// Returns an exclusive reference to the requested `name`d rate.
#[inline]
pub fn mut_rate(&mut self, name: &str) -> Option<&mut Rate> {
if let Ok(key) = name.chars().encode_sixbit::<u128>() {
self.rates.get_mut(&key)
} else {
None
}
}
// MAYBE TODO: set_rate?
/// Returns the duration of the fastest rate.
///
/// Returns `None` if there are no configured rates.
#[inline]
pub fn fastest_rate_duration(&mut self) -> Option<Duration> {
self.rates
.iter()
.min_by(|(_, a), (_, b)| a.duration().cmp(&b.duration()))
.map(|(_, r)| r.duration())
}
/// Returns a reference to the root rate.
#[inline]
pub fn ref_root_rate(&self) -> &Rate {
&self.root_rate
}
/// Returns an exclusive reference to the root rate.
#[inline]
pub fn mut_root_rate(&mut self) -> &mut Rate {
&mut self.root_rate
}
/* ticks */
/// Returns the duration between the last tick of the `name`d rate,
/// and the provided `instant`, as long as the duration is non-negative.
///
/// If the duration is non-negative, the ticks counter is also incremented
/// and the instant of the last tick is replaced with the given `instant`.
///
/// Returns `None` if the either the rate is not found in the rates list,
/// or if the time difference is negative.
///
/// # Precision
/// This version should give a much more precise average frame rate than
/// [`do_tick_fast`][Self::do_tick_fast], because it takes into account the
/// accumulated lag, at the cost of being a little less performant.
///
/// The maximum lag taken into account is ± 2.1 s (±[`i32::MAX`] ns).
pub fn do_tick(&mut self, instant: Instant, name: &str) -> Option<Duration> {
if let Ok(key) = name.chars().encode_sixbit::<u128>() {
if let Some(rate) = self.rates.get_mut(&key) {
if let Some(delta) = rate.do_tick(instant) {
// stats
if let Some(stats) = self.stats.get_mut(&key) {
let ns: u64 = delta.whole_nanoseconds() as u64;
stats.add_ns(ns);
stats.update(rate.ticks());
}
// log::trace![
// "{name:10}{rate} || Δ:{delta:.2}, TPS:{:.2}",
// 1.0 / delta.as_seconds_f64() ];
Some(delta)
} else {
None // not yet tick time
}
} else {
None // rate name not found
}
} else {
None // invalid rate name
}
}
/// Calls [`do_tick`][Self::do_tick] with `Instant::now()`.
#[inline(always)]
pub fn do_tick_now(&mut self, name: &str) -> Option<Duration> {
self.do_tick(Instant::now(), name)
}
/// Returns the duration between the last tick of the `name`d rate,
/// and the provided `instant`, as long as the duration is non-negative.
///
/// If the duration is non-negative, the ticks counter is also incremented
/// and the instant of the last tick is replaced with the given `instant`.
///
/// Returns `None` if the either the rate is not found in the rates list,
/// or if the time difference is negative.
///
/// # Precision
/// This version is less precise than [`do_tick`][Self::do_tick],
/// because it doesn't try to compensate accumulated lag. It will probably
/// lag a little behind the target rate, but should also be a little faster.
pub fn do_tick_fast(&mut self, instant: Instant, name: &str) -> Option<Duration> {
if let Ok(key) = name.chars().encode_sixbit::<u128>() {
if let Some(rate) = self.rates.get_mut(&key) {
if let Some(delta) = rate.do_tick_fast(instant) {
// stats
if let Some(stats) = self.stats.get_mut(&key) {
let ns: u64 = delta.whole_nanoseconds() as u64;
stats.add_ns(ns);
stats.update(rate.ticks());
}
// log::trace![
// "{name:10} {rate} || Δ:{delta:.2}, TPS:{:.2}",
// 1.0 / delta.as_seconds_f64() ];
Some(delta)
} else {
None // not yet tick time
}
} else {
None // rate name not found
}
} else {
None // invalid rate name
}
}
/// Calls [`do_tick_fast`][Self::do_tick_fast] with `Instant::now()`.
#[inline(always)]
pub fn do_tick_fast_now(&mut self, name: &str) -> Option<Duration> {
self.do_tick_fast(Instant::now(), name)
}
/* logging */
/// Logs the stats of the root rate.
#[inline]
pub fn log_root_rate(&self) {
// don't send
self.root_stats.log("ROOT", None);
}
/// Logs the stats of a given rate.
#[inline]
pub fn log_rate(&self, name: &str) {
if let Ok(key) = name.chars().encode_sixbit::<u128>() {
if let Some(stats) = self.stats.get(&key) {
let name = &key.decode_sixbit().collect::<String>();
let rate = self.rates.get(&key);
stats.log(name, rate);
}
}
}
/// Logs the stats of all rates.
#[inline]
pub fn log_all_rates(&self) {
self.log_root_rate();
for (key, _) in self.rates.iter() {
if let Some(stats) = self.stats.get(key) {
let name = &key.decode_sixbit().collect::<String>();
let rate = self.rates.get(key);
stats.log(name, rate);
}
}
}
/* sleep */
/// Request to sleep for the requested positive `duration`.
///
/// # State machine rules
/// On LoopStatus match:
/// + `Active`:
/// - sets status to `Sleep`
/// - sleeps for requested duration.
/// + `Sleep`:
/// - Returns `None`.
//
// IMPROVE: check minimum resolution?
pub fn sleep(&mut self, duration: Duration) {
if let LoopStatus::Active = self.status {
self.status = LoopStatus::Asleep;
if duration.is_positive() {
// log::debug!["sleep: {duration}"];
sleep(duration.unsigned_abs());
}
}
}
// MAYBE
// /// Sleeps enough time to stabilize as closest as possible to
// //
// // returns the delta
// pub fn sleep_target(&mut self, target: Duration) -> Option<Duration> {
// // let target
// // self.sleep(target)
// todo![];
// }
}