Display Timing System
Monolex uses an adaptive timing system that measures actual WebKit rendering performance and adjusts the entire backend accordingly. This provides optimal efficiency under varying system loads.Overview
┌─────────────────────────────────────────────────────────────────┐
│ ADAPTIVE TIMING ARCHITECTURE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Hardware Display (120Hz/60Hz/30Hz) │
│ │ │
│ ▼ │
│ WebKit measureWebKitFps() │
│ │ Measures actual rAF callback rate │
│ │ │
│ ▼ │
│ invoke("set_measured_fps", 30) │
│ │ │
│ ▼ │
│ Rust DisplayTiming │
│ │ effective_hz.store(30) │
│ │ │
│ ▼ │
│ All frame-based timing adapts: │
│ • tick_interval = frames(3) = 100ms │
│ • sync_deadline = frames(1) = 33ms │
│ • resize_settle = frames(6) = 200ms │
│ │
└─────────────────────────────────────────────────────────────────┘
Why Adaptive Timing?
WebKit’srequestAnimationFrame rate varies based on system conditions:
| Condition | Typical FPS | Cause |
|---|---|---|
| Normal | 60 fps | Standard operation |
| System load | 30 fps | CPU contention |
| Background tab | 1 fps | Browser throttling |
| ProMotion | 120 fps | High refresh display |
Two Control Layers
Monolex uses two complementary mechanisms to optimize rendering:┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ OUTER CONTROL (Hz) │ INNER CONTROL (ACK) │
│ ══════════════════════════════════════│════════════════════════════════════════════ │
│ │ │
│ Location: Rust backend │ Location: TypeScript frontend │
│ │ │
│ Mechanism: │ Mechanism: │
│ tick_interval = frames(3) │ waiting_ack flag │
│ 60Hz → 50ms │ true → block emit │
│ 30Hz → 100ms │ false → allow emit │
│ │ │
│ What it controls: │ What it controls: │
│ • tick() frequency │ • emit() permission │
│ • pull() frequency │ • Prevents TS overload │
│ • hash computation frequency │ │
│ • Overall render attempt rate │ │
│ │ │
│ Cost saved: │ Cost saved: │
│ • Rust CPU cycles │ • TS CPU cycles │
│ • tokio scheduler overhead │ • decode() calls │
│ │ • inject() calls │
│ │ • WebGL render calls │
│ │ │
│ When it helps: │ When it helps: │
│ System under load │ Frontend busy processing │
│ → WebKit RAF slows down │ → ACK delayed │
│ → Hz detection measures 30fps │ → waiting_ack stays true │
│ → tick_interval becomes 100ms │ → emit blocked until ACK │
│ → Rust does less work │ → Frontend not overwhelmed │
│ │ │
│ Proactive: │ Reactive: │
│ "Slow down before hitting wall" │ "Stop when wall is hit" │
│ │ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Hz Detection (Outer Control)
Controls how often the backend attempts to render:- Measures WebKit FPS using requestAnimationFrame
- Updates
effective_hzatomically in Rust - All frame-based durations scale automatically
┌─────────────────────────────────────────────────────────────────┐
│ FRAME-BASED TIMING CALCULATION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ "Wait for 3 frames" │
│ │ │
│ ├── @ 60Hz: 3 × 16.7ms = 50ms │
│ │ │
│ └── @ 30Hz: 3 × 33.3ms = 100ms │
│ │
│ Result: Same "3 frames" adapts to actual display speed │
│ │
└─────────────────────────────────────────────────────────────────┘
ACK Gating (Inner Control)
Controls whether each emit is allowed:- Frontend sends ACK after processing each frame
- Backend blocks next emit until ACK received
- Prevents frontend overload
┌─────────────────────────────────────────────────────────────────┐
│ ACK GATING LOGIC │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Is frontend still processing? │
│ │ │
│ ├── YES → Wait (don't send new frame) │
│ │ │
│ └── NO → Send update, then wait for confirmation │
│ │
│ This prevents overwhelming the display │
│ │
└─────────────────────────────────────────────────────────────────┘
Cost Savings
When Hz drops from 60 to 30:| Component | 60Hz | 30Hz | Reduction |
|---|---|---|---|
| tick() calls/sec | 20 | 10 | 50% |
| pull() calls/sec | 20 | 10 | 50% |
| emit() calls/sec | ~10 | ~10 | Same |
| Rust CPU usage | 100% | ~50% | 50% |
Measurement Process
┌─────────────────────────────────────────────────────────────────┐
│ FPS MEASUREMENT PROCESS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Step 1: Count 60 screen refreshes │
│ │ │
│ ▼ │
│ Step 2: Measure how long it took │
│ │ │
│ ▼ │
│ Step 3: Calculate: 60 ÷ time = FPS │
│ │ │
│ ▼ │
│ Result: Actual display performance (30, 45, 60, 120 fps) │
│ │
│ Example: │
│ ───────── │
│ 60 frames in 1000ms → 60 fps (normal) │
│ 60 frames in 2000ms → 30 fps (system under load) │
│ │
└─────────────────────────────────────────────────────────────────┘
Adaptive Intervals
- Normal (≥30 fps): Measure every 3 seconds
- Low (< 30 fps): Measure every 1 second
Self-Regulating System
The timing system creates a natural feedback loop:┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ SELF-REGULATING FEEDBACK LOOP │
│ │
│ System Load ↑ │
│ │ │
│ ▼ │
│ WebKit FPS ↓ (60 → 30) │
│ │ │
│ ▼ │
│ DisplayTiming measures drop │
│ │ │
│ ▼ │
│ tick_interval increases (50ms → 100ms) │
│ │ │
│ ▼ │
│ Rust does less work │
│ │ │
│ ▼ │
│ System Load ↓ │
│ │ │
│ ▼ │
│ (cycle continues) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
How It Works
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ DISPLAY TIMING SYSTEM │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ FRONTEND (Measures Display) │ │
│ │ ──────────────────────────── │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Count 60 │───▶│ Calculate │───▶│ Report to │ │ │
│ │ │ frames │ │ FPS │ │ Backend │ │ │
│ │ └────────────┘ └────────────┘ └─────┬──────┘ │ │
│ │ │ │ │
│ │ Repeats every 1-3 seconds │ │ │
│ │ │ │ │
│ └─────────────────────────────────────────────┼───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ BACKEND (Adapts Timing) │ │
│ │ ──────────────────────── │ │
│ │ │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
│ │ │ Receive │───▶│ Cap at │───▶│ Update all │ │ │
│ │ │ FPS value │ │ 20-120 Hz │ │ timers │ │ │
│ │ └────────────┘ └────────────┘ └────────────┘ │ │
│ │ │ │
│ │ Safety: Never below 20fps, never above display max │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Why Both Are Needed
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Hz Detection alone: │
│ ─────────────────── │
│ • Reduces tick frequency based on MEASURED display rate │
│ • But if measurement is wrong, could overwhelm frontend │
│ • Needs ACK as safety net │
│ │
│ ACK alone: │
│ ────────── │
│ • Prevents frontend overload (backpressure) │
│ • But Rust still does unnecessary work (wasted ticks) │
│ • Needs Hz Detection to reduce base rate │
│ │
│ Together: │
│ ───────── │
│ • Hz Detection = "Don't try too often" (proactive) │
│ • ACK = "Don't send if not ready" (reactive) │
│ • Optimal efficiency with safety fallback │
│ │
│ ═══════════════════════════════════════════════════════════════════════════════════ │
│ │
│ Hz Detection: "Know beforehand, run slower" (proactive rate adaptation) │
│ ACK Only: "Run fast, wait when blocked" (reactive backpressure) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Key Points
Hz Detection
Controls Rust backend tick rate. Proactive optimization that reduces work before overload.
ACK Gating
Protects frontend from overload. Reactive backpressure that blocks when busy.
Lock-Free
Uses AtomicU32 for thread-safe, lock-free Hz sharing across async tasks.
Self-Regulating
Creates natural feedback loop that prevents terminal from contributing to system load.
Related
- Pipeline Architecture - 10-stage pipeline with control points
- Dual-Loop Architecture - Data Loop vs Render Loop
- Control Layers - Hz (Outer) vs ACK (Inner)
- WebKit RAF - FPS measurement internals
- Tokio Tick - GridWorker tick architecture
- Buffer Injection - Frontend buffer handling
- Tauri IPC - IPC costs and batching
- Atomic State - State machine that uses frame-based timing
- Data Flow - How data moves through the pipeline