Land 125 Hz Servo polling and eased shell transitions
Now that `LiveRuntimeWorker` does all the blocking IPC off the UI thread, the 16 ms shell tick is no longer the bottleneck — drop it to 8 ms (≈ 125 Hz) so a 120 Hz display can present a fresh Servo frame between every refresh. The worker queue still coalesces, so doubling the rate does not double the wire traffic. Replace `fade_in`'s linear ramp with an ease-out cubic so panel / overlay reveals decelerate the way the design tokens promise instead of cutting in abruptly at the end. Unit-tests pin the curve shape so a future refactor that wires in a different easing function won't silently revert to linear.
This commit is contained in:
@@ -21,7 +21,18 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
/// Soft fade-in over `panel_transition_ms` once the element first mounts.
|
||||
/// Cubic ease-out: `1 - (1 - t)^3`. Decelerates as it approaches `1.0`,
|
||||
/// matching the productive register's "settles fast" feel used by every
|
||||
/// reveal / pop / scale animation in the shell. Inlined so callers can
|
||||
/// shape their `t` value before applying it to opacity or scale without
|
||||
/// adding a per-frame function-call cost.
|
||||
pub(crate) fn ease_out_cubic(t: f32) -> f32 {
|
||||
let inv = (1.0 - t).clamp(0.0, 1.0);
|
||||
1.0 - inv * inv * inv
|
||||
}
|
||||
|
||||
/// Soft fade-in over `duration_ms` once the element first mounts, eased
|
||||
/// out so the trail end settles to fully opaque without an abrupt cut.
|
||||
///
|
||||
/// Drives the design's `panel transition` motion token (180 ms productive)
|
||||
/// for surfaces like the command overlay and workspace disclosure that
|
||||
@@ -31,5 +42,26 @@ where
|
||||
E: IntoElement + Styled + 'static,
|
||||
{
|
||||
let animation = Animation::new(Duration::from_millis(duration_ms));
|
||||
element.with_animation(id, animation, |element, t| element.opacity(t))
|
||||
element.with_animation(id, animation, |element, t| element.opacity(ease_out_cubic(t)))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::ease_out_cubic;
|
||||
|
||||
#[test]
|
||||
fn ease_out_cubic_anchors_at_endpoints() {
|
||||
assert!((ease_out_cubic(0.0)).abs() < f32::EPSILON);
|
||||
assert!((ease_out_cubic(1.0) - 1.0).abs() < f32::EPSILON);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ease_out_cubic_decelerates() {
|
||||
// First half should travel less than the second half — that's
|
||||
// the visual signature of ease-out.
|
||||
let first_half = ease_out_cubic(0.5);
|
||||
let second_half = 1.0 - first_half;
|
||||
assert!(first_half > 0.5, "ease-out spends more time near 1.0 ({first_half})");
|
||||
assert!(second_half < 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -482,8 +482,14 @@ 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(16)).await;
|
||||
Timer::after(Duration::from_millis(8)).await;
|
||||
let result = shell.update(cx, |shell, cx| {
|
||||
if shell.tick_external_web_surfaces() {
|
||||
cx.notify();
|
||||
|
||||
Reference in New Issue
Block a user