use std::sync::Mutex;
pub const UNCALIBRATED_MS_PER_UNIT_PIXEL: f64 = 2.0e-8;
#[derive(Debug, Default)]
struct RateState {
ms_per_unit_pixel: Option<f64>,
calibrated: Option<f64>,
calibrating: bool,
}
#[derive(Debug, Default)]
pub struct GpuRate {
state: Mutex<RateState>,
}
impl GpuRate {
fn state(&self) -> std::sync::MutexGuard<'_, RateState> {
self.state
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
#[must_use]
pub fn ms_per_unit_pixel(&self) -> Option<f64> {
self.state().ms_per_unit_pixel
}
pub fn begin_calibration(&self) -> bool {
let mut state = self.state();
if state.ms_per_unit_pixel.is_some() || state.calibrating {
return false;
}
state.calibrating = true;
true
}
pub fn seed(&self, measured_ms_per_unit_pixel: f64) {
let mut state = self.state();
state.calibrating = false;
if !(measured_ms_per_unit_pixel.is_finite() && measured_ms_per_unit_pixel > 0.0) {
return;
}
state.calibrated = Some(measured_ms_per_unit_pixel);
let seeded = 2.0 * measured_ms_per_unit_pixel;
state.ms_per_unit_pixel = Some(
state
.ms_per_unit_pixel
.map_or(seeded, |rate| rate.max(seeded)),
);
}
pub fn abandon_calibration(&self) {
self.state().calibrating = false;
}
pub fn observe(&self, measured_ms: f64, unit_pixels: f64) {
if !(measured_ms.is_finite() && unit_pixels.is_finite() && unit_pixels > 0.0) {
return;
}
let measured = measured_ms / unit_pixels;
let mut state = self.state();
let (Some(rate), Some(floor)) = (state.ms_per_unit_pixel, state.calibrated) else {
return;
};
if measured < rate {
state.ms_per_unit_pixel = Some((rate * 0.9).max(measured).max(floor));
}
}
}
#[cfg(test)]
mod tests {
use super::GpuRate;
#[test]
fn the_rate_falls_slowly_to_the_calibration_and_never_rises_on_a_measurement() {
let rate = GpuRate::default();
assert_eq!(rate.ms_per_unit_pixel(), None);
rate.observe(10.0, 1.0);
assert_eq!(
rate.ms_per_unit_pixel(),
None,
"nothing to fold into before a seed"
);
assert!(rate.begin_calibration());
assert!(!rate.begin_calibration(), "one calibration at a time");
rate.seed(1.0);
assert_eq!(rate.ms_per_unit_pixel(), Some(2.0));
rate.observe(8.0, 2.0);
assert_eq!(rate.ms_per_unit_pixel(), Some(2.0));
rate.observe(0.0, 2.0);
assert_eq!(rate.ms_per_unit_pixel(), Some(1.8));
for _ in 0..100 {
rate.observe(0.0, 2.0);
}
assert_eq!(rate.ms_per_unit_pixel(), Some(1.0));
assert!(!rate.begin_calibration());
}
}