perf(app): settle web poll cadence after load

This commit is contained in:
2026-05-16 04:13:20 -04:00
parent 99d54a7089
commit 168c60bf2a
+56 -16
View File
@@ -10,7 +10,9 @@ const FRAME_SETTLE_WINDOW: Duration = Duration::from_millis(250);
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
pub(super) struct WebSurfacePollCadence { pub(super) struct WebSurfacePollCadence {
next_poll_at: Option<Instant>, next_poll_at: Option<Instant>,
active_until: Option<Instant>, load_active_until: Option<Instant>,
interaction_active_until: Option<Instant>,
settle_active_until: Option<Instant>,
last_render_phase: Option<WebSurfaceRenderPhase>, last_render_phase: Option<WebSurfaceRenderPhase>,
} }
@@ -23,15 +25,18 @@ impl WebSurfacePollCadence {
) { ) {
if started_loading { if started_loading {
self.last_render_phase = None; self.last_render_phase = None;
self.boost_until(now + LOAD_BOOST_WINDOW); self.settle_active_until = None;
extend_deadline(&mut self.load_active_until, now + LOAD_BOOST_WINDOW);
} }
match input_kind { match input_kind {
WebSurfaceInputKind::Idle => {} WebSurfaceInputKind::Idle => {}
WebSurfaceInputKind::Hover => self.boost_until(now + HOVER_BOOST_WINDOW), WebSurfaceInputKind::Hover => {
extend_deadline(&mut self.interaction_active_until, now + HOVER_BOOST_WINDOW);
}
WebSurfaceInputKind::Scroll WebSurfaceInputKind::Scroll
| WebSurfaceInputKind::Click | WebSurfaceInputKind::Click
| WebSurfaceInputKind::Text => { | WebSurfaceInputKind::Text => {
self.boost_until(now + INPUT_BOOST_WINDOW); extend_deadline(&mut self.interaction_active_until, now + INPUT_BOOST_WINDOW);
} }
} }
} }
@@ -42,15 +47,17 @@ impl WebSurfacePollCadence {
WebSurfaceRenderPhase::Created | WebSurfaceRenderPhase::Loading WebSurfaceRenderPhase::Created | WebSurfaceRenderPhase::Loading
if self.last_render_phase != Some(phase) => if self.last_render_phase != Some(phase) =>
{ {
self.boost_until(now + LOAD_BOOST_WINDOW); extend_deadline(&mut self.load_active_until, now + LOAD_BOOST_WINDOW);
} }
WebSurfaceRenderPhase::Created | WebSurfaceRenderPhase::Loading => {} WebSurfaceRenderPhase::Created | WebSurfaceRenderPhase::Loading => {}
WebSurfaceRenderPhase::Complete if self.last_render_phase != Some(phase) => { WebSurfaceRenderPhase::Complete if self.last_render_phase != Some(phase) => {
self.boost_until(now + FRAME_SETTLE_WINDOW); self.load_active_until = None;
extend_deadline(&mut self.settle_active_until, now + FRAME_SETTLE_WINDOW);
} }
WebSurfaceRenderPhase::Complete => {} WebSurfaceRenderPhase::Complete => {}
WebSurfaceRenderPhase::Other if self.last_render_phase != Some(phase) => { WebSurfaceRenderPhase::Other if self.last_render_phase != Some(phase) => {
self.boost_until(now + INPUT_BOOST_WINDOW); self.load_active_until = None;
extend_deadline(&mut self.interaction_active_until, now + INPUT_BOOST_WINDOW);
} }
WebSurfaceRenderPhase::Other => {} WebSurfaceRenderPhase::Other => {}
} }
@@ -70,17 +77,20 @@ impl WebSurfacePollCadence {
} }
fn current_interval(&self, now: Instant) -> Duration { fn current_interval(&self, now: Instant) -> Duration {
if self.active_until.is_some_and(|deadline| now < deadline) { if self.has_active_window(now) { ACTIVE_POLL_INTERVAL } else { IDLE_POLL_INTERVAL }
ACTIVE_POLL_INTERVAL
} else {
IDLE_POLL_INTERVAL
}
} }
fn boost_until(&mut self, deadline: Instant) { fn has_active_window(&self, now: Instant) -> bool {
if self.active_until.is_none_or(|current| deadline > current) { [self.load_active_until, self.interaction_active_until, self.settle_active_until]
self.active_until = Some(deadline); .into_iter()
} .flatten()
.any(|deadline| now < deadline)
}
}
fn extend_deadline(slot: &mut Option<Instant>, deadline: Instant) {
if slot.is_none_or(|current| deadline > current) {
*slot = Some(deadline);
} }
} }
@@ -205,6 +215,36 @@ mod tests {
assert!(cadence.should_poll(start + Duration::from_millis(380))); assert!(cadence.should_poll(start + Duration::from_millis(380)));
} }
#[test]
fn complete_frame_shortens_started_loading_boost() {
let start = Instant::now();
let mut cadence = WebSurfacePollCadence::default();
cadence.note_ensure(WebSurfaceInputKind::Idle, true, start);
cadence.note_frame("complete", start + Duration::from_secs(1));
cadence.note_poll_submitted(start + Duration::from_millis(1_300));
assert_eq!(
cadence.next_poll_delay(start + Duration::from_millis(1_300)),
IDLE_POLL_INTERVAL
);
}
#[test]
fn complete_frame_preserves_recent_input_boost() {
let start = Instant::now();
let mut cadence = WebSurfacePollCadence::default();
cadence.note_ensure(WebSurfaceInputKind::Scroll, false, start);
cadence.note_frame("complete", start + Duration::from_millis(100));
cadence.note_poll_submitted(start + Duration::from_millis(500));
assert_eq!(
cadence.next_poll_delay(start + Duration::from_millis(500)),
ACTIVE_POLL_INTERVAL
);
}
#[test] #[test]
fn repeated_complete_frames_do_not_extend_settle_window() { fn repeated_complete_frames_do_not_extend_settle_window() {
let start = Instant::now(); let start = Instant::now();