Skip to main content

Epoch Synchronization

EPOCH is MonoTerm’s solution for preventing resize race conditions between frontend and backend.

The Resize Problem

When a terminal is resized, multiple components must be updated synchronously.
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  TERMINAL RESIZE INVOLVES MULTIPLE COMPONENTS                          β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    When user resizes the window:                                       β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”             β•‘
β•‘    β”‚               β”‚  β”‚               β”‚  β”‚               β”‚             β•‘
β•‘    β”‚   xterm.js    β”‚  β”‚    Rust       β”‚  β”‚     PTY       β”‚             β•‘
β•‘    β”‚   (Frontend)  β”‚  β”‚   (Backend)   β”‚  β”‚   (Daemon)    β”‚             β•‘
β•‘    β”‚               β”‚  β”‚               β”‚  β”‚               β”‚             β•‘
β•‘    β”‚  120 x 40     β”‚  β”‚   120 x 40    β”‚  β”‚   120 x 40    β”‚             β•‘
β•‘    β”‚               β”‚  β”‚               β”‚  β”‚               β”‚             β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜             β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    All THREE must have the SAME dimensions.                            β•‘
β•‘    If they disagree, rendering will be corrupted.                      β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

The Race Condition

Resize notifications travel at different speeds through the system.
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  THE RACE CONDITION                                                    β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Timeline of a resize from 80x24 to 120x40:                          β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Time   xterm.js         Rust Backend        PTY/Shell               β•‘
β•‘    ────   ─────────────    ─────────────       ──────────              β•‘
β•‘                                                                        β•‘
β•‘    T0     80 x 24          80 x 24             80 x 24                 β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘           β”‚ User resizes    β”‚                   β”‚                      β•‘
β•‘           v window          β”‚                   β”‚                      β•‘
β•‘    T1     120 x 40          β”‚                   β”‚                      β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘           β”‚ Send resize     β”‚                   β”‚                      β•‘
β•‘           β”‚ request ──────▢ β”‚                   β”‚                      β•‘
β•‘           β”‚                 v                   β”‚                      β•‘
β•‘    T2     120 x 40         80 x 24             β”‚                       β•‘
β•‘           β”‚                 β”‚ Processing...     β”‚                      β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘    T3     β”‚                 β”‚ GridUpdate        β”‚                      β•‘
β•‘           β”‚                 β”‚ (80x24) sent!     β”‚                      β•‘
β•‘           β”‚ ◀────────────── β”‚                   β”‚                      β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘           β”‚ !!! SIZE        β”‚                   β”‚                      β•‘
β•‘           β”‚   MISMATCH!     β”‚                   β”‚                      β•‘
β•‘           β”‚   120x40 !=     β”‚                   β”‚                      β•‘
β•‘           β”‚   80x24         v                   β”‚                      β•‘
β•‘    T4     β”‚                120 x 40             β”‚                      β•‘
β•‘           β”‚                 β”‚ Forward to PTY    β”‚                      β•‘
β•‘           β”‚                 β”‚ ───────────────▢  β”‚                      β•‘
β•‘           β”‚                 β”‚                   v                      β•‘
β•‘    T5     β”‚                 β”‚                  120 x 40                β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘    T6     β”‚                 β”‚ GridUpdate        β”‚                      β•‘
β•‘           β”‚ ◀────────────── β”‚ (120x40) sent     β”‚                      β•‘
β•‘           β”‚                 β”‚                   β”‚                      β•‘
β•‘           β”‚ Sizes match OK  β”‚                   β”‚                      β•‘
β•‘           v                 v                   v                      β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    The Problem:                                                        β•‘
β•‘                                                                        β•‘
β•‘    At T3, xterm.js receives a GridUpdate with dimensions               β•‘
β•‘    80x24, but xterm.js is already 120x40.                              β•‘
β•‘                                                                        β•‘
β•‘    If we inject this update, text will wrap incorrectly,               β•‘
β•‘    cursor will be at wrong position, display will be corrupt.          β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

EPOCH: The Solution

