Atomic Loop
The heart of MonoTerm’s flicker-free rendering - inspired by Ink app patterns.Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ ║
║ ATOMIC LOOP ║
║ ║
║ "Virtual Synchronized Rendering for Terminals" ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
The Origin Story
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ HOW IT ALL STARTED ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ One day, we noticed something interesting about Ink apps... ║
║ ║
║ Ink = React for terminal UIs (npm, jest progress bars, etc.) ║
║ ║
║ They do something clever: ║
║ ║
║ 1. Hide cursor ║
║ 2. Draw everything ║
║ 3. Show cursor ║
║ ║
║ This means: Cursor visibility = Frame boundaries! ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
The Ink Pattern
Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ INK APP CURSOR PATTERN │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Time ──────────────────────────────────────────────────────────────────▶ │
│ │
│ Cursor │
│ State: VISIBLE ─┐ ┌─ VISIBLE ─┐ │
│ │ │ │ │
│ └─ HIDDEN ────────────┘ └─ HIDDEN ───▶ │
│ │ │ │
│ │ │ │
│ Frame 1 Content Frame 2 Content │
│ │
│ ════════════════════════════════════════════════════════════════════════════ │
│ │
│ Sequence: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ CSI ?25l (cursor hide) │ "I'm about to draw" │ │
│ │ ... draw content ... │ Frame content │ │
│ │ CSI ?25h (cursor show) │ "I'm done drawing" │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ CSI ?25l = ESC [ ? 2 5 l = Hide cursor │
│ CSI ?25h = ESC [ ? 2 5 h = Show cursor │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Think of it like a painter: They cover the canvas (hide cursor), paint the picture (draw content), then reveal the canvas (show cursor). You never see the painting in progress.
How MonoTerm Uses This
Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ATOMIC LOOP: FROM OBSERVATION TO IMPLEMENTATION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ OBSERVATION: │
│ ──────────── │
│ Programs that draw UI (npm, yarn, vitest, jest) all use cursor hide/show │
│ to prevent flicker. │
│ │
│ INSIGHT: │
│ ──────── │
│ We can detect these boundaries without any protocol changes! │
│ │
│ IMPLEMENTATION: │
│ ────────────── │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Incoming bytes │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────────────┐ │ │
│ │ │ Detect CSI ?25l │──▶ Start buffering │ │
│ │ │ (cursor hide) │ │ │
│ │ └───────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────────────┐ │ │
│ │ │ Buffer content │ (don't render yet) │ │
│ │ │ ... │ │ │
│ │ └───────────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌───────────────────┐ │ │
│ │ │ Detect CSI ?25h │──▶ Flush buffer to screen! │ │
│ │ │ (cursor show) │ │ │
│ │ └───────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ RESULT: │
│ ─────── │
│ Complete frames, no partial updates, no flicker! │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Virtual Synchronized Rendering
Why “Virtual”? Because we’re creating synchronization without explicit protocol support.Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ REAL vs VIRTUAL SYNCHRONIZATION │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ REAL (Hardware/Protocol) │
│ ──────────────────────── │
│ GPU waits for display's vertical blank (vsync) │
│ Requires hardware support │
│ │
│ ┌──────────┐ vsync ┌──────────┐ │
│ │ GPU │────────────────▶│ Display │ │
│ └──────────┘ └──────────┘ │
│ │
│ │
│ VIRTUAL (MonoTerm) │
│ ────────────────── │
│ Backend waits for application's "frame boundaries" │
│ No hardware needed - just pattern detection │
│ │
│ ┌──────────┐ cursor hide/show ┌──────────┐ │
│ │ Backend │───────────────────▶│ Frontend │ │
│ └──────────┘ (detected) └──────────┘ │
│ │
│ Same smooth result, different mechanism! │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Multiple Detection Methods
The Atomic Loop doesn’t rely on just one method. It has a priority hierarchy.Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SYNC DETECTION HIERARCHY (Priority Order) │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Priority 1: BSU/ESU (Explicit Protocol) │
│ ═══════════════════════════════════════ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ESC[?2026h ──────── content ──────── ESC[?2026l │ │
│ │ (Begin Sync) (End Sync) │ │
│ │ │ │
│ │ Most reliable. Modern apps use this. │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ │
│ Priority 2: Cursor Hide/Show (Ink Pattern) │
│ ═══════════════════════════════════════════ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ CSI ?25l ──────── content ──────── CSI ?25h │ │
│ │ (Hide) (Show) │ │
│ │ │ │
│ │ Works with npm, yarn, vitest, jest, etc. │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ │
│ Priority 3: Screen Clear (Implicit Sync) │
│ ════════════════════════════════════════ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Clear screen detected ──── wait 8ms ──── assume complete │ │
│ │ │ │
│ │ Fallback for apps that don't use cursor patterns. │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ │
│ Priority 4: Incomplete Sequence │
│ ═══════════════════════════════ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ ESC [ 3 1 ... (incomplete escape sequence) │ │
│ │ │ │ │
│ │ └── Wait for completion, don't render partial │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Suppression Rules
Higher priority methods suppress lower ones.Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SYNC SUPPRESSION DIAGRAM │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────┐ │
│ │ BSU Active │ │
│ │ (Highest Priority)│ │
│ └─────────┬─────────┘ │
│ │ │
│ │ Suppresses │
│ ▼ │
│ ┌───────────────────┐ │
│ │ Cursor Pattern │ │
│ │ (Medium) │ │
│ └─────────┬─────────┘ │
│ │ │
│ │ Suppresses │
│ ▼ │
│ ┌───────────────────┐ │
│ │ Implicit Sync │ │
│ │ (Lowest) │ │
│ └───────────────────┘ │
│ │
│ ════════════════════════════════════════════════════════════════════════════ │
│ │
│ Example: BSU active, cursor hide detected │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ESC[?2026h (BSU starts) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ CSI ?25l (cursor hide - IGNORED, BSU already active) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ... content ... │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ CSI ?25h (cursor show - IGNORED, BSU still active) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ESC[?2026l (ESU ends - NOW we flush!) │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Timeout Recovery
What if a cursor hide is never followed by cursor show?Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ TIMEOUT RECOVERY MECHANISM │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ Problem: "Lost" cursor show signal │
│ ───────────────────────────────── │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ CSI ?25l (cursor hide) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ... content ... │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ [Program crashes / loses connection / forgets] │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ CSI ?25h never comes! │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Screen frozen forever? ❌ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ Solution: Tick Timeout │
│ ───────────────────── │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ CSI ?25l (cursor hide, start timer) │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ... content ... │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ [16ms passes with no cursor show] │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ TIMEOUT! Force flush buffered content. │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ Screen updates, not frozen. ✅ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ 16ms = ~1 frame at 60Hz, reasonable wait time │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
Before and After
Copy
╔═══════════════════════════════════════════════════════════════════════════════╗
║ BEFORE: WITHOUT ATOMIC LOOP ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ npm install ║
║ ───────────── ║
║ ║
║ Screen update 1: ⠋ Installing... ║
║ Screen update 2: ⠙ Insta (partial!) ║
║ Screen update 3: ⠹ Installing d (partial!) ║
║ Screen update 4: ⠸ Installing dependencies (finally!) ║
║ ║
║ 4 updates, flickering, ugly ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
╔═══════════════════════════════════════════════════════════════════════════════╗
║ AFTER: WITH ATOMIC LOOP ║
╠═══════════════════════════════════════════════════════════════════════════════╣
║ ║
║ npm install ║
║ ───────────── ║
║ ║
║ Screen update 1: ⠋ Installing dependencies... ║
║ Screen update 2: ⠙ Installing dependencies... ║
║ Screen update 3: ⠹ Installing dependencies... ║
║ Screen update 4: ⠸ Installing dependencies... ║
║ ║
║ 4 updates, each complete, smooth animation ║
║ ║
╚═══════════════════════════════════════════════════════════════════════════════╝
Summary
Copy
┌─────────────────────────────────────────────────────────────────────────────────┐
│ ATOMIC LOOP SUMMARY │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ WHAT: Detects complete frame boundaries │
│ │
│ HOW: │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ 1. BSU/ESU (explicit protocol) ← Most reliable │ │
│ │ 2. Cursor pattern (Ink apps) ← Most common │ │
│ │ 3. Implicit sync (screen clear + timeout) ← Fallback │ │
│ │ 4. Incomplete seq (wait for completion) ← Safety net │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ WHY: │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ • No partial renders │ │
│ │ • No screen tearing │ │
│ │ • Works with existing programs (no changes needed) │ │
│ │ • "Virtual vsync" for terminals │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ORIGIN: Inspired by Ink app cursor hide/show pattern │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
It just works. You don’t need to configure anything. The Atomic Loop automatically detects frame boundaries from your existing terminal programs.