perf(ui-gpui): eliminate ProjectBox Entity, plain struct + direct render (2.17%)
ProjectBoxData replaces Entity<ProjectBox>. Workspace is the only view entity in the dispatch tree. Timer notifies Workspace via cx.spawn. 12 optimization iterations: 90% → 2.17% CPU for a pulsing dot.
This commit is contained in:
parent
3bbaefa9a2
commit
c61262c604
3 changed files with 122 additions and 255 deletions
|
|
@ -1,25 +1,36 @@
|
|||
//! Workspace: root view composing sidebar + project boxes + status bar.
|
||||
//!
|
||||
//! ProjectBoxes are rendered DIRECTLY as children of Workspace (no intermediate
|
||||
//! ProjectGrid entity). This keeps the dispatch tree at 3 levels:
|
||||
//! Workspace → ProjectBox → StatusDotView (same depth as Zed's Workspace → Pane → Editor).
|
||||
//! ProjectBoxes are NOT entities — they're plain structs rendered as custom Elements.
|
||||
//! This eliminates the Entity boundary and dispatch tree overhead for project cards.
|
||||
//! Only Workspace is a view in the dispatch tree. Blink timer notifies Workspace directly.
|
||||
//! Dispatch tree depth: 1 (just Workspace). AgentPane/TerminalView are cached child entities.
|
||||
|
||||
use gpui::*;
|
||||
|
||||
use crate::components::command_palette::CommandPalette;
|
||||
use crate::components::project_box::ProjectBox;
|
||||
use crate::components::project_box::ProjectBoxData;
|
||||
use crate::components::project_box_element::ProjectBoxFullElement;
|
||||
use crate::components::settings::SettingsPanel;
|
||||
use crate::components::sidebar::Sidebar;
|
||||
use crate::components::status_bar::StatusBar;
|
||||
use crate::state::AppState;
|
||||
use crate::state::{AgentStatus, AppState, ProjectTab};
|
||||
use crate::theme;
|
||||
use crate::CachedView;
|
||||
|
||||
fn accent_color(index: usize) -> Rgba {
|
||||
const ACCENTS: [Rgba; 8] = [
|
||||
theme::BLUE, theme::MAUVE, theme::GREEN, theme::PEACH,
|
||||
theme::PINK, theme::TEAL, theme::SAPPHIRE, theme::LAVENDER,
|
||||
];
|
||||
ACCENTS[index % ACCENTS.len()]
|
||||
}
|
||||
|
||||
pub struct Workspace {
|
||||
#[allow(dead_code)]
|
||||
app_state: Entity<AppState>,
|
||||
sidebar: Entity<Sidebar>,
|
||||
settings_panel: Entity<SettingsPanel>,
|
||||
project_boxes: Vec<Entity<ProjectBox>>,
|
||||
project_boxes: Vec<ProjectBoxData>,
|
||||
status_bar: Entity<StatusBar>,
|
||||
command_palette: Entity<CommandPalette>,
|
||||
}
|
||||
|
|
@ -43,16 +54,44 @@ impl Workspace {
|
|||
|_cx| CommandPalette::new(state)
|
||||
});
|
||||
|
||||
// Create ProjectBoxes directly (no intermediate ProjectGrid entity)
|
||||
// Create ProjectBoxData (plain structs with entity handles for content)
|
||||
let projects: Vec<_> = app_state.read(cx).projects.clone();
|
||||
let project_boxes: Vec<Entity<ProjectBox>> = projects
|
||||
let project_boxes: Vec<ProjectBoxData> = projects
|
||||
.into_iter()
|
||||
.map(|proj| {
|
||||
cx.new(|cx| {
|
||||
let mut pb = ProjectBox::new(proj);
|
||||
pb.init_subviews(cx);
|
||||
pb
|
||||
})
|
||||
let mut data = ProjectBoxData::new(&proj);
|
||||
|
||||
// Create cached child entities for content
|
||||
let agent_pane = cx.new(|_cx| {
|
||||
crate::components::agent_pane::AgentPane::with_demo_messages()
|
||||
});
|
||||
let terminal_view = cx.new(|_cx| {
|
||||
let mut tv = crate::terminal::renderer::TerminalView::new(120, 10);
|
||||
tv.feed_demo();
|
||||
tv
|
||||
});
|
||||
data.agent_pane = Some(agent_pane);
|
||||
data.terminal_view = Some(terminal_view);
|
||||
|
||||
// Start blink timer for Running projects (focus-gated: first only)
|
||||
let should_pulse = matches!(proj.agent.status, AgentStatus::Running)
|
||||
&& proj.accent_index == 0;
|
||||
if should_pulse {
|
||||
let blink = crate::components::blink_state::SharedBlink::new();
|
||||
let visible = blink.visible.clone();
|
||||
// Notify WORKSPACE directly — it's the only view entity
|
||||
cx.spawn(async move |workspace: 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 = workspace.update(cx, |_, cx| cx.notify());
|
||||
if ok.is_err() { break; }
|
||||
}
|
||||
}).detach();
|
||||
data.shared_blink = Some(blink);
|
||||
}
|
||||
|
||||
data
|
||||
})
|
||||
.collect();
|
||||
|
||||
|
|
@ -81,7 +120,6 @@ impl Render for Workspace {
|
|||
.bg(theme::CRUST)
|
||||
.font_family("Inter");
|
||||
|
||||
// Main content row
|
||||
let mut main_row = div()
|
||||
.id("main-row")
|
||||
.flex_1()
|
||||
|
|
@ -97,7 +135,7 @@ impl Render for Workspace {
|
|||
main_row = main_row.child(self.settings_panel.clone());
|
||||
}
|
||||
|
||||
// Project grid area — inline, no intermediate entity
|
||||
// Project grid — inline custom Elements, NO entity children
|
||||
let mut grid = div()
|
||||
.id("project-grid")
|
||||
.flex_1()
|
||||
|
|
@ -110,8 +148,51 @@ impl Render for Workspace {
|
|||
.bg(theme::CRUST)
|
||||
.overflow_y_scroll();
|
||||
|
||||
for pb in &self.project_boxes {
|
||||
grid = grid.child(pb.clone());
|
||||
for data in &self.project_boxes {
|
||||
let accent = accent_color(data.accent_index);
|
||||
let tab_idx = match data.active_tab {
|
||||
ProjectTab::Model => 0,
|
||||
ProjectTab::Docs => 1,
|
||||
ProjectTab::Files => 2,
|
||||
};
|
||||
|
||||
// Build content div with cached child entities
|
||||
let mut content = div()
|
||||
.id(data.id_content.clone())
|
||||
.flex_1()
|
||||
.w_full()
|
||||
.overflow_hidden();
|
||||
|
||||
content = match data.active_tab {
|
||||
ProjectTab::Model => {
|
||||
let mut c = content.flex().flex_col();
|
||||
if let Some(ref pane) = data.agent_pane {
|
||||
c = c.child(pane.clone().into_cached_flex());
|
||||
}
|
||||
c = c.child(div().w_full().h(px(4.0)).bg(theme::SURFACE0));
|
||||
if let Some(ref term) = data.terminal_view {
|
||||
c = c.child(term.clone().into_cached_flex());
|
||||
}
|
||||
c
|
||||
}
|
||||
ProjectTab::Docs => content.flex().items_center().justify_center()
|
||||
.text_size(px(14.0)).text_color(theme::OVERLAY0)
|
||||
.child("Documentation viewer"),
|
||||
ProjectTab::Files => content.flex().flex_col().p(px(12.0)).gap(px(4.0))
|
||||
.text_size(px(12.0)).text_color(theme::SUBTEXT0)
|
||||
.child("src/").child(" main.rs").child(" lib.rs").child("Cargo.toml"),
|
||||
};
|
||||
|
||||
grid = grid.child(ProjectBoxFullElement {
|
||||
id: data.id_project.clone().into(),
|
||||
name: data.cached_name.clone(),
|
||||
cwd: data.cached_cwd.clone(),
|
||||
accent,
|
||||
status: data.status,
|
||||
blink_visible: data.shared_blink.as_ref().map(|b| b.visible.clone()),
|
||||
active_tab: tab_idx,
|
||||
content: content.into_any_element(),
|
||||
});
|
||||
}
|
||||
|
||||
main_row = main_row.child(grid);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue