diff --git a/crates/ely_app/src/shell/web_surface_cadence.rs b/crates/ely_app/src/shell/web_surface_cadence.rs index a9cc1fc..33ff0a9 100644 --- a/crates/ely_app/src/shell/web_surface_cadence.rs +++ b/crates/ely_app/src/shell/web_surface_cadence.rs @@ -10,7 +10,9 @@ const FRAME_SETTLE_WINDOW: Duration = Duration::from_millis(250); #[derive(Clone, Debug, Default)] pub(super) struct WebSurfacePollCadence { next_poll_at: Option, - active_until: Option, + load_active_until: Option, + interaction_active_until: Option, + settle_active_until: Option, last_render_phase: Option, } @@ -23,15 +25,18 @@ impl WebSurfacePollCadence { ) { if started_loading { 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 { 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::Click | 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 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::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::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 => {} } @@ -70,17 +77,20 @@ impl WebSurfacePollCadence { } fn current_interval(&self, now: Instant) -> Duration { - if self.active_until.is_some_and(|deadline| now < deadline) { - ACTIVE_POLL_INTERVAL - } else { - IDLE_POLL_INTERVAL - } + if self.has_active_window(now) { ACTIVE_POLL_INTERVAL } else { IDLE_POLL_INTERVAL } } - fn boost_until(&mut self, deadline: Instant) { - if self.active_until.is_none_or(|current| deadline > current) { - self.active_until = Some(deadline); - } + fn has_active_window(&self, now: Instant) -> bool { + [self.load_active_until, self.interaction_active_until, self.settle_active_until] + .into_iter() + .flatten() + .any(|deadline| now < deadline) + } +} + +fn extend_deadline(slot: &mut Option, 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))); } + #[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] fn repeated_complete_frames_do_not_extend_settle_window() { let start = Instant::now();