use std::time::{Duration, Instant};
pub const MIN_HZ: u32 = 30;
pub const MAX_HZ: u32 = 240;
pub const FALLBACK_ANIMATION_MS: u64 = 120;
pub const MIN_ANIMATION_HZ: u32 = 4;
pub const MAX_ANIMATION_HZ: u32 = 30;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DisplayRefreshSource {
None,
#[cfg_attr(not(target_os = "macos"), allow(dead_code))]
MacosCoreGraphics,
EnvOverride,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisplayRefreshProbeResult {
pub hz: Option<u32>,
pub source: DisplayRefreshSource,
pub skip_reason: &'static str,
pub duration_ms: u64,
}
impl DisplayRefreshProbeResult {
#[must_use]
pub fn outcome(self) -> &'static str {
if self.hz.is_some() {
"ok"
} else if self.skip_reason == "error" {
"error"
} else {
"skipped"
}
}
}
const _: fn(DisplayRefreshProbeResult) -> &'static str = DisplayRefreshProbeResult::outcome;
pub fn probe_display_refresh() -> DisplayRefreshProbeResult {
#[cfg(test)]
{
pinned_probe()
}
#[cfg(not(test))]
{
static CACHE: std::sync::OnceLock<DisplayRefreshProbeResult> = std::sync::OnceLock::new();
*CACHE.get_or_init(probe_uncached)
}
}
#[cfg(test)]
const UNMEASURED: DisplayRefreshProbeResult = DisplayRefreshProbeResult {
hz: None,
source: DisplayRefreshSource::None,
skip_reason: "not_probed_under_test",
duration_ms: 0,
};
#[cfg(test)]
thread_local! {
static PINNED: std::cell::Cell<Option<DisplayRefreshProbeResult>> =
const { std::cell::Cell::new(None) };
}
#[cfg(test)]
fn pinned_probe() -> DisplayRefreshProbeResult {
PINNED.with(|pinned| pinned.get().unwrap_or(UNMEASURED))
}
#[cfg(test)]
pub(crate) struct DisplayRefreshPin {
previous: Option<DisplayRefreshProbeResult>,
}
#[cfg(test)]
impl DisplayRefreshPin {
pub(crate) fn measured(hz: u32) -> Self {
let accepted = accept_hz(hz);
Self::install(DisplayRefreshProbeResult {
hz: accepted,
source: DisplayRefreshSource::EnvOverride,
skip_reason: if accepted.is_some() {
""
} else {
"out_of_range"
},
duration_ms: 0,
})
}
fn install(next: DisplayRefreshProbeResult) -> Self {
let previous = PINNED.with(|pinned| pinned.replace(Some(next)));
Self { previous }
}
}
#[cfg(test)]
impl Drop for DisplayRefreshPin {
fn drop(&mut self) {
PINNED.with(|pinned| pinned.set(self.previous));
}
}
fn probe_uncached() -> DisplayRefreshProbeResult {
let start = Instant::now();
let (hz, source, skip_reason) = probe_inner();
DisplayRefreshProbeResult {
hz,
source,
skip_reason,
duration_ms: start.elapsed().as_millis() as u64,
}
}
fn probe_inner() -> (Option<u32>, DisplayRefreshSource, &'static str) {
if let Some(hz) = env_override_hz() {
return match accept_hz(hz) {
Some(hz) => (Some(hz), DisplayRefreshSource::EnvOverride, ""),
None => (None, DisplayRefreshSource::EnvOverride, "out_of_range"),
};
}
if is_remote_session() {
return (None, DisplayRefreshSource::None, "ssh");
}
#[cfg(target_os = "macos")]
{
match probe_macos() {
Ok(hz) => match accept_hz(hz) {
Some(hz) => (Some(hz), DisplayRefreshSource::MacosCoreGraphics, ""),
None => (
None,
DisplayRefreshSource::MacosCoreGraphics,
"out_of_range",
),
},
Err(reason) => (None, DisplayRefreshSource::MacosCoreGraphics, reason),
}
}
#[cfg(not(target_os = "macos"))]
{
(None, DisplayRefreshSource::None, "unsupported")
}
}
fn env_override_hz() -> Option<u32> {
let raw = std::env::var("CODEWHALE_DISPLAY_HZ").ok()?;
raw.trim().parse().ok()
}
fn is_remote_session() -> bool {
std::env::var_os("SSH_CONNECTION").is_some()
|| std::env::var_os("SSH_CLIENT").is_some()
|| std::env::var_os("SSH_TTY").is_some()
}
fn accept_hz(hz: u32) -> Option<u32> {
if (MIN_HZ..=MAX_HZ).contains(&hz) {
Some(hz)
} else {
None
}
}
#[cfg(target_os = "macos")]
fn probe_macos() -> Result<u32, &'static str> {
unsafe extern "C" {
fn CGMainDisplayID() -> u32;
fn CGDisplayCopyDisplayMode(display: u32) -> *mut std::ffi::c_void;
fn CGDisplayModeGetRefreshRate(mode: *mut std::ffi::c_void) -> f64;
fn CGDisplayModeRelease(mode: *mut std::ffi::c_void);
}
unsafe {
let display = CGMainDisplayID();
let mode = CGDisplayCopyDisplayMode(display);
if mode.is_null() {
return Err("no_mode");
}
let rate = CGDisplayModeGetRefreshRate(mode);
CGDisplayModeRelease(mode);
if !rate.is_finite() || rate <= 0.0 {
return Err("zero_rate");
}
let hz = rate.round() as u32;
if hz == 0 {
return Err("zero_rate");
}
Ok(hz)
}
}
#[must_use]
pub fn animation_interval_for_hz(display_hz: Option<u32>, low_motion: bool) -> Duration {
if low_motion {
return Duration::from_millis(2_400);
}
match display_hz {
None | Some(0..=60) => Duration::from_millis(FALLBACK_ANIMATION_MS),
Some(hz) => {
let target = (hz / 8).clamp(MIN_ANIMATION_HZ, MAX_ANIMATION_HZ);
let ms = (1000u32 / target.max(1)).max(1);
Duration::from_millis(u64::from(ms).min(FALLBACK_ANIMATION_MS))
}
}
}
#[must_use]
pub fn adaptive_animation_interval_ms(low_motion: bool) -> u64 {
let probe = probe_display_refresh();
animation_interval_for_hz(probe.hz, low_motion).as_millis() as u64
}
#[must_use]
pub fn draw_min_interval_for_hz(display_hz: Option<u32>, low_motion: bool) -> Duration {
use super::frame_rate_limiter::{LOW_MOTION_MIN_FRAME_INTERVAL, MIN_FRAME_INTERVAL};
if low_motion {
return LOW_MOTION_MIN_FRAME_INTERVAL;
}
let Some(hz) = display_hz else {
return MIN_FRAME_INTERVAL;
};
let capped = hz.clamp(30, 120);
let nanos = 1_000_000_000u64 / u64::from(capped);
Duration::from_nanos(nanos).max(MIN_FRAME_INTERVAL)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DrawCadenceTier {
Atmosphere,
Interactive,
}
#[must_use]
pub fn content_driven_draw_interval(
tier: DrawCadenceTier,
display_hz: Option<u32>,
low_motion: bool,
) -> Duration {
match tier {
DrawCadenceTier::Atmosphere => animation_interval_for_hz(display_hz, low_motion),
DrawCadenceTier::Interactive => draw_min_interval_for_hz(display_hz, low_motion),
}
}
#[must_use]
pub fn cadence_tier_from_signals(
streaming_or_loading: bool,
selection_active: bool,
input_nonempty: bool,
pointer_hover_active: bool,
) -> DrawCadenceTier {
if streaming_or_loading || selection_active || input_nonempty || pointer_hover_active {
DrawCadenceTier::Interactive
} else {
DrawCadenceTier::Atmosphere
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tui::frame_rate_limiter::{LOW_MOTION_MIN_FRAME_INTERVAL, MIN_FRAME_INTERVAL};
#[test]
fn falls_back_to_default_when_probe_has_no_hz() {
let interval = animation_interval_for_hz(None, false);
assert_eq!(interval, Duration::from_millis(FALLBACK_ANIMATION_MS));
assert_eq!(FALLBACK_ANIMATION_MS, 120);
}
#[test]
fn low_motion_wins_over_measured_hz() {
let interval = animation_interval_for_hz(Some(144), true);
assert_eq!(interval, Duration::from_millis(2_400));
}
#[test]
fn high_hz_display_raises_cadence_but_stays_bounded() {
let interval = animation_interval_for_hz(Some(144), false);
assert!(interval >= Duration::from_millis(33));
assert!(interval <= Duration::from_millis(250));
}
#[test]
fn sixty_hz_keeps_historical_atmosphere_cadence() {
let interval = animation_interval_for_hz(Some(60), false);
assert_eq!(interval, Duration::from_millis(FALLBACK_ANIMATION_MS));
}
#[test]
fn accept_hz_rejects_out_of_range() {
assert_eq!(accept_hz(10), None);
assert_eq!(accept_hz(60), Some(60));
assert_eq!(accept_hz(500), None);
}
#[test]
fn draw_cap_never_exceeds_historical_min_interval() {
let interval = draw_min_interval_for_hz(Some(240), false);
assert!(interval >= MIN_FRAME_INTERVAL);
}
#[test]
fn draw_cap_respects_low_motion() {
assert_eq!(
draw_min_interval_for_hz(Some(144), true),
LOW_MOTION_MIN_FRAME_INTERVAL
);
}
#[test]
fn probe_is_infallible_and_cached() {
let a = probe_display_refresh();
let b = probe_display_refresh();
assert_eq!(a, b);
assert!(a.outcome() == "ok" || a.outcome() == "skipped" || a.outcome() == "error");
}
#[test]
fn the_host_probe_itself_stays_infallible_and_in_range() {
let probe = probe_uncached();
assert!(
probe.outcome() == "ok" || probe.outcome() == "skipped" || probe.outcome() == "error"
);
if let Some(hz) = probe.hz {
assert!(
(MIN_HZ..=MAX_HZ).contains(&hz),
"{hz} outside accepted range"
);
assert!(probe.skip_reason.is_empty());
} else {
assert!(!probe.skip_reason.is_empty(), "a skip must name its reason");
}
}
#[test]
fn unpinned_tests_never_see_the_host_panel() {
let probe = probe_display_refresh();
assert_eq!(probe.hz, None, "a test must not inherit the developer's Hz");
assert_eq!(probe.skip_reason, "not_probed_under_test");
assert_eq!(
adaptive_animation_interval_ms(false),
FALLBACK_ANIMATION_MS,
"the unmeasured fallback is what CI computes"
);
}
#[test]
fn a_pinned_panel_drives_the_cadence_and_is_restored_on_drop() {
assert_eq!(probe_display_refresh().hz, None);
{
let _pin = DisplayRefreshPin::measured(144);
assert_eq!(probe_display_refresh().hz, Some(144));
assert_eq!(
adaptive_animation_interval_ms(false),
animation_interval_for_hz(Some(144), false).as_millis() as u64
);
assert!(adaptive_animation_interval_ms(false) < FALLBACK_ANIMATION_MS);
}
assert_eq!(
probe_display_refresh().hz,
None,
"the pin must not outlive its scope"
);
}
#[test]
fn a_pin_outside_the_accepted_range_reads_as_unmeasured() {
let _pin = DisplayRefreshPin::measured(1);
let probe = probe_display_refresh();
assert_eq!(probe.hz, None);
assert_eq!(probe.skip_reason, "out_of_range");
assert_eq!(adaptive_animation_interval_ms(false), FALLBACK_ANIMATION_MS);
}
}