Control Layers: Hz (Outer) vs ACK (Inner)
The Monolex render pipeline has two control mechanisms that operate at different layers:- Hz Detection = Outer Control Layer (Rust side)
- ACK Gating = Inner Control Layer (TypeScript side)
The Layered Architecture
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ CONTROL LAYER ARCHITECTURE │
│ │
│ ┌───────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ╔═══════════════════════════════════════════════════════════════════════════╗ │ │
│ │ ║ ║ │ │
│ │ ║ OUTER CONTROL LAYER (Hz Detection) ║ │ │
│ │ ║ ═══════════════════════════════════════════════════════════════════════ ║ │ │
│ │ ║ ║ │ │
│ │ ║ Scope: Entire Rust backend ║ │ │
│ │ ║ Controls: tick_interval (how often we attempt to render) ║ │ │
│ │ ║ Measured from: WebKit requestAnimationFrame ║ │ │
│ │ ║ Applied to: Rust tokio timer ║ │ │
│ │ ║ ║ │ │
│ │ ║ ┌─────────────────────────────────────────────────────────────────────┐ ║ │ │
│ │ ║ │ │ ║ │ │
│ │ ║ │ tick() ───► tick() ───► tick() ───► tick() │ ║ │ │
│ │ ║ │ │ │ │ │ │ ║ │ │
│ │ ║ │ ▼ ▼ ▼ ▼ │ ║ │ │
│ │ ║ │ pull() pull() pull() pull() │ ║ │ │
│ │ ║ │ │ │ │ │ │ ║ │ │
│ │ ║ │ ▼ ▼ ▼ ▼ │ ║ │ │
│ │ ║ │ hash hash hash hash │ ║ │ │
│ │ ║ │ │ │ │ │ │ ║ │ │
│ │ ║ │ ▼ ▼ ▼ ▼ │ ║ │ │
│ │ ║ │ │ ║ │ │
│ │ ║ │ 60Hz: ████████████████████████████████████████ (20 ops/sec) │ ║ │ │
│ │ ║ │ 30Hz: ████████████████████ (10 ops/sec) │ ║ │ │
│ │ ║ │ │ ║ │ │
│ │ ║ └─────────────────────────────────────────────────────────────────────┘ ║ │ │
│ │ ║ ║ │ │
│ │ ║ │ ║ │ │
│ │ ║ │ emit() ║ │ │
│ │ ║ ▼ ║ │ │
│ │ ║ ║ │ │
│ │ ║ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ ║ │ │
│ │ ║ ║ │ │
│ │ ║ │ INNER CONTROL LAYER (ACK Gating) │ ║ │ │
│ │ ║ ═══════════════════════════════════════════════════════════════ ║ │ │
│ │ ║ │ │ ║ │ │
│ │ ║ Scope: TypeScript frontend ║ │ │
│ │ ║ │ Controls: waiting_ack (whether we can emit this frame) │ ║ │ │
│ │ ║ Mechanism: Backpressure from frontend ║ │ │
│ │ ║ │ │ ║ │ │
│ │ ║ ┌─────────────────────────────────────────────────────────────┐ ║ │ │
│ │ ║ │ │ │ ║ │ │
│ │ ║ │ onGridUpdate() ───► decode() ───► inject() │ ║ │ │
│ │ ║ │ │ │ │ │ ║ │ │
│ │ ║ │ ▼ ▼ │ ║ │ │
│ │ ║ │ │ BufferLine[] WebGL Render │ ║ │ │
│ │ ║ │ │ │ ║ │ │
│ │ ║ │ │ ▼ │ ║ │ │
│ │ ║ │ requestAnimationFrame │ ║ │ │
│ │ ║ │ │ │ │ ║ │ │
│ │ ║ │ ▼ │ ║ │ │
│ │ ║ │ │ ack() ◄────────────────────────── Render Complete │ ║ │ │
│ │ ║ │ │ │ ║ │ │
│ │ ║ │ │ │ │ ║ │ │
│ │ ║ └─────┼───────────────────────────────────────────────────────┘ ║ │ │
│ │ ║ │ │ │ ║ │ │
│ │ ║ │ waiting_ack = false ║ │ │
│ │ ║ │ │ │ ║ │ │
│ │ ║ └ ─ ─ ─ ─ ┼ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ ║ │ │
│ │ ║ │ ║ │ │
│ │ ║ ▼ ║ │ │
│ │ ║ (next emit allowed) ║ │ │
│ │ ║ ║ │ │
│ │ ╚═══════════════════════════════════════════════════════════════════════════╝ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Why “Outer” and “Inner”?
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ CONCENTRIC CONTROL LAYERS │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────────────────────────────┐ │ │
│ │ │ │ │ │
│ │ │ ┌─────────────────────────────────────────────────────────────────┐ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ │ 👁️ USER │ │ │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ │ └─────────────────────────────────────────────────────────┘ │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ INNER: ACK (TS Layer) │ │ │ │
│ │ │ │ - decode, inject, WebGL │ │ │ │
│ │ │ │ - Protects frontend from overload │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ └─────────────────────────────────────────────────────────────────┘ │ │ │
│ │ │ │ │ │
│ │ │ OUTER: Hz (Rust Layer) │ │ │
│ │ │ - tick, pull, hash, emit │ │ │
│ │ │ - Controls how often we attempt to render │ │ │
│ │ │ │ │ │
│ │ └─────────────────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ DATA INGESTION (No control - always runs) │ │
│ │ - PTY, VTE, Grid │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
| Layer | Control | Why This Name |
|---|---|---|
| Outer (Hz) | Rust | Wraps around the entire IPC boundary. Controls the rate of all render attempts. |
| Inner (ACK) | TypeScript | Inside the render path. Controls whether a specific emit is allowed. |
Side-by-Side Comparison
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ 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" │
│ │ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Pipeline Flow with Control Layers
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ DATA FLOW WITH CONTROL LAYERS │
│ │
│ │
│ PTY ──► feed() ──► VTE ──► Grid │
│ │ │
│ │ pending_data = true │
│ ▼ │
│ ╔═══════════════════════════════════════════════════════════════════════════════════╗ │
│ ║ ║ │
│ ║ OUTER LAYER (Hz Control) ║ │
│ ║ ──────────────────────────────────────────────────────────────────────────── ║ │
│ ║ ║ │
│ ║ tick_interval.tick() ◄──── Hz Detection sets interval ║ │
│ ║ │ ║ │
│ ║ ▼ ║ │
│ ║ ┌─────────────┐ ║ │
│ ║ │ tick() │ timeout checks ║ │
│ ║ └──────┬──────┘ ║ │
│ ║ │ ║ │
│ ║ ▼ ║ │
│ ║ ┌─────────────┐ ║ │
│ ║ │ pull() │ read grid, compute hash, build update ║ │
│ ║ │ │ ║ │
│ ║ │ ┌──────────────────────────────────────────────────────────────────────┐ ║ │
│ ║ │ │ │ ║ │
│ ║ │ │ INNER LAYER (ACK Control) │ ║ │
│ ║ │ │ ──────────────────────────────────────────────────────────────── │ ║ │
│ ║ │ │ │ ║ │
│ ║ │ │ waiting_ack? │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ │ ├── true ──► return None (don't emit) │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ │ └── false ──► return Some(GridUpdate) │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ │ ▼ │ ║ │
│ ║ │ │ ┌─────────────┐ │ ║ │
│ ║ │ │ │ emit() │ │ ║ │
│ ║ │ │ └──────┬──────┘ │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ │ │ IPC │ ║ │
│ ║ │ │ ▼ │ ║ │
│ ║ │ │ ┌─────────────┐ │ ║ │
│ ║ │ │ │ Frontend │ │ ║ │
│ ║ │ │ │ decode() │ │ ║ │
│ ║ │ │ │ inject() │ │ ║ │
│ ║ │ │ │ WebGL │ │ ║ │
│ ║ │ │ └──────┬──────┘ │ ║ │
│ ║ │ │ │ │ ║ │
│ ║ │ │ │ ack() │ ║ │
│ ║ │ │ ▼ │ ║ │
│ ║ │ │ waiting_ack = false │ ║ │
│ ║ │ │ │ ║ │
│ ║ │ └──────────────────────────────────────────────────────────────────────┘ ║ │
│ ║ │ │ ║ │
│ ║ └─────────────┘ ║ │
│ ║ ║ │
│ ╚═══════════════════════════════════════════════════════════════════════════════════╝ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Cost Reduction Summary
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ WHERE COSTS ARE REDUCED │
│ │
│ RUST BACKEND (Outer Layer) │ FRONTEND / TS (Inner Layer) │
│ ══════════════════════════════════ │ ══════════════════════════════════════════ │
│ │ │
│ tick() ← Hz reduces this │ │
│ tick() ← Hz reduces this │ │
│ pull() ← Hz reduces this │ │
│ hash compute ← Hz reduces this │ │
│ build_update() ← Hz reduces this │ │
│ │ │
│ ───────────────── emit() ────────────┼───────────────────────────────────────────── │
│ │ │ │
│ │ │ onGridUpdate() ← ACK reduces this │
│ │ │ decode() ← ACK reduces this │
│ │ │ inject() ← ACK reduces this │
│ │ │ WebGL render ← ACK reduces this │
│ │ │ │
│ │◄────────────────┼── ack() │
│ │ │
└────────────────────────────────────────┴───────────────────────────────────────────────┘
Summary Table
| Aspect | Outer (Hz) | Inner (ACK) |
|---|---|---|
| Layer | Rust backend | TypeScript frontend |
| Mechanism | tick_interval duration | waiting_ack flag |
| Controls | Render attempt frequency | Emit permission |
| Reduces | Rust CPU, scheduler overhead | TS CPU, WebGL calls |
| Strategy | Proactive | Reactive |
| Analogy | Factory production rate | Shipping gate check |
Key Insight
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Hz (Outer): Controls HOW OFTEN we try │
│ "Factory production rate" │
│ │
│ ACK (Inner): Controls WHETHER we can send │
│ "Shipping gate check" │
│ │
│ ─────────────────────────────────────────────────────────────────────────────────── │
│ │
│ Hz reduces Rust workload (outer layer savings) │
│ ACK protects TS from overload (inner layer protection) │
│ │
│ Together: Optimal efficiency across both layers │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Summary
Hz = Outer Control
Rust-side timer interval. Controls how often we attempt to render.
ACK = Inner Control
TypeScript-side backpressure. Controls whether we can emit this frame.
Proactive vs Reactive
Hz slows down before hitting wall. ACK stops when wall is hit.
Layered Protection
Two layers ensure optimal efficiency across entire pipeline.
Related
- Pipeline Architecture - 10-stage pipeline with control points
- Dual-Loop Architecture - Data Loop vs Render Loop
- Overview - Adaptive timing system overview