Skip to main content

cranpose_app_shell/
frame_rate_boost.rs

1use std::time::Duration;
2
3use web_time::Instant;
4
5use crate::FrameUpdateResult;
6
7/// How long input, moving content or a continuous redraw keeps the fast rate.
8const BOOST_HOLD_OFF: Duration = Duration::from_secs(3);
9
10/// Android's `ViewRootImpl` counts a window as redrawn continuously while its
11/// last two frame intervals add up to less than this.
12const CONTINUOUS_REDRAW_SPAN: Duration = Duration::from_millis(100);
13
14/// When [`FrameRatePreference::Auto`](crate::FrameRatePreference::Auto) asks
15/// for the display's fastest rate, following how Android's views vote a
16/// frame-rate category: input, content that moves, and a large view redrawn
17/// continuously all vote high. A Cranpose window is one screen-sized view, as
18/// a Compose window is, so continuous redraws boost it and intermittent ones,
19/// such as a blinking cursor, do not. A boost holds for a few seconds, so the
20/// display does not flap between rates.
21#[derive(Clone, Copy, Debug, Default)]
22pub struct FrameRateBoost {
23    boosted_at: Option<Instant>,
24    redraws: [Option<Instant>; 2],
25}
26
27impl FrameRateBoost {
28    /// Records input that arrived at `now`.
29    pub fn note_input(&mut self, now: Instant) {
30        self.boosted_at = Some(now);
31    }
32
33    /// Records the frame an update produced at `now`.
34    pub fn note_frame(&mut self, result: FrameUpdateResult, now: Instant) {
35        if result.content_moved {
36            self.boosted_at = Some(now);
37        }
38        if result.content_redrawn {
39            let [older, newer] = self.redraws;
40            if older.is_some_and(|at| now.saturating_duration_since(at) < CONTINUOUS_REDRAW_SPAN) {
41                self.boosted_at = Some(now);
42            }
43            self.redraws = [newer, Some(now)];
44        }
45    }
46
47    /// Whether the fast rate still holds at `now`.
48    pub fn boosted(&self, now: Instant) -> bool {
49        self.boosted_at
50            .is_some_and(|at| now.saturating_duration_since(at) < BOOST_HOLD_OFF)
51    }
52}
53
54#[cfg(test)]
55#[path = "tests/frame_rate_boost_tests.rs"]
56mod tests;