EPOCH is a version number that increments on each resize.
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  EPOCH VERSIONING                                                      β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Concept:                                                            β•‘
β•‘                                                                        β•‘
β•‘    - Each resize increments a counter called "EPOCH"                   β•‘
β•‘    - EPOCH is included in every GridUpdate                             β•‘
β•‘    - Frontend rejects updates with old EPOCH                           β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β•‘
β•‘    β”‚                                                              β”‚    β•‘
β•‘    β”‚   Frontend                       Backend                     β”‚    β•‘
β•‘    β”‚                                                              β”‚    β•‘
β•‘    β”‚   currentEpoch: 5                                            β”‚    β•‘
β•‘    β”‚                                                              β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ User resizes window          β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ currentEpoch++ (now 6)       β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ resize_session(epoch: 6)     β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ ────────────────────────────▢ β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚ Store epoch = 6      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚    GridUpdate (epoch: 5)     β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ ◀─────────────────────────── β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ 5 < 6 ──▢ DISCARD            β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ (stale update)               β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚    GridUpdate (epoch: 6)     β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ ◀─────────────────────────── β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ 6 == 6 ──▢ ACCEPT            β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚ (current update)             β”‚                      β”‚    β•‘
β•‘    β”‚        β”‚                              β”‚                      β”‚    β•‘
β•‘    β”‚        v                              v                      β”‚    β•‘
β•‘    β”‚                                                              β”‚    β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Rule:                                                               β•‘
β•‘                                                                        β•‘
β•‘    if (update.epoch < currentEpoch) {                                  β•‘
β•‘        // This update was generated before the resize                  β•‘
β•‘        // DISCARD IT - dimensions are wrong                            β•‘
β•‘    }                                                                   β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Frontend-Initiated Pattern

The epoch is managed by the frontend, not the backend.
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  FRONTEND-INITIATED EPOCH PATTERN                                      β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Why Frontend Controls Epoch:                                        β•‘
β•‘                                                                        β•‘
β•‘    The frontend knows FIRST when a resize happens (xterm.js event).    β•‘
β•‘    It must increment epoch BEFORE sending resize to backend.           β•‘
β•‘    This ensures any in-flight GridUpdates are marked as stale.         β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β•‘
β•‘    β”‚                                                          β”‚       β•‘
β•‘    β”‚  Frontend (xterm.js)           Backend (Rust)            β”‚       β•‘
β•‘    β”‚                                                          β”‚       β•‘
β•‘    β”‚  1. Window resize event        β”‚                         β”‚       β•‘
β•‘    β”‚     detected                   β”‚                         β”‚       β•‘
β•‘    β”‚                                β”‚                         β”‚       β•‘
β•‘    β”‚  2. prepareResize()            β”‚                         β”‚       β•‘
β•‘    β”‚     currentEpoch++             β”‚                         β”‚       β•‘
β•‘    β”‚     (now epoch=6)              β”‚                         β”‚       β•‘
β•‘    β”‚                                β”‚                         β”‚       β•‘
β•‘    β”‚  3. requestResize(cols, rows,  β”‚                         β”‚       β•‘
β•‘    β”‚     epoch=6)                   β”‚                         β”‚       β•‘
β•‘    β”‚     ───────────────────────────▢ resize(c, r, epoch=6)   β”‚       β•‘
β•‘    β”‚                                β”‚ epoch = 6               β”‚       β•‘
β•‘    β”‚                                β”‚                         β”‚       β•‘
β•‘    β”‚  4. Any GridUpdate with        β”‚                         β”‚       β•‘
β•‘    β”‚     epoch < 6 is REJECTED      β”‚                         β”‚       β•‘
β•‘    β”‚                                β”‚                         β”‚       β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Complete EPOCH Flow

