feat(context): Sistema de registro de paneles dirty
Añade el sistema de dirty panels para optimización de rendering: - panel_areas: HashMap para registrar rectángulos de paneles - dirty_panels: HashMap con flags de invalidación por panel - registerPanelArea(id, rect): Registrar panel con nombre - invalidatePanel(id): Marcar panel como dirty - isPanelDirty(id): Consultar si necesita redraw - markAllPanelsDirty(): Para resize/tab change - getDirtyPanelRects(): Obtener rects dirty para renderer - clearDirtyPanels(): Limpiar flags (automático en endFrame) Diseño: - La aplicación registra paneles al inicio - La aplicación notifica cambios via invalidatePanel() - El renderer usa getDirtyPanelRects() para limpieza selectiva - Los flags se limpian automáticamente en endFrame() Parte de la implementación de Dirty Panels Fase 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
4b7069b076
commit
105ff0063d
1 changed files with 146 additions and 0 deletions
|
|
@ -87,6 +87,16 @@ pub const Context = struct {
|
|||
/// Whether the entire screen needs redraw
|
||||
full_redraw: bool,
|
||||
|
||||
// =========================================================================
|
||||
// Dirty Panel System (granularity per named panel)
|
||||
// =========================================================================
|
||||
|
||||
/// Registered panel areas by name (e.g., "who_list", "doc_detail")
|
||||
panel_areas: std.StringHashMapUnmanaged(Layout.Rect),
|
||||
|
||||
/// Dirty flags per panel (true = needs redraw)
|
||||
dirty_panels: std.StringHashMapUnmanaged(bool),
|
||||
|
||||
/// Frame statistics
|
||||
stats: FrameStats,
|
||||
|
||||
|
|
@ -157,6 +167,8 @@ pub const Context = struct {
|
|||
.height = height,
|
||||
.dirty_rects = .{},
|
||||
.full_redraw = true,
|
||||
.panel_areas = .{},
|
||||
.dirty_panels = .{},
|
||||
.stats = .{},
|
||||
.focus = FocusSystem.init(),
|
||||
};
|
||||
|
|
@ -177,6 +189,8 @@ pub const Context = struct {
|
|||
.height = height,
|
||||
.dirty_rects = .{},
|
||||
.full_redraw = true,
|
||||
.panel_areas = .{},
|
||||
.dirty_panels = .{},
|
||||
.stats = .{},
|
||||
.focus = FocusSystem.init(),
|
||||
};
|
||||
|
|
@ -188,6 +202,8 @@ pub const Context = struct {
|
|||
self.overlay_commands.deinit(self.allocator);
|
||||
self.id_stack.deinit(self.allocator);
|
||||
self.dirty_rects.deinit(self.allocator);
|
||||
self.panel_areas.deinit(self.allocator);
|
||||
self.dirty_panels.deinit(self.allocator);
|
||||
self.frame_arena.deinit();
|
||||
}
|
||||
|
||||
|
|
@ -238,6 +254,10 @@ pub const Context = struct {
|
|||
|
||||
// Reset full_redraw for next frame
|
||||
self.full_redraw = false;
|
||||
|
||||
// Clear dirty panel flags AFTER rendering
|
||||
// (renderer uses getDirtyPanelRects during frame)
|
||||
self.clearDirtyPanels();
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
|
@ -743,6 +763,132 @@ pub const Context = struct {
|
|||
self.width = width;
|
||||
self.height = height;
|
||||
self.invalidateAll();
|
||||
self.markAllPanelsDirty(); // Resize requires all panels to redraw
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
// Dirty Panel System (granularity per named panel)
|
||||
//
|
||||
// The application registers panel areas at startup:
|
||||
// ctx.registerPanelArea("who_list", rect);
|
||||
//
|
||||
// When data changes, the application marks panels dirty:
|
||||
// ctx.invalidatePanel("who_list");
|
||||
//
|
||||
// The renderer checks which panels are dirty:
|
||||
// const dirty_rects = ctx.getDirtyPanelRects();
|
||||
// renderer.clearDirtyRegions(dirty_rects);
|
||||
//
|
||||
// At endFrame(), dirty flags are cleared automatically.
|
||||
// =========================================================================
|
||||
|
||||
/// Register a named panel area. Call at startup or when layout changes.
|
||||
/// The ID should be descriptive (e.g., "who_list", "doc_detail").
|
||||
pub fn registerPanelArea(self: *Self, id: []const u8, rect: Layout.Rect) void {
|
||||
self.panel_areas.put(self.allocator, id, rect) catch {
|
||||
// If we can't register, fall back to full redraw
|
||||
self.full_redraw = true;
|
||||
};
|
||||
// New panels start dirty (need initial draw)
|
||||
self.dirty_panels.put(self.allocator, id, true) catch {};
|
||||
}
|
||||
|
||||
/// Update a panel's rect (e.g., after window resize).
|
||||
/// Does NOT mark the panel dirty - call invalidatePanel separately if needed.
|
||||
pub fn updatePanelArea(self: *Self, id: []const u8, rect: Layout.Rect) void {
|
||||
if (self.panel_areas.getPtr(id)) |ptr| {
|
||||
ptr.* = rect;
|
||||
}
|
||||
}
|
||||
|
||||
/// Mark a panel as needing redraw.
|
||||
/// Call this when data changes that affects the panel's content.
|
||||
pub fn invalidatePanel(self: *Self, id: []const u8) void {
|
||||
if (self.dirty_panels.getPtr(id)) |ptr| {
|
||||
ptr.* = true;
|
||||
} else {
|
||||
// Panel not registered - this is probably a bug
|
||||
// For safety, do full redraw
|
||||
self.full_redraw = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a panel is marked dirty.
|
||||
pub fn isPanelDirty(self: *Self, id: []const u8) bool {
|
||||
if (self.full_redraw) return true;
|
||||
return self.dirty_panels.get(id) orelse false;
|
||||
}
|
||||
|
||||
/// Mark all registered panels as dirty.
|
||||
/// Use for resize, tab change, or other global invalidation.
|
||||
pub fn markAllPanelsDirty(self: *Self) void {
|
||||
var iter = self.dirty_panels.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
entry.value_ptr.* = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get rectangles of all dirty panels (for renderer).
|
||||
/// Returns slice allocated from frame arena (valid until next beginFrame).
|
||||
pub fn getDirtyPanelRects(self: *Self) []const Layout.Rect {
|
||||
if (self.full_redraw) {
|
||||
// Return single rect covering entire screen
|
||||
const full = Layout.Rect{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.w = self.width,
|
||||
.h = self.height,
|
||||
};
|
||||
const result = self.frame_arena.alloc_slice(Layout.Rect, 1) orelse return &.{};
|
||||
result[0] = full;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Count dirty panels
|
||||
var dirty_count: usize = 0;
|
||||
var iter = self.dirty_panels.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) dirty_count += 1;
|
||||
}
|
||||
|
||||
if (dirty_count == 0) return &.{};
|
||||
|
||||
// Allocate result array from frame arena
|
||||
const result = self.frame_arena.alloc_slice(Layout.Rect, dirty_count) orelse return &.{};
|
||||
|
||||
// Fill with dirty panel rects
|
||||
var i: usize = 0;
|
||||
var iter2 = self.dirty_panels.iterator();
|
||||
while (iter2.next()) |entry| {
|
||||
if (entry.value_ptr.*) {
|
||||
if (self.panel_areas.get(entry.key_ptr.*)) |rect| {
|
||||
result[i] = rect;
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result[0..i];
|
||||
}
|
||||
|
||||
/// Check if any panel is dirty (useful for skip-redraw optimization).
|
||||
pub fn hasAnyDirtyPanel(self: *Self) bool {
|
||||
if (self.full_redraw) return true;
|
||||
|
||||
var iter = self.dirty_panels.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
if (entry.value_ptr.*) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Clear all dirty panel flags.
|
||||
/// Called automatically at endFrame(), but can be called manually if needed.
|
||||
pub fn clearDirtyPanels(self: *Self) void {
|
||||
var iter = self.dirty_panels.iterator();
|
||||
while (iter.next()) |entry| {
|
||||
entry.value_ptr.* = false;
|
||||
}
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
|
|
|||
Loading…
Reference in a new issue