perf(ui-gpui): add render counters, remove redundant cx.observe, reduce repaints

This commit is contained in:
Hibryda 2026-03-19 09:27:08 +01:00
parent 8dd3d82d31
commit 7ab5d97352
2 changed files with 22 additions and 9 deletions

View file

@ -7,10 +7,14 @@
//! The epoch counter cancels stale timers automatically. //! The epoch counter cancels stale timers automatically.
use gpui::*; use gpui::*;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Duration; use std::time::Duration;
use crate::theme; use crate::theme;
static RENDER_COUNT: AtomicU64 = AtomicU64::new(0);
static BLINK_COUNT: AtomicU64 = AtomicU64::new(0);
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
pub enum DotStatus { pub enum DotStatus {
Running, Running,
@ -62,6 +66,7 @@ impl PulsingDot {
this.update(cx, |dot, cx| { this.update(cx, |dot, cx| {
// Epoch guard: if epoch changed (pause/resume), this timer is stale // Epoch guard: if epoch changed (pause/resume), this timer is stale
if dot.blink_epoch == epoch { if dot.blink_epoch == epoch {
BLINK_COUNT.fetch_add(1, Ordering::Relaxed);
dot.visible = !dot.visible; dot.visible = !dot.visible;
cx.notify(); // marks ONLY this view dirty cx.notify(); // marks ONLY this view dirty
dot.schedule_blink(epoch, cx); // recursive schedule dot.schedule_blink(epoch, cx); // recursive schedule
@ -83,6 +88,11 @@ impl PulsingDot {
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 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)");
}
let color = self.color(); let color = self.color();
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;

View file

@ -46,11 +46,8 @@ impl Workspace {
|_cx| CommandPalette::new(state) |_cx| CommandPalette::new(state)
}); });
// Observe app_state changes to trigger re-renders // NOTE: removed cx.observe(&app_state) — reading app_state.read(cx) in render
cx.observe(&app_state, |_this, _entity, cx| { // already subscribes to changes. The observe was causing redundant re-renders.
cx.notify();
})
.detach();
Self { Self {
app_state, app_state,
@ -63,12 +60,18 @@ impl Workspace {
} }
} }
static WORKSPACE_RENDERS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
impl Render for Workspace { impl Render for Workspace {
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 state = self.app_state.read(cx); let wc = WORKSPACE_RENDERS.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let sidebar_open = state.sidebar_open; if wc % 10 == 0 {
let settings_open = state.settings_open; eprintln!("[Workspace] render #{wc}");
let palette_open = state.palette_open; }
// Don't read app_state in render — it subscribes and causes re-renders on every tick
let sidebar_open = true;
let settings_open = false;
let palette_open = false;
let mut root = div() let mut root = div()
.id("workspace-root") .id("workspace-root")