Step-by-step walkthrough of a resize with EPOCH.
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  EPOCH FLOW: RESIZE FROM 80x24 TO 120x40                               β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘   Frontend                 Backend                   PTY               β•‘
β•‘   (epoch=5)               (epoch=5)               (80x24)              β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘  ─────┼───────────────────────┼───────────────────────┼─────          β•‘
β•‘   1   β”‚ Window resize event   β”‚                       β”‚                β•‘
β•‘       β”‚ xterm now 120x40      β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘  ─────┼───────────────────────┼───────────────────────┼─────          β•‘
β•‘   2   β”‚ prepareResize()       β”‚                       β”‚                β•‘
β•‘       β”‚ epoch++ (now 6)       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚ requestResize({       β”‚                       β”‚                β•‘
β•‘       β”‚   cols: 120,          β”‚                       β”‚                β•‘
β•‘       β”‚   rows: 40,           β”‚                       β”‚                β•‘
β•‘       β”‚   epoch: 6            β”‚                       β”‚                β•‘
β•‘       β”‚ }) ───────────────────▢                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘  ─────┼───────────────────────┼───────────────────────┼─────          β•‘
β•‘   3   β”‚                       β”‚ Meanwhile, shell      β”‚                β•‘
β•‘       β”‚                       β”‚ outputs data...       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚ GridUpdate (epoch=5)  β”‚                β•‘
β•‘       │◀──────────────────────│                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚ DISCARD (5 < 6)       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘  ─────┼───────────────────────┼───────────────────────┼─────          β•‘
β•‘   4   β”‚                       β”‚ Process resize        β”‚                β•‘
β•‘       β”‚                       β”‚ epoch = 6             β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚ resize_pty(120,40) ───┼────▢           β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘  ─────┼───────────────────────┼───────────────────────┼─────          β•‘
β•‘   5   β”‚                       β”‚                       β”‚ 120x40         β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚ Shell re-renders...   β”‚                β•‘
β•‘       β”‚                       │◀──────────────────────│                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚ GridUpdate (epoch=6)  β”‚                β•‘
β•‘       │◀──────────────────────│                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       β”‚ ACCEPT (6 >= 6)       β”‚                       β”‚                β•‘
β•‘       β”‚ Inject to display     β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘  ─────┼───────────────────────┼───────────────────────┼─────          β•‘
β•‘   6   β”‚ Display correct       β”‚                       β”‚                β•‘
β•‘       β”‚ 120x40 content        β”‚                       β”‚                β•‘
β•‘       β”‚                       β”‚                       β”‚                β•‘
β•‘       v                       v                       v                β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Double Verification

Even with EPOCH, we verify actual dimensions match.
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  DOUBLE VERIFICATION                                                   β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    EPOCH alone is not enough. We also check dimensions:                β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Validation Steps:                                                   β•‘
β•‘                                                                        β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚  Step 1: EPOCH Check                        β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚  update.epoch < currentEpoch ?              β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚    YES ──▢ DISCARD (stale)                  β”‚                     β•‘
β•‘    β”‚    NO  ──▢ Continue to Step 2               β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β•‘
β•‘                          β”‚                                             β•‘
β•‘                          v                                             β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚  Step 2: Dimension Check                    β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚  update.cols == xterm.cols AND              β”‚                     β•‘
β•‘    β”‚  update.rows == xterm.rows ?                β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚    NO  ──▢ Request backend resize           β”‚                     β•‘
β•‘    β”‚          Skip injection                     β”‚                     β•‘
β•‘    β”‚    YES ──▢ Continue to Step 3               β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β•‘
β•‘                          β”‚                                             β•‘
β•‘                          v                                             β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚  Step 3: Inject to Display                  β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β”‚  Safe to inject - all checks passed         β”‚                     β•‘
β•‘    β”‚                                             β”‚                     β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                     β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Why Both Checks?                                                    β•‘
β•‘                                                                        β•‘
β•‘    EPOCH: Catches updates generated before resize                      β•‘
β•‘    Dimension: Catches unexpected size differences                      β•‘
β•‘                                                                        β•‘
β•‘    Belt AND suspenders. Both are needed for safety.                    β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Why Not Just Use Dimensions?

