agent-orchestrator/ui-gpui/src/components/blink_state.rs
Hibryda 6f0f276400 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)
2026-03-19 23:45:05 +01:00

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()
}