Dual-Loop Architecture
Monolex uses two distinct loops for terminal rendering: a Data Loop that ingests PTY output, and a Render Loop that pushes updates to the frontend.The Big Picture
Copy
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ ┃
┃ MONOLEX DUAL-LOOP ARCHITECTURE ┃
┃ ┃
┃ ┌──────────────────────────────────────────────────────────────────────────────────────┐ ┃
┃ │ │ ┃
┃ │ USER │ ┃
┃ │ │ │ ┃
┃ │ │ keyboard input │ ┃
┃ │ ▼ │ ┃
┃ │ ┌───────┐ ┌─────────────────────────────────────────────────────────────┐ │ ┃
┃ │ │ SHELL │ ◄──────►│ PTY DAEMON │ │ ┃
┃ │ │ bash │ stdin │ (pty-daemon-rust) │ │ ┃
┃ │ │ zsh │ stdout │ │ │ ┃
┃ │ └───────┘ └──────────────────────────┬──────────────────────────────────┘ │ ┃
┃ │ │ │ ┃
┃ │ │ Unix Socket │ ┃
┃ │ │ (raw bytes) │ ┃
┃ │ ▼ │ ┃
┃ │ ╔════════════════════════════════════════════════════════════════════════════════╗ │ ┃
┃ │ ║ ║ │ ┃
┃ │ ║ RUST BACKEND ║ │ ┃
┃ │ ║ ║ │ ┃
┃ │ ║ ┌─────────────────────────────────────────────────────────────────────────┐ ║ │ ┃
┃ │ ║ │ │ ║ │ ┃
┃ │ ║ │ ╭─────────────────────────────────────────────────────────────────╮ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ██████╗ █████╗ ████████╗ █████╗ │ │ ║ │ ┃
┃ │ ║ │ │ ██╔══██╗██╔══██╗╚══██╔══╝██╔══██╗ │ │ ║ │ ┃
┃ │ ║ │ │ ██║ ██║███████║ ██║ ███████║ │ │ ║ │ ┃
┃ │ ║ │ │ ██║ ██║██╔══██║ ██║ ██╔══██║ │ │ ║ │ ┃
┃ │ ║ │ │ ██████╔╝██║ ██║ ██║ ██║ ██║ │ │ ║ │ ┃
┃ │ ║ │ │ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ │ │ ║ │ ┃
┃ │ ║ │ │ L O O P (Data Ingestion) │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ Trigger: EVENT (PTY data arrival) │ │ ║ │ ┃
┃ │ ║ │ │ Control: NONE (always runs) │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ socket │───►│ feed() │───►│ VTE Parser │ │ │ ║ │ ┃
┃ │ ║ │ │ │ .recv() │ │ buffer │ │ (Alacritty) │ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ BSU/ESU │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ └─────────────┘ └─────────────┘ └──────────┬──────────┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ▼ │ │ ║ │ ┃
┃ │ ║ │ │ ┌─────────────────────┐ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ Grid<Cell> │ │ │ ║ │ ┃
┃ │ ║ │ │ │ (Ring Buffer) │ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ pending_data=true │ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ └─────────┼───────────┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ ╰───────────────────────────────────────────────────┼─────────────╯ │ ║ │ ┃
┃ │ ║ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ ║ │ ┃
┃ │ ║ │ ▼ │ ║ │ ┃
┃ │ ║ │ ┌────────────────────────────────────────────────────────┐ │ ║ │ ┃
┃ │ ║ │ │ SHARED STATE │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ • Grid<Cell> - Terminal content │ │ ║ │ ┃
┃ │ ║ │ │ • pending_data - "New data available" flag │ │ ║ │ ┃
┃ │ ║ │ │ • syncing - BSU/ESU sync flag │ │ ║ │ ┃
┃ │ ║ │ │ • waiting_ack - ACK pending flag │ │ ║ │ ┃
┃ │ ║ │ │ • line_hashes[] - For diff calculation │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ └────────────────────────────────────────────────────────┘ │ ║ │ ┃
┃ │ ║ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ ║ │ ┃
┃ │ ║ │ ╭───────────────────────────────────────────────────┼─────────────╮ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ██████╗ ███████╗███╗ ██╗██████╗ ███████╗██████╗│ │ │ ║ │ ┃
┃ │ ║ │ │ ██╔══██╗██╔════╝████╗ ██║██╔══██╗██╔════╝██╔══██╗ │ │ ║ │ ┃
┃ │ ║ │ │ ██████╔╝█████╗ ██╔██╗ ██║██║ ██║█████╗ ██████╔╝ │ │ ║ │ ┃
┃ │ ║ │ │ ██╔══██╗██╔══╝ ██║╚██╗██║██║ ██║██╔══╝ ██╔══██╗ │ │ ║ │ ┃
┃ │ ║ │ │ ██║ ██║███████╗██║ ╚████║██████╔╝███████╗██║ ██║ │ │ ║ │ ┃
┃ │ ║ │ │ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝ │ │ ║ │ ┃
┃ │ ║ │ │ L O O P (Render Push) ▼ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ Trigger: TIMER (tick_interval) │ │ ║ │ ┃
┃ │ ║ │ │ Control: Hz Detection (loop frequency) │ │ ║ │ ┃
┃ │ ║ │ │ ACK Gating (emit permission) │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐ │ │ ║ │ ┃
┃ │ ║ │ │ Hz Detection Gate │ │ │ ║ │ ┃
┃ │ ║ │ │ │ tick_interval = frames(3) │ │ ║ │ ┃
┃ │ ║ │ │ 60Hz: 50ms │ 30Hz: 100ms │ │ │ ║ │ ┃
┃ │ ║ │ │ └ ─ ─ ─ ─ ─ ─│─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ▼ │ │ ║ │ ┃
┃ │ ║ │ │ ┌─────────────────────┐ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ tick() │ Check timeouts │ │ ║ │ ┃
┃ │ ║ │ │ │ - sync_deadline │ - BSU timeout │ │ ║ │ ┃
┃ │ ║ │ │ │ - ack_deadline │ - ACK timeout │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ └──────────┬──────────┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ ▼ │ │ ║ │ ┃
┃ │ ║ │ │ ┌─────────────────────┐ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ pull() │ Build GridUpdate │ │ ║ │ ┃
┃ │ ║ │ │ │ - read Grid │ - Read cells │ │ ║ │ ┃
┃ │ ║ │ │ │ - compute hash │ - Calculate diff │ │ ║ │ ┃
┃ │ ║ │ │ │ - build update │ - Encode data │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ ┌───────────────────────────────────────────┐ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ ACK Gate │ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ waiting_ack? ──► true → return None │ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ false → return Some() │ │ │ ║ │ ┃
┃ │ ║ │ │ │ └───────────────────────────────────────────┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ └──────────┬──────────┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ Some(GridUpdate) │ │ ║ │ ┃
┃ │ ║ │ │ ▼ │ │ ║ │ ┃
┃ │ ║ │ │ ┌─────────────────────┐ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ │ emit() │ Send to frontend │ │ ║ │ ┃
┃ │ ║ │ │ │ Binary Channel │ waiting_ack = true │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ │ └──────────┬──────────┘ │ │ ║ │ ┃
┃ │ ║ │ │ │ │ │ ║ │ ┃
┃ │ ║ │ ╰──────────────┼──────────────────────────────────────────────────╯ │ ║ │ ┃
┃ │ ║ │ │ │ ║ │ ┃
┃ │ ║ └──────────────────┼──────────────────────────────────────────────────────┘ ║ │ ┃
┃ │ ║ │ ║ │ ┃
┃ │ ╚═════════════════════╪═════════════════════════════════════════════════════════╝ │ ┃
┃ │ │ │ ┃
┃ │ │ Tauri IPC (Binary) │ ┃
┃ │ ▼ │ ┃
┃ │ ╔════════════════════════════════════════════════════════════════════════════════╗│ ┃
┃ │ ║ ║│ ┃
┃ │ ║ FRONTEND (WebKit) ║│ ┃
┃ │ ║ ║│ ┃
┃ │ ║ ┌────────────────────────────────────────────────────────────────────────┐ ║│ ┃
┃ │ ║ │ │ ║│ ┃
┃ │ ║ │ onGridUpdate() │ ║│ ┃
┃ │ ║ │ │ │ ║│ ┃
┃ │ ║ │ ▼ │ ║│ ┃
┃ │ ║ │ decode() ───► inject() ───► xterm.js Buffer ───► WebGL Render │ ║│ ┃
┃ │ ║ │ │ │ ║│ ┃
┃ │ ║ │ │ │ ║│ ┃
┃ │ ║ │ ▼ │ ║│ ┃
┃ │ ║ │ requestAnimationFrame │ ║│ ┃
┃ │ ║ │ (measured by Hz Detection)│ ║│ ┃
┃ │ ║ │ │ │ ║│ ┃
┃ │ ║ │ ▼ │ ║│ ┃
┃ │ ║ │ invoke('ack') ◄────────────────────────────── Render Complete │ ║│ ┃
┃ │ ║ │ │ │ ║│ ┃
┃ │ ║ │ │ │ ║│ ┃
┃ │ ║ └────────┼───────────────────────────────────────────────────────────────┘ ║│ ┃
┃ │ ║ │ ║│ ┃
┃ │ ╚════════════╪═══════════════════════════════════════════════════════════════════╝│ ┃
┃ │ │ │ ┃
┃ │ │ ACK │ ┃
┃ │ │ │ ┃
┃ │ ▼ │ ┃
┃ │ waiting_ack = false │ ┃
┃ │ (unblock next emit) │ ┃
┃ │ │ ┃
┃ │ │ │ ┃
┃ │ │ Visual output │ ┃
┃ │ ▼ │ ┃
┃ │ │ ┃
┃ │ 👁️ USER │ ┃
┃ │ │ ┃
┃ └────────────────────────────────────────────────────────────────────────────────────┘ ┃
┃ ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
Simplified View
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ DATA LOOP (Event-Driven) RENDER LOOP (Timer-Driven) │
│ ════════════════════════ ═══════════════════════════ │
│ │
│ ╭───────────────────────╮ ╭───────────────────────╮ │
│ │ │ │ │ │
│ │ PTY Data Arrives │ │ tick_interval fires │◄── Hz Detection │
│ │ │ │ │ │ │ controls this │
│ │ ▼ │ │ ▼ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ feed() │ │ │ │ tick() │ │ │
│ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ VTE │ │ │ │ pull() │ │ │
│ │ │ Parser │ │ │ │ │◄──────┼── ACK controls this │
│ │ └─────┬─────┘ │ │ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ▼ │ │ ▼ │ │
│ │ ┌───────────┐ │ │ ┌───────────┐ │ │
│ │ │ Grid │───────┼──────────────┼──►│ emit() │ │ │
│ │ │ Update │ │ pending │ └─────┬─────┘ │ │
│ │ └───────────┘ │ _data=true │ │ │ │
│ │ │ │ │ │ │
│ ╰───────────────────────╯ │ ▼ │ │
│ │ ┌───────────┐ │ │
│ │ │ WebKit │ │ │
│ │ │ inject │ │ │
│ │ └─────┬─────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────┐ │ │
│ │ │ ACK │───────┼──► waiting_ack=false│
│ │ └───────────┘ │ │
│ │ │ │
│ ╰───────────────────────╯ │
│ │
│ No Control Two Controls: │
│ (Always runs) • Hz = Loop frequency │
│ • ACK = Emit permission │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Loop Interaction via Shared State
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ SHARED STATE (AtomicState) │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ Grid<Cell> │ │ pending_data │ │ syncing │ │ waiting_ack │ │ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │ │ (content) │ │ (flag) │ │ (flag) │ │ (flag) │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │ │
│ └───────────┼──────────────────┼──────────────────┼──────────────────┼───────────┘ │
│ │ │ │ │ │
│ │ │ │ │ │
│ ╔═══════════╪══════════════════╪══════════════════╪══════════════════╪═══════════╗ │
│ ║ │ │ │ │ ║ │
│ ║ DATA │ WRITE │ WRITE │ WRITE │ ║ │
│ ║ LOOP ▼ ▼ ▼ │ ║ │
│ ║ │ ║ │
│ ║ feed() ─────► Grid ────► pending_data=true │ ║ │
│ ║ ─────────────────► syncing=true (if BSU) │ ║ │
│ ║ │ ║ │
│ ╚════════════════════════════════════════════════════════════════════╪═══════════╝ │
│ │ │
│ │ │
│ ╔════════════════════════════════════════════════════════════════════╪═══════════╗ │
│ ║ │ ║ │
│ ║ RENDER │ READ │ READ │ READ │ READ ║ │
│ ║ LOOP ▼ ▼ ▼ ▼ WRITE ║ │
│ ║ ║ │
│ ║ pull() ◄───── Grid ◄───── pending_data? ◄─── syncing? ◄─── waiting_ack? ║ │
│ ║ │ ║ │
│ ║ ───────────────────────────────────────────────► waiting_ack=true ║ │
│ ║ (after emit) ║ │
│ ║ ║ │
│ ║ ack() ─────────────────────────────────────────────────► waiting_ack=false ║ │
│ ║ (from frontend) ║ │
│ ║ ║ │
│ ╚════════════════════════════════════════════════════════════════════════════════╝ │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Timing Comparison
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ TIME (ms) 0 10 20 30 40 50 60 70 80 90 100 110 120 │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ │
│ ════════════════════════════════════════════════════════════════════════════════ │
│ │
│ DATA LOOP ● ● ●●●●●●●●●● ● ● ●●●● │
│ (PTY data) │ │ │ │ │ │ │ │
│ │ │ │ (cat │ │ │ │ (ls) │
│ │ │ │ file) │ │ │ │ │
│ ▼ ▼ ▼ ▼ ▼ ▼ ▼ │
│ │
│ Grid █ █ █████████████ █ █ ████ │
│ Updates │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │
│ ════════════════════════════════════════════════════════════════════════════════ │
│ │
│ RENDER LOOP ┃ ┃ ┃ ┃ ┃ ┃ │
│ @ 60Hz ┃ ┃ ┃ ┃ ┃ ┃ │
│ (50ms tick) ┃ ┃ ┃ ┃ ┃ ┃ │
│ ▼ ▼ ▼ ▼ ▼ ▼ │
│ │
│ tick T T T T T T │
│ pull P→emit P→emit P→emit P→emit P→emit P→emit │
│ │
│ ════════════════════════════════════════════════════════════════════════════════ │
│ │
│ RENDER LOOP ┃ ┃ ┃ │
│ @ 30Hz ┃ ┃ ┃ │
│ (100ms tick) ┃ ┃ ┃ │
│ ▼ ▼ ▼ │
│ │
│ tick T T T │
│ pull P→emit P→emit P→emit │
│ │
│ ════════════════════════════════════════════════════════════════════════════════ │
│ │
│ Note: Data loop timing is IRREGULAR (depends on PTY data arrival) │
│ Render loop timing is REGULAR (depends on tick_interval) │
│ │
│ Data loop may fire 0 times, 1 time, or 100 times between render loop ticks │
│ Render loop always fires at fixed intervals (controlled by Hz) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Summary
| Aspect | Data Loop | Render Loop |
|---|---|---|
| Trigger | Event (PTY data) | Timer (tick_interval) |
| Frequency | Irregular (0~N per tick) | Regular (Hz-based) |
| Control | None | Hz + ACK |
| Purpose | Data ingestion | Render push |
| What it does | Parse → Store | Read → Build → Emit |
| CPU cost | Per-data (can’t reduce) | Per-tick (Hz reduces) |
Copy
┌─────────────────────────────────────────────────────────────────────────────────────────┐
│ │
│ Data Loop: "Process data whenever it arrives" (passive, reactive) │
│ │
│ Render Loop: "Periodically check state and send" (active, timer-based) │
│ │
│ ───────────────────────────────────────────────────────────────────────────────── │
│ │
│ Hz Detection: Controls the Render Loop "frequency" │
│ ACK Gating: Controls the Render Loop "emit permission" │
│ │
│ Data Loop cannot be controlled (data must be processed when it arrives) │
│ │
└─────────────────────────────────────────────────────────────────────────────────────────┘
Related
- Pipeline Architecture - Complete 10-stage pipeline
- Control Layers - Hz (Outer) vs ACK (Inner)
- Overview - Adaptive timing system overview