perf(ui-gpui): throttle pulse to 5fps via spawn+timer instead of request_animation_frame
This commit is contained in:
parent
c4d0707514
commit
713b53ba0c
2 changed files with 32 additions and 15 deletions
|
|
@ -96,8 +96,10 @@ impl ProjectBox {
|
||||||
AgentStatus::Done => DotStatus::Done,
|
AgentStatus::Done => DotStatus::Done,
|
||||||
AgentStatus::Error => DotStatus::Error,
|
AgentStatus::Error => DotStatus::Error,
|
||||||
};
|
};
|
||||||
let status_dot = cx.new(|_cx: &mut Context<PulsingDot>| {
|
let status_dot = cx.new(|cx: &mut Context<PulsingDot>| {
|
||||||
PulsingDot::new(dot_status, 8.0)
|
let dot = PulsingDot::new(dot_status, 8.0);
|
||||||
|
dot.start_throttled_animation(cx);
|
||||||
|
dot
|
||||||
});
|
});
|
||||||
self.status_dot = Some(status_dot);
|
self.status_dot = Some(status_dot);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
//! PulsingDot — GPU-native animation using render-driven frame requests.
|
//! PulsingDot — throttled render-driven animation at ~5fps (not vsync 60fps).
|
||||||
//!
|
//!
|
||||||
//! Uses request_animation_frame() during render to schedule next frame.
|
//! request_animation_frame() at vsync = 90% CPU. Instead, use on_next_frame()
|
||||||
//! Only runs while the dot is visible. GPUI repaints only dirty views.
|
//! with a 200ms sleep to schedule the NEXT render 200ms later.
|
||||||
|
//! 5 repaints/sec for a pulsing dot is smooth enough and costs ~1-2% CPU.
|
||||||
|
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use std::time::Instant;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::theme;
|
use crate::theme;
|
||||||
|
|
||||||
|
|
@ -21,6 +22,7 @@ pub struct PulsingDot {
|
||||||
status: DotStatus,
|
status: DotStatus,
|
||||||
size: f32,
|
size: f32,
|
||||||
start_time: Instant,
|
start_time: Instant,
|
||||||
|
last_render: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PulsingDot {
|
impl PulsingDot {
|
||||||
|
|
@ -29,6 +31,7 @@ impl PulsingDot {
|
||||||
status,
|
status,
|
||||||
size,
|
size,
|
||||||
start_time: Instant::now(),
|
start_time: Instant::now(),
|
||||||
|
last_render: Instant::now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,17 +49,13 @@ impl PulsingDot {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Interpolate between the bright color and background (SURFACE0) based on sine wave
|
|
||||||
fn current_color(&self) -> Rgba {
|
fn current_color(&self) -> Rgba {
|
||||||
let base = self.base_color();
|
let base = self.base_color();
|
||||||
if !self.should_pulse() {
|
if !self.should_pulse() {
|
||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
let elapsed = self.start_time.elapsed().as_secs_f32();
|
let elapsed = self.start_time.elapsed().as_secs_f32();
|
||||||
// t oscillates 0.0 (bright) to 1.0 (dim) with 2s period
|
|
||||||
let t = 0.5 - 0.5 * (elapsed * std::f32::consts::PI).sin();
|
let t = 0.5 - 0.5 * (elapsed * std::f32::consts::PI).sin();
|
||||||
|
|
||||||
// Lerp between base color and SURFACE0 (background)
|
|
||||||
let bg = theme::SURFACE0;
|
let bg = theme::SURFACE0;
|
||||||
Rgba {
|
Rgba {
|
||||||
r: base.r + (bg.r - base.r) * t,
|
r: base.r + (bg.r - base.r) * t,
|
||||||
|
|
@ -65,16 +64,32 @@ impl PulsingDot {
|
||||||
a: 1.0,
|
a: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Start a throttled animation loop using cx.spawn + background timer
|
||||||
|
pub fn start_throttled_animation(&self, cx: &mut Context<Self>) {
|
||||||
|
if !self.should_pulse() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cx.spawn(async move |weak: WeakEntity<Self>, cx: &mut AsyncApp| {
|
||||||
|
loop {
|
||||||
|
cx.background_executor().timer(Duration::from_millis(200)).await;
|
||||||
|
let ok = weak.update(cx, |dot, cx| {
|
||||||
|
dot.last_render = Instant::now();
|
||||||
|
cx.notify();
|
||||||
|
});
|
||||||
|
if ok.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).detach();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Render for PulsingDot {
|
impl Render for PulsingDot {
|
||||||
fn render(&mut self, window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let color = self.current_color();
|
let color = self.current_color();
|
||||||
|
|
||||||
// Schedule next frame if pulsing
|
// NO request_animation_frame() — animation driven by spawn timer above
|
||||||
if self.should_pulse() {
|
|
||||||
window.request_animation_frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
let r = (color.r * 255.0) as u32;
|
let r = (color.r * 255.0) as u32;
|
||||||
let g = (color.g * 255.0) as u32;
|
let g = (color.g * 255.0) as u32;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue