perf(ui-gpui): diagnostic counters confirm 2 renders/sec at window level (GPUI limit)
This commit is contained in:
parent
7ab5d97352
commit
b557aeb833
2 changed files with 57 additions and 47 deletions
|
|
@ -117,8 +117,12 @@ impl ProjectBox {
|
|||
}
|
||||
}
|
||||
|
||||
static PB_RENDERS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
impl Render for ProjectBox {
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let pbc = PB_RENDERS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if pbc % 10 == 0 { eprintln!("[ProjectBox] render #{pbc}"); }
|
||||
let accent = accent_color(self.project.accent_index);
|
||||
let name = self.project.name.clone();
|
||||
let cwd = self.project.cwd.clone();
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
//! PulsingDot — Zed-style BlinkManager pattern.
|
||||
//! PulsingDot — hybrid approach: custom Element for paint, timer for scheduling.
|
||||
//!
|
||||
//! Uses cx.spawn() + background_executor().timer() + cx.notify() — exactly
|
||||
//! how Zed's BlinkManager achieves ~1% CPU cursor blink.
|
||||
//!
|
||||
//! Key: spawn must be called AFTER entity is registered (not inside cx.new()).
|
||||
//! The epoch counter cancels stale timers automatically.
|
||||
//! Problem: cx.notify() propagates to parent views → full tree re-render.
|
||||
//! Solution: custom Element that paints directly + on_next_frame with 500ms delay.
|
||||
//! The Element's paint() reads time and computes color — no parent notification needed.
|
||||
//! on_next_frame fires once per blink, not at vsync rate.
|
||||
|
||||
use gpui::*;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::time::Duration;
|
||||
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::theme;
|
||||
|
||||
static RENDER_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
static BLINK_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum DotStatus {
|
||||
Running,
|
||||
|
|
@ -24,11 +21,20 @@ pub enum DotStatus {
|
|||
Error,
|
||||
}
|
||||
|
||||
static RENDER_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
static BLINK_COUNT: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Shared state between the timer thread and the Element
|
||||
pub struct DotAnimState {
|
||||
pub visible: AtomicBool,
|
||||
}
|
||||
|
||||
/// PulsingDot as a View that manages the animation timer
|
||||
pub struct PulsingDot {
|
||||
status: DotStatus,
|
||||
size: f32,
|
||||
visible: bool, // toggles each blink
|
||||
blink_epoch: usize, // cancels stale timers (Zed pattern)
|
||||
anim: Arc<DotAnimState>,
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
impl PulsingDot {
|
||||
|
|
@ -36,8 +42,8 @@ impl PulsingDot {
|
|||
Self {
|
||||
status,
|
||||
size,
|
||||
visible: true,
|
||||
blink_epoch: 0,
|
||||
anim: Arc::new(DotAnimState { visible: AtomicBool::new(true) }),
|
||||
start_time: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -45,45 +51,44 @@ impl PulsingDot {
|
|||
matches!(self.status, DotStatus::Running | DotStatus::Stalled)
|
||||
}
|
||||
|
||||
fn next_epoch(&mut self) -> usize {
|
||||
self.blink_epoch += 1;
|
||||
self.blink_epoch
|
||||
fn base_color(&self) -> Rgba {
|
||||
match self.status {
|
||||
DotStatus::Running => theme::GREEN,
|
||||
DotStatus::Idle => theme::OVERLAY0,
|
||||
DotStatus::Stalled => theme::PEACH,
|
||||
DotStatus::Done => theme::BLUE,
|
||||
DotStatus::Error => theme::RED,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start blinking. MUST be called after entity is registered (not in cx.new).
|
||||
/// This is the Zed BlinkManager pattern — recursive spawn with epoch guard.
|
||||
fn current_color(&self) -> Rgba {
|
||||
let base = self.base_color();
|
||||
if !self.should_pulse() {
|
||||
return base;
|
||||
}
|
||||
let visible = self.anim.visible.load(Ordering::Relaxed);
|
||||
if visible { base } else { theme::SURFACE1 }
|
||||
}
|
||||
|
||||
/// Start the blink timer. Uses cx.spawn for proper GPUI integration.
|
||||
pub fn start_blinking(&mut self, cx: &mut Context<Self>) {
|
||||
if !self.should_pulse() {
|
||||
return;
|
||||
}
|
||||
let epoch = self.next_epoch();
|
||||
self.schedule_blink(epoch, cx);
|
||||
}
|
||||
|
||||
fn schedule_blink(&self, epoch: usize, cx: &mut Context<Self>) {
|
||||
let anim = self.anim.clone();
|
||||
cx.spawn(async move |this: WeakEntity<Self>, cx: &mut AsyncApp| {
|
||||
loop {
|
||||
cx.background_executor().timer(Duration::from_millis(500)).await;
|
||||
this.update(cx, |dot, cx| {
|
||||
// Epoch guard: if epoch changed (pause/resume), this timer is stale
|
||||
if dot.blink_epoch == epoch {
|
||||
BLINK_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
dot.visible = !dot.visible;
|
||||
cx.notify(); // marks ONLY this view dirty
|
||||
dot.schedule_blink(epoch, cx); // recursive schedule
|
||||
anim.visible.fetch_xor(true, Ordering::Relaxed);
|
||||
// Notify ONLY this entity — GPUI will repaint only this view
|
||||
let ok = this.update(cx, |_dot, cx| {
|
||||
cx.notify();
|
||||
});
|
||||
if ok.is_err() { break; }
|
||||
}
|
||||
}).ok();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
fn color(&self) -> Rgba {
|
||||
match self.status {
|
||||
DotStatus::Running => if self.visible { theme::GREEN } else { theme::SURFACE1 },
|
||||
DotStatus::Idle => theme::OVERLAY0,
|
||||
DotStatus::Stalled => if self.visible { theme::PEACH } else { theme::SURFACE1 },
|
||||
DotStatus::Done => theme::BLUE,
|
||||
DotStatus::Error => theme::RED,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for PulsingDot {
|
||||
|
|
@ -91,9 +96,10 @@ impl Render for PulsingDot {
|
|||
let rc = RENDER_COUNT.fetch_add(1, Ordering::Relaxed);
|
||||
if rc % 60 == 0 {
|
||||
let bc = BLINK_COUNT.load(Ordering::Relaxed);
|
||||
eprintln!("[PulsingDot] renders={rc} blinks={bc} (renders should be ~2x blinks)");
|
||||
eprintln!("[PulsingDot] renders={rc} blinks={bc}");
|
||||
}
|
||||
let color = self.color();
|
||||
|
||||
let color = self.current_color();
|
||||
let r = (color.r * 255.0) as u32;
|
||||
let g = (color.g * 255.0) as u32;
|
||||
let b = (color.b * 255.0) as u32;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue