Render Coordinator¶
Overview¶
The RenderCoordinator manages rendering optimization and dirty region tracking for the TUI. It coordinates performance monitoring, adaptive input polling, UI caching, and partial screen updates to maintain smooth 30fps rendering while minimizing CPU usage during idle periods.
Location: Sources/Substation/Framework/RenderCoordinator.swift
Architecture¶
graph TB
subgraph RenderCoordinator["RenderCoordinator"]
RO[RenderOptimizer]
PM[PerformanceMonitor]
AP[Adaptive Polling]
VLC[VirtualListControllers]
SC[SearchControllers]
end
subgraph DirtyTracking["Dirty Region Tracking"]
Header[Header Region]
Sidebar[Sidebar Region]
Main[Main Panel]
Status[Status Bar]
end
subgraph Output["Rendering Output"]
Screen[Terminal Screen]
RP[RenderPlan]
end
RO --> DirtyTracking
PM --> RO
AP --> RO
DirtyTracking --> RP
RP --> Screen
Class Definition¶
@MainActor
final class RenderCoordinator {
// Render State
var needsRedraw: Bool = true
var lastDrawTime: Date
var redrawThrottleInterval: TimeInterval = 0.032 // ~30fps
// Performance Tracking
var lastPerformanceLog: Date
var performanceLogInterval: TimeInterval = 30.0
var previousScrollOffset: Int
var lastScrollTime: Date
var scrollEventCount: Int
var scrollBatchTimer: Timer?
// Dependencies
let renderOptimizer: RenderOptimizer
let performanceMonitor: PerformanceMonitor
var virtualListControllers: [String: VirtualListController]
var searchControllers: [String: ListSearchController]
}
Dirty Region System¶
The RenderCoordinator tracks which parts of the screen need redrawing to minimize unnecessary updates.
Screen Regions¶
graph TB
subgraph Screen["Terminal Screen"]
H[Header Region]
S[Sidebar Region]
M[Main Panel]
ST[Status Bar]
end
style H fill:#f9f,stroke:#333
style S fill:#bbf,stroke:#333
style M fill:#bfb,stroke:#333
style ST fill:#fbb,stroke:#333
Marking Regions Dirty¶
/// Mark header region as dirty
func markHeaderDirty()
/// Mark sidebar region as dirty
func markSidebarDirty()
/// Mark status bar region as dirty
func markStatusBarDirty()
/// Mark that a scroll operation occurred
func markScrollOperation()
/// Mark that a view transition occurred (full screen redraw)
func markViewTransition()
Redraw Management¶
Redraw Flow¶
flowchart TD
A[Event Occurs] --> B{Event Type}
B -->|User Input| C[markInputReceived]
B -->|Scroll| D[markScrollOperation]
B -->|View Change| E[markViewTransition]
B -->|Data Update| F[markNeedsRedraw]
C --> G[markNeedsRedraw]
D --> G
E --> H[markFullScreenDirty]
F --> G
G --> I{shouldRedraw?}
H --> I
I -->|Yes| J[Render]
I -->|No| K[Skip]
J --> L[markDrawCompleted]
Redraw Methods¶
/// Mark that the screen needs redraw
func markNeedsRedraw()
/// Check if we should redraw (respecting throttle)
func shouldRedraw() -> Bool
/// Force immediate redraw (for important updates)
func forceRedraw()
/// Mark that the screen was drawn
func markDrawCompleted()
Adaptive Polling¶
The RenderCoordinator implements adaptive polling to balance responsiveness and CPU efficiency.
Polling Intervals¶
| State | Interval | Description |
|---|---|---|
| Active Input | 5ms | Immediate responsiveness |
| Recent Input | 10ms | Short idle period |
| Cooling Down | 20ms | Medium idle |
| Mostly Idle | 30ms | Longer idle |
| Deep Idle | 50ms | Maximum efficiency |
Adaptive Polling Flow¶
flowchart TD
A[Poll for Input] --> B{Input Received?}
B -->|Yes| C[Reset to 5ms]
B -->|No| D{Time Since Input}
D -->|< 0.1s| E[5ms - Active]
D -->|< 1.0s| F[10ms - Recent]
D -->|< 5.0s| G[20ms - Cooling]
D -->|>= 5.0s| H{Idle Count > 100?}
H -->|Yes| I[50ms - Deep Idle]
H -->|No| J[30ms - Mostly Idle]
C --> K[Process Input]
E --> L[Sleep & Loop]
F --> L
G --> L
I --> L
J --> L
Polling Methods¶
/// Mark that input was received - resets adaptive polling
func markInputReceived()
/// Get adaptive sleep interval for polling based on activity
func getAdaptiveSleepInterval() -> UInt64
/// Calculate and update sleep interval based on idle polling state
func updateAdaptiveSleepInterval()
/// Increment the idle poll counter
func incrementIdlePolls()
Render Plan¶
The RenderCoordinator generates a RenderPlan that specifies which regions to redraw.
/// Get the current render plan
func getRenderPlan(screenRows: Int32, screenCols: Int32) -> RenderPlan
The RenderPlan contains information about: - Which regions need updating - Screen dimensions - Dirty region bounds
UI Cache Management¶
The RenderCoordinator manages view-specific caches:
Virtual List Controllers¶
Virtual list controllers handle efficient rendering of large lists by only rendering visible items.
Search Controllers¶
Search controllers manage search state and filtering for list views.
Cache Clearing¶
Performance Optimization¶
Animation Frequency¶
/// Reduce animation frequency for performance
func reduceAnimationFrequency()
/// Optimize rendering frequency
func optimizeRenderingFrequency()
/// Reset rendering frequency to default
func resetRenderingFrequency()
Scroll Batching¶
The coordinator tracks scroll events to batch rapid scroll operations:
State Diagram¶
stateDiagram-v2
[*] --> Clean: Initialize
Clean --> Dirty: markNeedsRedraw()
Clean --> FullRedraw: markViewTransition()
Dirty --> Rendering: shouldRedraw() = true
FullRedraw --> Rendering: shouldRedraw() = true
Rendering --> Clean: markDrawCompleted()
state Dirty {
[*] --> Header: markHeaderDirty()
[*] --> Sidebar: markSidebarDirty()
[*] --> MainPanel: markScrollOperation()
[*] --> StatusBar: markStatusBarDirty()
Header --> Multiple
Sidebar --> Multiple
MainPanel --> Multiple
StatusBar --> Multiple
}
Usage Examples¶
Basic Render Loop¶
while running {
// Check for input with adaptive sleep
if let key = getInput() {
renderCoordinator.markInputReceived()
processInput(key)
}
// Render if needed
if renderCoordinator.shouldRedraw() {
let plan = renderCoordinator.getRenderPlan(
screenRows: rows,
screenCols: cols
)
render(with: plan)
renderCoordinator.markDrawCompleted()
}
// Adaptive sleep
let sleepInterval = renderCoordinator.getAdaptiveSleepInterval()
try await Task.sleep(nanoseconds: sleepInterval)
}
Handling View Transitions¶
func changeView(to newView: ViewMode) {
viewCoordinator.currentView = newView
renderCoordinator.markViewTransition()
renderCoordinator.handleUICacheClearing()
}
Scroll Handling¶
func handleScroll(direction: ScrollDirection) {
viewCoordinator.scrollOffset += direction.delta
renderCoordinator.markScrollOperation()
}
Forcing Redraw¶
Performance Metrics¶
The RenderCoordinator tracks performance metrics through the PerformanceMonitor:
- Frame time
- Render frequency
- Idle poll counts
- Sleep interval distribution
Performance logs are output every 30 seconds (configurable via performanceLogInterval).
Integration Points¶
RenderOptimizer¶
Handles low-level dirty region tracking and render throttling.
PerformanceMonitor¶
Collects and reports performance metrics.
VirtualListController¶
Manages efficient rendering of large lists.
ListSearchController¶
Manages search state for list filtering.
Best Practices¶
- Always mark dirty regions specifically rather than full screen when possible
- Use markViewTransition() only for actual view changes
- Let adaptive polling manage sleep intervals automatically
- Clear UI caches when views change to prevent stale state
- Use forceRedraw() sparingly for critical updates