Skip to main content

Design Patterns in MonoTerm

This document explains the key design patterns used in MonoTerm’s architecture. These patterns are language-independent design solutions, not Rust-specific features.
┌─────────────────────────────────────────────────────────────────┐
│  PATTERN ORIGINS                                                │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   SessionActor Pattern          ACK Flow Control                │
│   ──────────────────            ────────────────                │
│                                                                 │
│   Origin: Erlang (1986)         Origin: TCP/IP (1974)           │
│   Level: Concurrency Pattern    Level: Network Protocol         │
│                                                                 │
│   Actor Model (1973)            TCP ACK (1974)                  │
│       │                             │                           │
│       ▼                             ▼                           │
│   Erlang/OTP                    Backpressure                    │
│       │                             │                           │
│       ▼                             ▼                           │
│   Akka (Scala), Orleans (C#)    Flow Control                    │
│       │                             │                           │
│       ▼                             ▼                           │
│   Tokio MPSC (Rust)             MonoTerm ACK                    │
│                                                                 │
│   Conclusion: Design pattern level, not system level            │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

The Problem: Concurrent Access

Multiple parts of the system need to access session data simultaneously.
┌─────────────────────────────────────────────────────────────────┐
│  THE CONCURRENT ACCESS PROBLEM                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Places that access "sessions" data:                           │
│                                                                 │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │  1. User creates new tab     Insert new session         │   │
│   │  2. PTY data arrives         Write to session           │   │
│   │  3. User closes tab          Remove session             │   │
│   │  4. Resize event             Modify session             │   │
│   │  5. Tray menu lists tabs     Read all sessions          │   │
│   │                                                         │   │
│   │  These can happen SIMULTANEOUSLY!                       │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Dangerous Situation:                                          │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │   Thread A: Processing PTY data...                      │   │
│   │             Get session "tab-1"                         │   │
│   │             Write data...         While writing!        │   │
│   │                                        │                │   │
│   │   Thread B: User closes tab!           ▼                │   │
│   │             Remove "tab-1"     Simultaneous delete!     │   │
│   │                                                         │   │
│   │   Result: Crash!                                        │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Solution 1: Mutex (Traditional)

The traditional approach uses locks.
┌─────────────────────────────────────────────────────────────────┐
│  MUTEX APPROACH                                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Must acquire lock on every access                             │
│                                                                 │
│   Thread A          Thread B          Thread C                  │
│      │                 │                 │                      │
│      ▼                 ▼                 ▼                      │
│   lock()           lock()            lock()                     │
│      │                │ WAIT             │ WAIT                 │
│   working...          │                  │                      │
│      │                │                  │                      │
│   unlock()            │                  │                      │
│                    working...            │                      │
│                       │                  │                      │
│                    unlock()              │                      │
│                                      working...                 │
│                                                                 │
│   Problems:                                                     │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Lock Contention: Only one can work at a time           │   │
│   │  Deadlock Risk: A waits for B, B waits for A = frozen   │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Solution 2: Actor Pattern (MonoTerm)

MonoTerm uses the Actor pattern - no locks needed.
┌─────────────────────────────────────────────────────────────────┐
│  ACTOR PATTERN                                                  │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Core Idea:                                                    │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Don't touch data directly                              │   │
│   │  Send a message to the person in charge (Actor)         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│                                                                 │
│   Thread A ──┐                                                  │
│              │     ┌─────────────┐                              │
│   Thread B ──┼────▶│  MPSC       │────▶ SessionActor            │
│              │     │  Channel    │      (single owner)          │
│   Thread C ──┘     └─────────────┘           │                  │
│                                              ▼                  │
│                                          sessions               │
│                                          (data)                 │
│                                                                 │
│   Rules:                                                        │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Anyone can put messages in Channel (simultaneous OK)   │   │
│   │  Only Actor accesses sessions (1 person = no conflicts) │   │
│   │  No locks needed!                                       │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   ═══════════════════════════════════════════════════════════   │
│                                                                 │
│   Analogy:                                                      │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │   Mutex = Multiple people trying to open refrigerator   │   │
│   │           Take turns one by one (Lock)                  │   │
│   │           Pushing causes accidents (Deadlock)           │   │
│   │                                                         │   │
│   │   Actor = One refrigerator manager                      │   │
│   │           Leave note "get me a coke please"             │   │
│   │           Manager processes requests in order           │   │
│   │           Conflicts are impossible                      │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

SessionActor Structure

The SessionActor owns all session state.
┌─────────────────────────────────────────────────────────────────┐
│  SESSIONACTOR STRUCTURE                                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Message Types (what requests are possible):                   │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │    CreateSession     Create new tab                     │   │
│   │    CloseSession      Close tab                          │   │
│   │    WriteData         Write to PTY                       │   │
│   │    Resize            Change terminal size               │   │
│   │    GridAck           ACK received from frontend         │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Actor Owns:                                                   │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │    sessions          Session data (Actor only!)         │   │
│   │    grid_workers      Per-session workers                │   │
│   │    rx                Mailbox (receives messages)        │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Actor Main Loop:                                              │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │    loop {                                               │   │
│   │        Get message from mailbox                         │   │
│   │                                                         │   │
│   │        Match message type:                              │   │
│   │            CreateSession  Create new session            │   │
│   │            CloseSession   Remove session                │   │
│   │            WriteData      Write to session              │   │
│   │            Resize         Resize session                │   │
│   │            GridAck        Open ACK gate                 │   │
│   │    }                                                    │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Actor Pattern Benefits

┌─────────────────────────────────────────────────────────────────┐
│  ACTOR BENEFITS                                                 │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Frontend             Tauri Commands        SessionActor       │
│   (TypeScript)         (multiple threads)    (single owner)     │
│                                                                 │
│   ┌─────────┐                                                   │
│   │ New Tab │────┐                                              │
│   └─────────┘    │                                              │
│                  │      ┌──────────┐       ┌─────────────────┐  │
│   ┌─────────┐    ├─────▶│ MPSC     │──────▶│ SessionActor   │   │
│   │ Resize  │────┤      │ Channel  │       │                │   │
│   └─────────┘    │      └──────────┘       │ sessions: {...} │  │
│                  │                          │      ↑          │ │
│   ┌─────────┐    │                          │ Only accessible │ │
│   │Close Tab│────┘                          │ from here!      │ │
│   └─────────┘                               └─────────────────┘ │
│                                                                 │
│   ┌─────────┐                                     │             │
│   │PTY Data │─────────────────────────────────────┘             │
│   └─────────┘                                                   │
│                                                                 │
│   ═══════════════════════════════════════════════════════════   │
│                                                                 │
│   Actor Benefits:                                               │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  No lock contention    (single owner)                   │   │
│   │  No deadlocks          (no locks!)                      │   │
│   │  Guaranteed ordering   (MPSC channel)                   │   │
│   │  Easy to reason about  (one thread owns state)          │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Why Rust?

The patterns work in any language, but Rust provides additional benefits.
┌─────────────────────────────────────────────────────────────────┐
│  WHY RUST FOR MONOTERM                                          │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   Patterns work anywhere:                                       │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │   JavaScript    Python    Go    Rust                    │   │
│   │       ✓           ✓       ✓       ✓                     │   │
│   │                                                         │   │
│   │   Actor pattern and ACK flow control work in all        │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Where Rust matters:                                           │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │   1. VTE Parsing (Alacritty)                            │   │
│   │      Called thousands of times per second               │   │
│   │      GC would cause stuttering                          │   │
│   │                                                         │   │
│   │   2. Cell Processing                                    │   │
│   │      120 cols x 40 rows = 4,800 cells                   │   │
│   │      Needs native speed                                 │   │
│   │                                                         │   │
│   │   3. PTY I/O                                            │   │
│   │      Unix socket communication                          │   │
│   │      Needs system-level access                          │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Architecture choice:                                          │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │                                                         │   │
│   │   Option A: VTE(Rust) → IPC → SessionActor(JS) → IPC    │   │
│   │                                                         │   │
│   │   Option B: VTE(Rust) + SessionActor(Rust) → IPC        │   │
│   │             ↑ MonoTerm uses this                        │   │
│   │                                                         │   │
│   │   IPC count: A = 2 times, B = 1 time                    │   │
│   │                                                         │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Complete Architecture

┌─────────────────────────────────────────────────────────────────┐
│  PATTERNS ACROSS UNIX SOCKET                                    │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   PTY Daemon              Unix Socket            Tauri Backend  │
│   ──────────              ───────────            ─────────────  │
│                                                                 │
│   ┌─────────┐                                   ┌─────────────┐ │
│   │  Shell  │                                   │SessionActor │ │
│   │  bash   │────────────────────────────────▶  │             │ │
│   │  zsh    │           Raw bytes               │  Sessions   │ │
│   │  fish   │                                   │  Workers    │ │
│   └─────────┘                                   └──────┬──────┘ │
│                                                        │        │
│                                                        ▼        │
│                                               ┌─────────────┐   │
│                                               │  VTE Parser │   │
│                                               │ (Alacritty) │   │
│                                               └──────┬──────┘   │
│                                                      │          │
│                                                      ▼          │
│                               Tauri IPC     ┌─────────────┐     │
│   Frontend ◀────────────────────────────────│ Cell Convert│     │
│   (xterm.js WebGL)                          │  + DiffHint │     │
│                                             └─────────────┘     │
│                                                                 │
│   Pattern Summary:                                              │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  SessionActor: Lock-free concurrency via MPSC           │   │
│   │  ACK Flow: Frontend-driven backpressure                 │   │
│   │  Sidecar: PTY daemon survives app crash                 │   │
│   │  DiffHint: Intelligent partial rendering                │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Summary

┌─────────────────────────────────────────────────────────────────┐
│  DESIGN PATTERNS SUMMARY                                        │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   SessionActor / ACK Flow Control:                              │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  Level: Design Pattern (language-independent)           │   │
│   │  Origin: Erlang (Actor), TCP (ACK)                      │   │
│   │  Rust required? NO                                      │   │
│   │  Works in JS/Python/Go? YES                             │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Where Rust is critical:                                       │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  VTE Parsing (Alacritty)    Performance critical        │   │
│   │  PTY I/O (Unix socket)      System level                │   │
│   │  Cell Processing            Better without GC           │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
│   Why this architecture:                                        │
│   ┌─────────────────────────────────────────────────────────┐   │
│   │  VTE parsing is in Rust anyway                          │   │
│   │  SessionActor in Rust reduces IPC overhead              │   │
│   │  Patterns are high-level, placement determines perf     │   │
│   └─────────────────────────────────────────────────────────┘   │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