Why do we need EPOCH when we could just compare dimensions?
╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  WHY EPOCH IS NECESSARY                                                β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Scenario: Rapid Resize                                              β•‘
β•‘                                                                        β•‘
β•‘    User quickly resizes: 80x24 ──▢ 120x40 ──▢ 80x24                    β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Time    Action                        Dimensions                    β•‘
β•‘    ────    ──────                        ──────────                    β•‘
β•‘    T1      Start                         80 x 24                       β•‘
β•‘    T2      Resize to 120x40              120 x 40                      β•‘
β•‘    T3      GridUpdate (80x24) in flight  ...                           β•‘
β•‘    T4      Resize back to 80x24          80 x 24                       β•‘
β•‘    T5      GridUpdate (80x24) arrives    ...                           β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    Without EPOCH:                                                      β•‘
β•‘                                                                        β•‘
β•‘    At T5, dimensions match (80x24 == 80x24).                           β•‘
β•‘    But this update was generated at T1, before T2's resize!            β•‘
β•‘    The content is STALE - cursor position is wrong.                    β•‘
β•‘                                                                        β•‘
β•‘    Dimension check alone: PASS (80 == 80, 24 == 24)                    β•‘
β•‘    But update is stale:   WRONG CONTENT!                               β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    With EPOCH:                                                         β•‘
β•‘                                                                        β•‘
β•‘    T1: epoch = 5, generate update (epoch=5, 80x24)                     β•‘
β•‘    T2: epoch++ = 6, resize to 120x40                                   β•‘
β•‘    T4: epoch++ = 7, resize to 80x24                                    β•‘
β•‘    T5: update (epoch=5) arrives, currentEpoch=7                        β•‘
β•‘                                                                        β•‘
β•‘    EPOCH check: 5 < 7 ──▢ DISCARD                                      β•‘
β•‘                                                                        β•‘
β•‘    We correctly reject the stale update!                               β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•

Summary

╔═══════════════════════════════════════════════════════════════════════╗
β•‘                                                                        β•‘
β•‘  EPOCH SYNCHRONIZATION IN ONE DIAGRAM                                  β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘       FRONTEND                              BACKEND                    β•‘
β•‘    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”                  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”              β•‘
β•‘    β”‚                β”‚                  β”‚                β”‚              β•‘
β•‘    β”‚  currentEpoch  β”‚                  β”‚   Grid State   β”‚              β•‘
β•‘    β”‚     β”Œβ”€β”€β”€β”      β”‚                  β”‚   .epoch       β”‚              β•‘
β•‘    β”‚     β”‚ 5 β”‚      β”‚                  β”‚     β”Œβ”€β”€β”€β”      β”‚              β•‘
β•‘    β”‚     β””β”€β”€β”€β”˜      β”‚                  β”‚     β”‚ 5 β”‚      β”‚              β•‘
β•‘    β”‚        β”‚       β”‚                  β”‚     β””β”€β”€β”€β”˜      β”‚              β•‘
β•‘    β”‚        β”‚       β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚ On resize:     β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚ prepareResize()β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚ epoch++ (now 6)β”‚ requestResize()  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚                │─────────────────▢│ epoch = 6      β”‚              β•‘
β•‘    β”‚                β”‚                  β”‚                β”‚              β•‘
β•‘    β”‚                β”‚  GridUpdate      β”‚        β”‚       β”‚              β•‘
β•‘    β”‚                β”‚  (epoch: 5)      β”‚        β”‚       β”‚              β•‘
β•‘    β”‚        │◀──────┼──────────────────│        β”‚       β”‚              β•‘
β•‘    β”‚        β”‚       β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚   5 < 6 ?      β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚   YES ──▢ DROP β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚                β”‚  GridUpdate      β”‚        β”‚       β”‚              β•‘
β•‘    β”‚                β”‚  (epoch: 6)      β”‚        β”‚       β”‚              β•‘
β•‘    β”‚        │◀──────┼──────────────────│        β”‚       β”‚              β•‘
β•‘    β”‚        β”‚       β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚   6 < 6 ?      β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚   NO ──▢ ACCEPTβ”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚        β”‚       β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚   Inject to    β”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚   xterm displayβ”‚                  β”‚        β”‚       β”‚              β•‘
β•‘    β”‚                β”‚                  β”‚                β”‚              β•‘
β•‘    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜              β•‘
β•‘                                                                        β•‘
β•‘                                                                        β•‘
β•‘    EPOCH = Monotonically increasing version number                     β•‘
β•‘                                                                        β•‘
β•‘    - Frontend increments on resize                                     β•‘
β•‘    - Frontend passes to backend                                        β•‘
β•‘    - Backend stores epoch in GridUpdate                                β•‘
β•‘    - Frontend validates before injection                               β•‘
β•‘                                                                        β•‘
β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•