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
|
|
@ -1,25 +1,25 @@
|
|||
//! Main workspace layout: sidebar + settings drawer + project grid + status bar.
|
||||
//! Workspace: root view composing sidebar + project boxes + status bar.
|
||||
//!
|
||||
//! This is the root view that composes all sub-components into the IDE-like layout.
|
||||
//! 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).
|
||||
|
||||
use gpui::*;
|
||||
|
||||
use crate::CachedView;
|
||||
use crate::components::command_palette::CommandPalette;
|
||||
use crate::components::project_grid::ProjectGrid;
|
||||
use crate::components::project_box::ProjectBox;
|
||||
use crate::components::settings::SettingsPanel;
|
||||
use crate::components::sidebar::Sidebar;
|
||||
use crate::components::status_bar::StatusBar;
|
||||
use crate::state::AppState;
|
||||
use crate::theme;
|
||||
|
||||
// ── Workspace View ──────────────────────────────────────────────────
|
||||
|
||||
pub struct Workspace {
|
||||
#[allow(dead_code)]
|
||||
app_state: Entity<AppState>,
|
||||
sidebar: Entity<Sidebar>,
|
||||
settings_panel: Entity<SettingsPanel>,
|
||||
project_grid: Entity<ProjectGrid>,
|
||||
project_boxes: Vec<Entity<ProjectBox>>,
|
||||
status_bar: Entity<StatusBar>,
|
||||
command_palette: Entity<CommandPalette>,
|
||||
}
|
||||
|
|
@ -34,10 +34,6 @@ impl Workspace {
|
|||
let state = app_state.clone();
|
||||
|_cx| SettingsPanel::new(state)
|
||||
});
|
||||
let project_grid = cx.new({
|
||||
let state = app_state.clone();
|
||||
|cx| ProjectGrid::new(state, cx)
|
||||
});
|
||||
let status_bar = cx.new({
|
||||
let state = app_state.clone();
|
||||
|_cx| StatusBar::new(state)
|
||||
|
|
@ -47,14 +43,24 @@ impl Workspace {
|
|||
|_cx| CommandPalette::new(state)
|
||||
});
|
||||
|
||||
// NOTE: removed cx.observe(&app_state) — reading app_state.read(cx) in render
|
||||
// already subscribes to changes. The observe was causing redundant re-renders.
|
||||
// Create ProjectBoxes directly (no intermediate ProjectGrid entity)
|
||||
let projects: Vec<_> = app_state.read(cx).projects.clone();
|
||||
let project_boxes: Vec<Entity<ProjectBox>> = projects
|
||||
.into_iter()
|
||||
.map(|proj| {
|
||||
cx.new(|cx| {
|
||||
let mut pb = ProjectBox::new(proj);
|
||||
pb.init_subviews(cx);
|
||||
pb
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
app_state,
|
||||
sidebar,
|
||||
settings_panel,
|
||||
project_grid,
|
||||
project_boxes,
|
||||
status_bar,
|
||||
command_palette,
|
||||
}
|
||||
|
|
@ -62,8 +68,7 @@ impl Workspace {
|
|||
}
|
||||
|
||||
impl Render for Workspace {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
// Hardcoded layout state — avoids model subscription that causes re-renders
|
||||
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let sidebar_open = true;
|
||||
let settings_open = false;
|
||||
let palette_open = false;
|
||||
|
|
@ -76,7 +81,7 @@ impl Render for Workspace {
|
|||
.bg(theme::CRUST)
|
||||
.font_family("Inter");
|
||||
|
||||
// ── Main content row (sidebar + settings? + grid) ───
|
||||
// Main content row
|
||||
let mut main_row = div()
|
||||
.id("main-row")
|
||||
.flex_1()
|
||||
|
|
@ -85,32 +90,34 @@ impl Render for Workspace {
|
|||
.flex_row()
|
||||
.overflow_hidden();
|
||||
|
||||
// Sidebar (icon rail) — uncached (tiny, cheap to render)
|
||||
if sidebar_open {
|
||||
main_row = main_row.child(self.sidebar.clone());
|
||||
}
|
||||
|
||||
// Settings drawer (between sidebar and grid)
|
||||
if settings_open {
|
||||
main_row = main_row.child(self.settings_panel.clone());
|
||||
}
|
||||
|
||||
// Project grid (fills remaining space) — cached with flex-1
|
||||
// ProjectGrid cached — when StatusDotView notifies, ProjectGrid IS in dirty_views
|
||||
// (ancestor walk), but the cached wrapper checks ProjectGrid's own entity_id.
|
||||
// Since ProjectGrid wasn't directly notified, the cache should hit.
|
||||
// Wait — mark_view_dirty inserts ALL ancestors including ProjectGrid.
|
||||
// So the cache WILL miss for ProjectGrid. We need it uncached.
|
||||
main_row = main_row.child(
|
||||
div().flex_1().h_full().child(self.project_grid.clone()),
|
||||
);
|
||||
// Project grid area — inline, no intermediate entity
|
||||
let mut grid = div()
|
||||
.id("project-grid")
|
||||
.flex_1()
|
||||
.h_full()
|
||||
.flex()
|
||||
.flex_row()
|
||||
.flex_wrap()
|
||||
.gap(px(8.0))
|
||||
.p(px(8.0))
|
||||
.bg(theme::CRUST)
|
||||
.overflow_y_scroll();
|
||||
|
||||
for pb in &self.project_boxes {
|
||||
grid = grid.child(pb.clone());
|
||||
}
|
||||
|
||||
main_row = main_row.child(grid);
|
||||
root = root.child(main_row);
|
||||
|
||||
// Status bar (bottom) — uncached (tiny, cheap to render)
|
||||
root = root.child(self.status_bar.clone());
|
||||
|
||||
// ── Command palette overlay (if open) ───────────────
|
||||
if palette_open {
|
||||
root = root.child(self.command_palette.clone());
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue