1use std::sync::Mutex;
13
14pub const UNCALIBRATED_MS_PER_UNIT_PIXEL: f64 = 2.0e-8;
18
19#[derive(Debug, Default)]
20struct RateState {
21 ms_per_unit_pixel: Option<f64>,
22 calibrated: Option<f64>,
24 calibrating: bool,
25}
26
27#[derive(Debug, Default)]
29pub struct GpuRate {
30 state: Mutex<RateState>,
31}
32
33impl GpuRate {
34 fn state(&self) -> std::sync::MutexGuard<'_, RateState> {
35 self.state
36 .lock()
37 .unwrap_or_else(std::sync::PoisonError::into_inner)
38 }
39
40 #[must_use]
42 pub fn ms_per_unit_pixel(&self) -> Option<f64> {
43 self.state().ms_per_unit_pixel
44 }
45
46 pub fn begin_calibration(&self) -> bool {
48 let mut state = self.state();
49 if state.ms_per_unit_pixel.is_some() || state.calibrating {
50 return false;
51 }
52 state.calibrating = true;
53 true
54 }
55
56 pub fn seed(&self, measured_ms_per_unit_pixel: f64) {
58 let mut state = self.state();
59 state.calibrating = false;
60 if !(measured_ms_per_unit_pixel.is_finite() && measured_ms_per_unit_pixel > 0.0) {
61 return;
62 }
63 state.calibrated = Some(measured_ms_per_unit_pixel);
64 let seeded = 2.0 * measured_ms_per_unit_pixel;
65 state.ms_per_unit_pixel = Some(
66 state
67 .ms_per_unit_pixel
68 .map_or(seeded, |rate| rate.max(seeded)),
69 );
70 }
71
72 pub fn abandon_calibration(&self) {
74 self.state().calibrating = false;
75 }
76
77 pub fn observe(&self, measured_ms: f64, unit_pixels: f64) {
80 if !(measured_ms.is_finite() && unit_pixels.is_finite() && unit_pixels > 0.0) {
81 return;
82 }
83 let measured = measured_ms / unit_pixels;
84 let mut state = self.state();
85 let (Some(rate), Some(floor)) = (state.ms_per_unit_pixel, state.calibrated) else {
86 return;
87 };
88 if measured < rate {
89 state.ms_per_unit_pixel = Some((rate * 0.9).max(measured).max(floor));
90 }
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::GpuRate;
97
98 #[test]
99 fn the_rate_falls_slowly_to_the_calibration_and_never_rises_on_a_measurement() {
100 let rate = GpuRate::default();
101 assert_eq!(rate.ms_per_unit_pixel(), None);
102 rate.observe(10.0, 1.0);
103 assert_eq!(
104 rate.ms_per_unit_pixel(),
105 None,
106 "nothing to fold into before a seed"
107 );
108 assert!(rate.begin_calibration());
109 assert!(!rate.begin_calibration(), "one calibration at a time");
110 rate.seed(1.0);
111 assert_eq!(rate.ms_per_unit_pixel(), Some(2.0));
112 rate.observe(8.0, 2.0);
114 assert_eq!(rate.ms_per_unit_pixel(), Some(2.0));
115 rate.observe(0.0, 2.0);
116 assert_eq!(rate.ms_per_unit_pixel(), Some(1.8));
117 for _ in 0..100 {
118 rate.observe(0.0, 2.0);
119 }
120 assert_eq!(rate.ms_per_unit_pixel(), Some(1.0));
121 assert!(!rate.begin_calibration());
122 }
123}