- 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)
67 lines
2.2 KiB
Rust
67 lines
2.2 KiB
Rust
//! Blink state using Arc<AtomicBool> — zero entity overhead.
|
|
//!
|
|
//! The pulsing dot reads from a shared atomic. A background thread toggles it
|
|
//! every 500ms and calls window.request_animation_frame() via a stored callback.
|
|
//! No Entity, no cx.notify(), no dispatch tree involvement.
|
|
|
|
use gpui::*;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
use crate::state::AgentStatus;
|
|
use crate::theme;
|
|
|
|
/// Shared blink state — just an atomic bool. No Entity, no GPUI overhead.
|
|
pub struct SharedBlink {
|
|
pub visible: Arc<AtomicBool>,
|
|
}
|
|
|
|
impl SharedBlink {
|
|
pub fn new() -> Self {
|
|
Self { visible: Arc::new(AtomicBool::new(true)) }
|
|
}
|
|
|
|
/// Start blinking on a background thread. Calls `cx.notify()` on the
|
|
/// parent view entity to trigger repaint.
|
|
pub fn start<V: 'static + Render>(
|
|
&self,
|
|
parent: &Entity<V>,
|
|
cx: &mut Context<V>,
|
|
) {
|
|
let visible = self.visible.clone();
|
|
let weak = parent.downgrade();
|
|
cx.spawn(async move |_weak_parent: WeakEntity<V>, cx: &mut AsyncApp| {
|
|
loop {
|
|
cx.background_executor().timer(Duration::from_millis(500)).await;
|
|
visible.fetch_xor(true, Ordering::Relaxed);
|
|
// Notify the PARENT view directly — no intermediate entity
|
|
let ok = weak.update(cx, |_, cx| cx.notify());
|
|
if ok.is_err() { break; }
|
|
}
|
|
}).detach();
|
|
}
|
|
}
|
|
|
|
/// Render a status dot as an inline div. Reads from SharedBlink atomically.
|
|
/// No Entity, no dispatch tree node, no dirty propagation.
|
|
pub fn render_status_dot(status: AgentStatus, blink: Option<&SharedBlink>) -> Div {
|
|
let blink_visible = blink
|
|
.map(|b| b.visible.load(Ordering::Relaxed))
|
|
.unwrap_or(true);
|
|
|
|
let color = match status {
|
|
AgentStatus::Running if !blink_visible => theme::SURFACE1,
|
|
AgentStatus::Running => theme::GREEN,
|
|
AgentStatus::Idle => theme::OVERLAY0,
|
|
AgentStatus::Done => theme::BLUE,
|
|
AgentStatus::Error => theme::RED,
|
|
};
|
|
|
|
div()
|
|
.w(px(8.0))
|
|
.h(px(8.0))
|
|
.rounded(px(4.0))
|
|
.bg(color)
|
|
.flex_shrink_0()
|
|
}
|