Actor + ACK Synergy
Actor orders the processing, ACK controls the emission. Together they form a complete flow control system.Two Patterns, Two Origins, One Goal
Copy
┌─────────────────────────────────────────────────────────────────┐
│ TWO PATTERNS WORKING TOGETHER │
├─────────────────────────────────────────────────────────────────┤
│ │
│ +----------------------------+------------------------------+ │
│ | ACTOR PATTERN | ACK PATTERN | │
│ +----------------------------+------------------------------+ │
│ | | | │
│ | Origin: Erlang (1973/1986) | Origin: TCP/IP (1974) | │
│ | | | │
│ | Purpose: Concurrency | Purpose: Flow control | │
│ | without locks | between endpoints | │
│ | | | │
│ | Mechanism: Message passing | Mechanism: Acknowledgment | │
│ | Single owner | "Ready for more" | │
│ | | | │
│ | Guarantees: Ordered | Guarantees: No overflow | │
│ | No races | Receiver-paced | │
│ | | | │
│ +----------------------------+------------------------------+ │
│ │
│ In MonoTerm, they work TOGETHER: │
│ │
│ Actor: "I process PTY data in order, no locks" │
│ | │
│ v │
│ ACK: "I only emit to frontend when it is ready" │
│ │
│ Combined: Ordered processing + Paced emission │
│ │
└─────────────────────────────────────────────────────────────────┘
Complete Data Flow
Copy
┌─────────────────────────────────────────────────────────────────┐
│ COMPLETE DATA FLOW: PTY TO SCREEN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ STAGE 1: PTY DAEMON (Rust Sidecar) │
│ ------------------------------------------------------- │
│ │
│ Shell/Command ---> PTY Master ---> Unix Socket ---> (Tauri) │
│ │
│ * Pure byte stream │
│ * Unbounded channel (no data loss) │
│ * No knowledge of ACK │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ STAGE 2: TAURI BACKEND (Rust) - ACTOR DOMAIN │
│ ------------------------------------------------------- │
│ │
│ +------------+ +-----------------------------------------+ │
│ | Socket | | SessionActor | │
│ | Reader |---->| | │
│ +------------+ | rx.recv() --> match cmd { | │
│ | HandlePtyData => { | │
│ | grid_worker.send() | │
│ | } | │
│ | } | │
│ | | │
│ | GUARANTEES: | │
│ | * Sequential processing | │
│ | * No lock contention | │
│ | * Order preserved | │
│ +-----------------+-----------------------+ │
│ | │
│ v │
│ +-----------------------------------------+ │
│ | GridWorker | │
│ | | │
│ | Alacritty VTE Parser | │
│ | | | │
│ | v | │
│ | State Absorber (overwrite, not queue) | │
│ | | | │
│ | v | │
│ | if !waiting_ack { <-- ACK CHECK | │
│ | emit("pty-grid") | │
│ | waiting_ack = true | │
│ | } | │
│ +-----------------------------------------+ │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ STAGE 3: FRONTEND (TypeScript) - ACK SOURCE │
│ ------------------------------------------------------- │
│ │
│ listen("pty-grid") --> xterm.js render --> invoke("grid_ack") │
│ | │
│ v │
│ "I am ready for more" │
│ │
│ GUARANTEES: │
│ * Renderer never overwhelmed │
│ * Screen always stable │
│ * No frame drops │
│ │
└─────────────────────────────────────────────────────────────────┘
ACK Handshake Mechanism
Copy
┌─────────────────────────────────────────────────────────────────┐
│ ACK STATE MACHINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ +----------------------------------+ │
│ | | │
│ +----------------v----------------+ emit() | │
│ | | ----------------+ │
│ | waiting_ack = false | │
│ | (READY TO EMIT) | │
│ | | │
│ +----------------+----------------+ │
│ | │
│ | new PTY data arrives │
│ | emit("pty-grid") │
│ | waiting_ack = true │
│ v │
│ +---------------------------------+ │
│ | | │
│ | waiting_ack = true | <-- More PTY data arrives │
│ | (WAITING FOR ACK) | State absorbs, no emit│
│ | | │
│ +----------------+----------------+ │
│ | │
│ | Frontend: invoke("grid_ack") │
│ | waiting_ack = false │
│ | │
│ +---------------------------------------------│
│ │
└─────────────────────────────────────────────────────────────────┘
Timeline Example
Copy
┌─────────────────────────────────────────────────────────────────┐
│ ACK TIMELINE │
├─────────────────────────────────────────────────────────────────┤
│ │
│ T=0ms PTY data, waiting_ack=false -> emit, waiting_ack=true │
│ T=5ms PTY data, waiting_ack=true -> State absorbs (NO emit) │
│ T=10ms PTY data, waiting_ack=true -> State absorbs (NO emit) │
│ T=15ms PTY data, waiting_ack=true -> State absorbs (NO emit) │
│ T=16ms Frontend renders, grid_ack() -> waiting_ack=false │
│ T=16ms New state ready -> emit, waiting_ack=true │
│ ... │
│ │
│ Result: 4 PTY updates, 2 emits, 0 frame drops, smooth 60fps │
│ │
└─────────────────────────────────────────────────────────────────┘
Why Both Patterns Are Needed
Copy
┌─────────────────────────────────────────────────────────────────┐
│ ACTOR WITHOUT ACK │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PTY: 1000 messages/sec │
│ | │
│ v │
│ Actor: process all 1000 sequentially (order OK) │
│ | │
│ v │
│ Emit: 1000 events/sec to Frontend │
│ | │
│ v │
│ Frontend: Can only render 60/sec │
│ | │
│ v │
│ Result: Event queue grows, memory grows, crash │
│ │
│ Actor guarantees ORDER, not PACE. │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ACK WITHOUT ACTOR │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PTY: 1000 messages/sec │
│ | │
│ v │
│ Mutex-protected state: lock contention, possible reordering │
│ | │
│ v │
│ ACK: Controls emission (pace OK) │
│ | │
│ v │
│ Result: State updates may race, inconsistent display │
│ │
│ ACK guarantees PACE, not ORDER. │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ACTOR + ACK TOGETHER │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PTY: 1000 messages/sec │
│ | │
│ v │
│ Actor: process all 1000 sequentially <-- ORDER │
│ | │
│ v │
│ State Absorber: coalesce into single state │
│ | │
│ v │
│ ACK: emit only when ready <-- PACE │
│ | │
│ v │
│ Frontend: receives 60 states/sec, all consistent │
│ | │
│ v │
│ Result: Smooth, ordered, memory-stable │
│ │
│ ACTOR + ACK = ORDER + PACE │
│ │
└─────────────────────────────────────────────────────────────────┘
The Synergy Diagram
Copy
┌─────────────────────────────────────────────────────────────────┐
│ ACTOR + ACK: UNIFIED VIEW │
├─────────────────────────────────────────────────────────────────┤
│ │
│ PTY Daemon │
│ +-----------+ │
│ | Unbounded | │
│ | Channel | -----------------------------------------+ │
│ +-----------+ | │
│ | | │
│ | Unix Socket | │
│ v | │
│ +--------------------------------------------------------+ │
│ | TAURI BACKEND | │
│ | | │
│ | +-----------+ | │
│ | | Reader | | │
│ | +-----------+ | │
│ | | tx.send(HandlePtyData) | │
│ | v | │
│ | +----------------------------------------------+ | │
│ | | SessionActor | | │
│ | | | | │
│ | | [ACTOR GUARANTEE: Sequential, No Locks] | | │
│ | | | | | │
│ | | v | | │
│ | | GridWorker.send(data) | | │
│ | +----------------------+-----------------------+ | │
│ | | | │
│ | v | │
│ | +----------------------------------------------+ | │
│ | | GridWorker | | │
│ | | | | │
│ | | Alacritty VTE Parser | | │
│ | | | | | │
│ | | v | | │
│ | | State Absorber (overwrites, always current) | | │
│ | | | | | │
│ | | v | | │
│ | | [ACK GUARANTEE: Emit only when ready] | | │
│ | | | | │
│ | +----------------------+-----------------------+ | │
│ | | | │
│ +-------------------------+------------------------------+ │
│ | │
│ | emit("pty-grid") │
│ v │
│ +--------------------------------------------------------+ │
│ | FRONTEND | │
│ | | │
│ | listen("pty-grid") --> xterm.js --> invoke("grid_ack") |---+
│ | |
│ +--------------------------------------------------------+
│ │
│ RESULT: 1000 updates/sec -> 60 screen updates/sec │
│ All ordered, no memory growth │
│ │
└─────────────────────────────────────────────────────────────────┘
Data Reduction Through Synergy
Copy
┌─────────────────────────────────────────────────────────────────┐
│ DATA REDUCTION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ INPUT: 1000 PTY updates/second │
│ │
│ +------------------+-------------+-------------+--------------+│
│ | Stage | Updates | Reduction | Cumulative |│
│ +------------------+-------------+-------------+--------------+│
│ | PTY Output | 1000/sec | - | 1000/sec |│
│ | | | | | |│
│ | v | | | |│
│ | Actor (order) | 1000/sec | 0% | 1000/sec |│
│ | | | | | (ordered) |│
│ | v | | | |│
│ | State Absorber | 1000/sec | 0%* | 1000/sec |│
│ | | | *processes | | (coalesced) |│
│ | v | all, stores | | |│
│ | ACK Gate | 60/sec | 94% | 60/sec |│
│ | | | | | |│
│ | v | | | |│
│ | Frontend Render | 60/sec | 0% | 60/sec |│
│ +------------------+-------------+-------------+--------------+│
│ │
│ TOTAL REDUCTION: 1000 -> 60 = 94% fewer events to frontend │
│ │
│ All 1000 updates are PROCESSED (no data loss) │
│ Only EMISSION is reduced │
│ │
└─────────────────────────────────────────────────────────────────┘
Summary
Copy
┌─────────────────────────────────────────────────────────────────┐
│ ACTOR + ACK: KEY TAKEAWAYS │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Actor guarantees ORDER (sequential, no races) │
│ 2. ACK guarantees PACE (emit only when frontend ready) │
│ 3. State Absorber bridges them (coalescing) │
│ 4. Together: ORDER + PACE + MEMORY STABILITY │
│ │
│ ----------------------------------------------------------- │
│ │
│ Neither pattern alone solves high-output terminal rendering. │
│ Together, they form a complete flow control system. │
│ │
└─────────────────────────────────────────────────────────────────┘