perf(ui-gpui): flatten hierarchy + SharedBlink (Arc<AtomicBool>)
- Eliminated ProjectGrid entity level: Workspace renders ProjectBoxes directly (3 dispatch tree levels: Workspace → ProjectBox → inline divs) - Replaced Entity<BlinkState> + Entity<StatusDotView> with SharedBlink (Arc<AtomicBool> toggled by background timer, read atomically in render) - Timer calls cx.notify() directly on ProjectBox (no intermediate entities) - CPU: 2.93% (unchanged from 3.07% — confirms cost is ProjectBox::render() overhead, not hierarchy depth or entity count) - Each blink frame: ~15ms total (render + layout + prepaint + paint + GPU submit) - Zed comparison: ~5ms per blink frame (EditorElement is custom Element, not div tree)
This commit is contained in:
parent
ddec12db3f
commit
6f0f276400
3 changed files with 113 additions and 105 deletions
|
|
@ -11,7 +11,7 @@ use crate::CachedView;
|
|||
|
||||
use crate::state::{AgentStatus, Project, ProjectTab};
|
||||
use crate::theme;
|
||||
use crate::components::pulsing_dot::{PulsingDot, DotStatus};
|
||||
// blink_state used via fully-qualified path in init_subviews + render
|
||||
|
||||
// ── Accent Colors by Index ──────────────────────────────────────────
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ pub struct ProjectBox {
|
|||
pub project: Project,
|
||||
pub agent_pane: Option<Entity<crate::components::agent_pane::AgentPane>>,
|
||||
pub terminal_view: Option<Entity<crate::terminal::renderer::TerminalView>>,
|
||||
pub status_dot_view: Option<Entity<crate::components::blink_state::StatusDotView>>,
|
||||
pub shared_blink: Option<crate::components::blink_state::SharedBlink>,
|
||||
// Cached strings to avoid allocation on every render
|
||||
id_project: SharedString,
|
||||
id_model: SharedString,
|
||||
|
|
@ -88,7 +88,7 @@ impl ProjectBox {
|
|||
project,
|
||||
agent_pane: None,
|
||||
terminal_view: None,
|
||||
status_dot_view: None,
|
||||
shared_blink: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -96,25 +96,26 @@ impl ProjectBox {
|
|||
pub fn init_subviews(&mut self, cx: &mut Context<Self>) {
|
||||
// Initialize sub-views after entity registration
|
||||
|
||||
// StatusDotView: tiny Entity that reads BlinkState.
|
||||
// ProjectBox does NOT read BlinkState → doesn't re-render on blink.
|
||||
// Only StatusDotView (1 div) re-renders 2x/sec.
|
||||
// Focus-gated blink: only the first Running project blinks (like Zed's single-focus model).
|
||||
// In production, this would be gated on project focus state.
|
||||
// SharedBlink: Arc<AtomicBool> toggled by background timer.
|
||||
// Timer calls cx.notify() on THIS ProjectBox directly — no intermediate entities.
|
||||
// mark_view_dirty walks: ProjectBox → Workspace (2 levels only).
|
||||
let should_pulse = matches!(self.project.agent.status, AgentStatus::Running)
|
||||
&& self.project.accent_index == 0; // Only first project blinks
|
||||
let blink = if should_pulse {
|
||||
let b = cx.new(|_cx| crate::components::blink_state::BlinkState::new());
|
||||
crate::components::blink_state::BlinkState::start_from_context(&b, cx);
|
||||
Some(b)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let status = self.project.agent.status;
|
||||
let dot_view = cx.new(|_cx| {
|
||||
crate::components::blink_state::StatusDotView::new(status, blink)
|
||||
});
|
||||
self.status_dot_view = Some(dot_view);
|
||||
&& self.project.accent_index == 0;
|
||||
if should_pulse {
|
||||
let blink = crate::components::blink_state::SharedBlink::new();
|
||||
// Get our own entity handle to pass to the timer
|
||||
let self_entity = cx.entity().downgrade();
|
||||
let visible = blink.visible.clone();
|
||||
cx.spawn(async move |_: WeakEntity<Self>, cx: &mut AsyncApp| {
|
||||
loop {
|
||||
cx.background_executor().timer(std::time::Duration::from_millis(500)).await;
|
||||
visible.fetch_xor(true, std::sync::atomic::Ordering::Relaxed);
|
||||
let ok = self_entity.update(cx, |_, cx| cx.notify());
|
||||
if ok.is_err() { break; }
|
||||
}
|
||||
}).detach();
|
||||
self.shared_blink = Some(blink);
|
||||
}
|
||||
|
||||
// Create agent pane with demo messages
|
||||
let agent_pane = cx.new(|_cx| {
|
||||
|
|
@ -239,7 +240,10 @@ impl Render for ProjectBox {
|
|||
.border_b_1()
|
||||
.border_color(theme::SURFACE0)
|
||||
.child(div().w(px(3.0)).h(px(20.0)).rounded(px(2.0)).bg(accent))
|
||||
.children(self.status_dot_view.clone())
|
||||
.child(crate::components::blink_state::render_status_dot(
|
||||
self.project.agent.status,
|
||||
self.shared_blink.as_ref(),
|
||||
))
|
||||
.child(self.cached_name.clone())
|
||||
.child(div().flex_1())
|
||||
.child(self.cached_cwd.clone()),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue