perf(web-surface): adapt shell tick cadence
This commit is contained in:
@@ -42,8 +42,6 @@ mod web_surface_worker;
|
||||
#[cfg(test)]
|
||||
mod gpui_harness_tests;
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use ely_browser_core::{BrowserCore, InitialBrowserConfig};
|
||||
use ely_domain::{DEFAULT_TRANSLUCENCY_PCT, ProfileId, SpaceId, TabId};
|
||||
use gpui::{AppContext, Context, Entity, FocusHandle, Subscription, Timer, Window};
|
||||
@@ -459,14 +457,12 @@ impl ElyShell {
|
||||
|
||||
fn start_external_web_surface_timer(cx: &mut Context<ElyShell>) {
|
||||
cx.spawn(async move |shell, cx| {
|
||||
// 8 ms ≈ 125 Hz, matched to a 120 Hz display's frame budget.
|
||||
// The Servo worker thread does the blocking IPC, so this tick
|
||||
// only enqueues Poll requests and drains the response channel
|
||||
// — cheap enough to run twice as often as the previous 60 Hz
|
||||
// schedule and lets active pages feed the renderer a fresh
|
||||
// frame between every display refresh.
|
||||
loop {
|
||||
Timer::after(Duration::from_millis(8)).await;
|
||||
let delay = match shell.update(cx, |shell, _| shell.external_web_surface_tick_delay()) {
|
||||
Ok(delay) => delay,
|
||||
Err(_) => break,
|
||||
};
|
||||
Timer::after(delay).await;
|
||||
let result = shell.update(cx, |shell, cx| {
|
||||
if shell.tick_external_web_surfaces() {
|
||||
cx.notify();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::{collections::BTreeMap, time::Instant};
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use ely_domain::{BrowserTab, TabId};
|
||||
use gpui::{Bounds, Pixels, Point};
|
||||
@@ -7,6 +8,7 @@ use crate::services::ProfileDataMode;
|
||||
|
||||
use super::web_surface_metadata::WebSurfacePageMetadata;
|
||||
use super::{
|
||||
web_surface_cadence::IDLE_POLL_INTERVAL,
|
||||
web_surface_frame::WebSurfaceFrame,
|
||||
web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollDelta, WebSurfaceSize},
|
||||
web_surface_permissions::WebSurfaceSitePermission,
|
||||
@@ -149,6 +151,13 @@ impl WebSurfaceStore {
|
||||
result
|
||||
}
|
||||
|
||||
pub(super) fn next_tick_delay(&self, visible_tab_ids: &[TabId]) -> Duration {
|
||||
self.runtime
|
||||
.next_poll_delay(visible_tab_ids, Instant::now())
|
||||
.unwrap_or(IDLE_POLL_INTERVAL)
|
||||
.min(IDLE_POLL_INTERVAL)
|
||||
}
|
||||
|
||||
pub(super) fn retain_tabs(&mut self, open_tab_ids: &[TabId]) {
|
||||
let stale_tab_ids = self
|
||||
.surfaces
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
const ACTIVE_POLL_INTERVAL: Duration = Duration::from_millis(8);
|
||||
const IDLE_POLL_INTERVAL: Duration = Duration::from_millis(80);
|
||||
pub(super) const ACTIVE_POLL_INTERVAL: Duration = Duration::from_millis(8);
|
||||
pub(super) const IDLE_POLL_INTERVAL: Duration = Duration::from_millis(80);
|
||||
const LOAD_BOOST_WINDOW: Duration = Duration::from_secs(5);
|
||||
const INPUT_BOOST_WINDOW: Duration = Duration::from_millis(600);
|
||||
const HOVER_BOOST_WINDOW: Duration = Duration::from_millis(120);
|
||||
@@ -57,6 +57,10 @@ impl WebSurfacePollCadence {
|
||||
self.next_poll_at.is_none_or(|next| now >= next)
|
||||
}
|
||||
|
||||
pub(super) fn next_poll_delay(&self, now: Instant) -> Duration {
|
||||
self.next_poll_at.map_or(Duration::ZERO, |next| next.saturating_duration_since(now))
|
||||
}
|
||||
|
||||
pub(super) fn note_poll_submitted(&mut self, now: Instant) {
|
||||
self.next_poll_at = Some(now + self.current_interval(now));
|
||||
}
|
||||
@@ -120,7 +124,9 @@ impl WebSurfaceInputKind {
|
||||
mod tests {
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use super::{WebSurfaceInputKind, WebSurfacePollCadence};
|
||||
use super::{
|
||||
ACTIVE_POLL_INTERVAL, IDLE_POLL_INTERVAL, WebSurfaceInputKind, WebSurfacePollCadence,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn idle_poll_uses_low_frequency_after_submission() {
|
||||
@@ -129,6 +135,7 @@ mod tests {
|
||||
|
||||
cadence.note_poll_submitted(start);
|
||||
|
||||
assert_eq!(cadence.next_poll_delay(start), IDLE_POLL_INTERVAL);
|
||||
assert!(!cadence.should_poll(start + Duration::from_millis(79)));
|
||||
assert!(cadence.should_poll(start + Duration::from_millis(80)));
|
||||
}
|
||||
@@ -141,6 +148,7 @@ mod tests {
|
||||
cadence.note_ensure(WebSurfaceInputKind::Scroll, false, start);
|
||||
cadence.note_poll_submitted(start);
|
||||
|
||||
assert_eq!(cadence.next_poll_delay(start), ACTIVE_POLL_INTERVAL);
|
||||
assert!(!cadence.should_poll(start + Duration::from_millis(7)));
|
||||
assert!(cadence.should_poll(start + Duration::from_millis(8)));
|
||||
}
|
||||
|
||||
@@ -69,14 +69,34 @@ impl ElyShell {
|
||||
result.changed || url_changed || metadata_changed || sync_changed
|
||||
}
|
||||
|
||||
pub(super) fn external_web_surface_tick_delay(&self) -> std::time::Duration {
|
||||
let visible_tab_ids = match &self.state {
|
||||
super::ShellState::Ready(core) => {
|
||||
core.visible_content_tab_ids().unwrap_or_else(|_| Vec::new())
|
||||
}
|
||||
super::ShellState::StartupError(_) => Vec::new(),
|
||||
};
|
||||
self.web_surfaces.next_tick_delay(&visible_tab_ids)
|
||||
}
|
||||
|
||||
fn flush_external_web_surface_tick(&mut self, cx: &mut Context<Self>) {
|
||||
if self.tick_external_web_surfaces() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn record_external_web_viewport(
|
||||
&mut self,
|
||||
tab_id: TabId,
|
||||
bounds: Bounds<Pixels>,
|
||||
scale_factor: f32,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let _ = self.web_surfaces.record_viewport_size(&tab_id, bounds, scale_factor);
|
||||
if self.web_surfaces.record_viewport_size(&tab_id, bounds, scale_factor)
|
||||
== WebSurfaceInputOutcome::Applied
|
||||
{
|
||||
self.flush_external_web_surface_tick(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn scroll_external_web_viewport(
|
||||
@@ -86,15 +106,18 @@ impl ElyShell {
|
||||
delta: Point<Pixels>,
|
||||
position: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let _ = self.web_surfaces.record_scroll_delta(
|
||||
if self.web_surfaces.record_scroll_delta(
|
||||
&tab_id,
|
||||
requested_url.as_str(),
|
||||
delta,
|
||||
position,
|
||||
scale_factor,
|
||||
);
|
||||
) == WebSurfaceInputOutcome::Applied
|
||||
{
|
||||
self.flush_external_web_surface_tick(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn hover_external_web_viewport(
|
||||
@@ -102,9 +125,13 @@ impl ElyShell {
|
||||
tab_id: TabId,
|
||||
position: Point<Pixels>,
|
||||
scale_factor: f32,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let _ = self.web_surfaces.record_hover_point(&tab_id, position, scale_factor);
|
||||
if self.web_surfaces.record_hover_point(&tab_id, position, scale_factor)
|
||||
== WebSurfaceInputOutcome::Applied
|
||||
{
|
||||
self.flush_external_web_surface_tick(cx);
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn click_external_web_viewport(
|
||||
@@ -113,16 +140,19 @@ impl ElyShell {
|
||||
requested_url: String,
|
||||
position: Point<Pixels>,
|
||||
window: &mut gpui::Window,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
self.focus_handle.focus(window);
|
||||
let scale_factor = window.scale_factor();
|
||||
let _ = self.web_surfaces.record_click_point(
|
||||
if self.web_surfaces.record_click_point(
|
||||
&tab_id,
|
||||
requested_url.as_str(),
|
||||
position,
|
||||
scale_factor,
|
||||
);
|
||||
) == WebSurfaceInputOutcome::Applied
|
||||
{
|
||||
self.flush_external_web_surface_tick(cx);
|
||||
}
|
||||
}
|
||||
|
||||
/// Hand focus to the shell's root focus handle so subsequent
|
||||
@@ -138,11 +168,12 @@ impl ElyShell {
|
||||
tab_id: TabId,
|
||||
requested_url: String,
|
||||
text: &str,
|
||||
_cx: &mut Context<Self>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> bool {
|
||||
if self.web_surfaces.record_typed_text(&tab_id, requested_url.as_str(), text)
|
||||
== WebSurfaceInputOutcome::Applied
|
||||
{
|
||||
self.flush_external_web_surface_tick(cx);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
use std::{collections::BTreeMap, fs, path::PathBuf, time::Instant};
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
fs,
|
||||
path::PathBuf,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ely_domain::{BrowserTab, ProfileId, TabId};
|
||||
|
||||
@@ -177,14 +182,26 @@ impl WebSurfaceRuntime {
|
||||
let Some(scoped) = self.workers.get(&session.scope) else {
|
||||
continue;
|
||||
};
|
||||
if scoped.worker.submit_poll(tab_id.as_str().to_string()) {
|
||||
session.cadence.note_poll_submitted(poll_now);
|
||||
}
|
||||
let _ = scoped.worker.submit_poll(tab_id.as_str().to_string());
|
||||
session.cadence.note_poll_submitted(poll_now);
|
||||
}
|
||||
|
||||
frames
|
||||
}
|
||||
|
||||
pub(super) fn next_poll_delay(
|
||||
&self,
|
||||
visible_tab_ids: &[TabId],
|
||||
now: Instant,
|
||||
) -> Option<Duration> {
|
||||
visible_tab_ids
|
||||
.iter()
|
||||
.filter_map(|tab_id| self.sessions.get(tab_id))
|
||||
.filter(|session| self.workers.contains_key(&session.scope))
|
||||
.map(|session| session.cadence.next_poll_delay(now))
|
||||
.min()
|
||||
}
|
||||
|
||||
pub(super) fn close_tab(&mut self, tab_id: &TabId) {
|
||||
let Some(session) = self.sessions.remove(tab_id) else {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
collections::BTreeMap,
|
||||
sync::atomic::{AtomicUsize, Ordering},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use ely_domain::{BrowserTab, ProfileId, SpaceId, TabId, UrlText};
|
||||
@@ -9,6 +10,7 @@ use crate::{
|
||||
services::ProfileDataMode,
|
||||
shell::{
|
||||
WebSurfaceStore,
|
||||
web_surface_cadence::{ACTIVE_POLL_INTERVAL, IDLE_POLL_INTERVAL},
|
||||
web_surface_geometry::{WebSurfaceScrollOffset, WebSurfaceSize},
|
||||
web_surface_state::{WebSurfaceInputOutcome, WebSurfacePendingInput},
|
||||
web_surface_worker::{LiveRuntimeClient, LiveRuntimeClientError},
|
||||
@@ -111,6 +113,30 @@ fn unchanged_surface_without_input_skips_runtime_ensure() -> Result<(), String>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn store_tick_delay_tracks_runtime_cadence() -> Result<(), String> {
|
||||
let mut store = WebSurfaceStore::new_with_runtime(WebSurfaceRuntime::new_with_client_factory(
|
||||
fake_client_factory,
|
||||
));
|
||||
let tab = web_tab(TabId::new(), ProfileId::new(), "https://example.com/cadence")?;
|
||||
let visible = vec![tab.id().clone()];
|
||||
|
||||
assert_eq!(store.next_tick_delay(&visible), IDLE_POLL_INTERVAL);
|
||||
assert_eq!(
|
||||
store.record_viewport_size(tab.id(), viewport_bounds(), 1.0),
|
||||
WebSurfaceInputOutcome::Applied,
|
||||
);
|
||||
assert!(store.ensure_surface(&tab, ProfileDataMode::Transient, &[]).changed);
|
||||
assert_eq!(store.next_tick_delay(&visible), Duration::ZERO);
|
||||
|
||||
let _ = store.tick(&visible);
|
||||
let delay = store.next_tick_delay(&visible);
|
||||
|
||||
assert!(delay <= ACTIVE_POLL_INTERVAL);
|
||||
assert!(delay > Duration::ZERO);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sidecar_exit_removes_dead_runtime_client() -> Result<(), String> {
|
||||
RECOVERY_FACTORY_COUNT.store(0, Ordering::SeqCst);
|
||||
|
||||
Reference in New Issue
Block a user