Skip to main content

Buffer Model

MonoTerm uses an optimized memory model to ensure scroll consistency while minimizing memory usage.

The Problem

┌───────────────────────────────────────────────────────────────────────┐
│  THE PROBLEM: Scroll Desync                                           │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  xterm.js calculates scroll based on buffer size:                     │
│                                                                       │
│    scrollAreaHeight = buffer.lines.length x rowHeight                 │
│    maxScrollTop = scrollAreaHeight - viewportHeight                   │
│                                                                       │
│  If buffer doesn't match scrollArea:                                  │
│                                                                       │
│    ┌──────────────────┐      ┌──────────────────┐                     │
│    │ buffer.lines = X │  !=  │ scrollArea = Y   │                     │
│    └──────────────────┘      └──────────────────┘                     │
│               │                       │                               │
│               └───────────┬───────────┘                               │
│                           ▼                                           │
│                    SCROLL DESYNC!                                     │
│                    - Scroll jumps                                     │
│                    - Thumb size wrong                                 │
│                    - Position calculation broken                      │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

The Solution: Optimized Memory Layout

┌───────────────────────────────────────────────────────────────────────┐
│  BUFFER STRUCTURE                                                     │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  Variables:                                                           │
│    H = history (scrollback lines)                                     │
│    V = viewport (visible rows)                                        │
│                                                                       │
│                                                                       │
│  Index:  0         H-1   H      H+V-1  H+V    H+2V-1                  │
│          │         │     │      │      │      │                       │
│          ▼         ▼     ▼      ▼      ▼      ▼                       │
│  ┌───────┬─────────┬─────┬──────┬──────┬──────┬───────┐               │
│  │ ##################### │/////////////│@@@@@@@@@@@@@ │               │
│  │    HISTORY (H)        │  VIEWPORT   │   PADDING    │               │
│  │    scrollback         │    (V)      │     (V)      │               │
│  │    actual data        │ actual data │  empty lines │               │
│  └───────────────────────┴─────────────┴──────────────┘               │
│                                                                       │
│  Legend:                                                              │
│    #### = History lines (scrollback content)                          │
│    //// = Viewport lines (visible screen content)                     │
│    @@@@ = Padding lines (empty, for scroll consistency)               │
│                                                                       │
│  Actual content: H + V lines (from Rust)                              │
│  Total size:     H + 2V lines (with padding)                          │
│  Padding:        V lines (constant, regardless of H)                  │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Why This Layout Works

Safety Guarantee

┌───────────────────────────────────────────────────────────────────────┐
│  ARRAY BOUNDS SAFETY                                                  │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  Maximum access pattern in xterm.js:                                  │
│                                                                       │
│    buffer.lines[ydisp + y]                                            │
│    where: ydisp = H  (at bottom)                                      │
│           y = 0..V-1 (viewport rows)                                  │
│                                                                       │
│  Maximum index accessed:                                              │
│    max_index = H + (V - 1)                                            │
│              = H + V - 1                                              │
│                                                                       │
│  Buffer length:                                                       │
│    buffer.lines.length = H + 2V                                       │
│                                                                       │
│  Safety check:                                                        │
│    H + V - 1 < H + 2V                                                 │
│    V - 1 < 2V                                                         │
│    -1 < V                          ALWAYS TRUE (for V > 0)            │
│                                                                       │
│  Result: Buffer access is always within bounds                        │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Memory Efficiency

┌───────────────────────────────────────────────────────────────────────┐
│  MEMORY COMPARISON                                                    │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  With H=1000 (history), V=40 (viewport):                              │
│                                                                       │
│                                                                       │
│  Alternative Model (wasteful):                                        │
│  ──────────────────────────────                                       │
│    buffer = 2H + V = 2040 lines                                       │
│    padding = H = 1000 lines      (grows with history!)                │
│                                                                       │
│    H=0      -> padding = 0                                            │
│    H=100    -> padding = 100                                          │
│    H=10000  -> padding = 10000   WASTEFUL!                            │
│                                                                       │
│                                                                       │
│  MonoTerm Model (efficient):                                          │
│  ───────────────────────────                                          │
│    buffer = H + 2V = 1080 lines                                       │
│    padding = V = 40 lines        (constant!)                          │
│                                                                       │
│    V=40     -> padding = 40      ALWAYS ~40 lines                     │
│    Regardless of history size!                                        │
│                                                                       │
│                                                                       │
│  Memory savings with 10,000 line history:                             │
│                                                                       │
│    Alternative: 20,040 lines                                          │
│    MonoTerm:    10,080 lines                                          │
│    Saved:       9,960 lines (99%)                                     │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Visual Memory Layout

┌───────────────────────────────────────────────────────────────────────┐
│  MEMORY LAYOUT VISUALIZATION                                          │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  Example: H=100 (history), V=40 (viewport)                            │
│                                                                       │
│                                                                       │
│  ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐       │
│  │  0  │  1  │  2  │ ... │ 99  │ 100 │ 101 │ ... │ 139 │ 140 │       │
│  └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘       │
│  │<─────────── H=100 ──────────→│<───── V=40 ─────→│<── V=40 ──→│    │
│                                                                       │
│                                                                       │
│  Region     │ Indices    │ Content      │ Source                      │
│  ───────────┼────────────┼──────────────┼─────────────                │
│  History    │ 0..99      │ Scrollback   │ Rust data                   │
│  Viewport   │ 100..139   │ Screen       │ Rust data                   │
│  Padding    │ 140..179   │ Empty        │ (no data)                   │
│                                                                       │
│                                                                       │
│  Total length: 180 = H(100) + 2V(80)                                  │
│  Actual data:  140 = H(100) + V(40)                                   │
│  Padding:       40 = V                                                │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Edge Cases

No History (Fresh Terminal)

┌───────────────────────────────────────────────────────────────────────┐
│  EDGE CASE: Empty History                                             │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  When: Terminal just started, no scrollback yet                       │
│                                                                       │
│  Values:                                                              │
│    H = 0, V = 40                                                      │
│                                                                       │
│  Layout:                                                              │
│    ┌─────────────────────────────────┬───────────────────────────────┐│
│    │/////////////////////////////////│@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@││
│    │        VIEWPORT (V=40)          │          PADDING (V=40)       ││
│    └─────────────────────────────────┴───────────────────────────────┘│
│    Index: 0                          39 40                           79│
│                                                                       │
│  Buffer size: 0 + 80 = 80 lines                                       │
│  Safety: max_index = 0 + 39 = 39 < 80  OK                             │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Large History

┌───────────────────────────────────────────────────────────────────────┐
│  EDGE CASE: Large Scrollback                                          │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  When: Long-running terminal, lots of output                          │
│                                                                       │
│  Values:                                                              │
│    H = 100,000 (max scrollback)                                       │
│    V = 40                                                             │
│                                                                       │
│                                                                       │
│  Alternative (wasteful):                                              │
│    buffer.length = 200,040 lines                                      │
│    padding = 100,000 lines (same as history!)                         │
│                                                                       │
│                                                                       │
│  MonoTerm (efficient):                                                │
│    buffer.length = 100,080 lines                                      │
│    padding = 40 lines (same as viewport)                              │
│                                                                       │
│                                                                       │
│  Memory savings: 99,960 empty lines avoided!                          │
│                                                                       │
│  Safety: max_index = 100,039 < 100,080  OK                            │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Scrollbar Integration

┌───────────────────────────────────────────────────────────────────────┐
│  SCROLLBAR SIZE CALCULATION                                           │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  From GridUpdate:                                                     │
│    totalLines = H + 2V                                                │
│    viewportRows = V                                                   │
│                                                                       │
│  Thumb SIZE (proportion):                                             │
│    thumbHeight = (V / (H + 2V)) x trackHeight                         │
│                                                                       │
│  Thumb POSITION:                                                      │
│    scrollableRange = H + V                                            │
│    scrollFraction = ydisp / scrollableRange                           │
│    thumbTop = scrollFraction x (trackHeight - thumbHeight)            │
│                                                                       │
│                                                                       │
│  Track representation:                                                │
│                                                                       │
│  ┌───┐                                                                │
│  │...│  <- History region (H)                                         │
│  │...│                                                                │
│  │...│                                                                │
│  │###│  <- Thumb (V / (H+2V) of track)                                │
│  │...│  <- Padding region (V)                                         │
│  └───┘                                                                │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Scroll Position Mapping

┌───────────────────────────────────────────────────────────────────────┐
│  SCROLL POSITION MAPPING                                              │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  ydisp (internal)  <-->  scrollTop (DOM pixel)                        │
│                                                                       │
│  Conversion:                                                          │
│    scrollTop = ydisp x rowHeight                                      │
│    ydisp = Math.floor(scrollTop / rowHeight)                          │
│                                                                       │
│  Range:                                                               │
│    ydisp: 0 (top of history) to H (bottom, current screen)            │
│    scrollTop: 0 to (H + V) x rowHeight                                │
│                                                                       │
│  Visual:                                                              │
│                                                                       │
│    ydisp=0       ydisp=H/2      ydisp=H                               │
│    scrollTop=0   scrollTop=M   scrollTop=max                          │
│        │             │              │                                 │
│        ▼             ▼              ▼                                 │
│    ┌───────┐     ┌───────┐     ┌───────┐                              │
│    │#######│     │.......│     │.......│  <- viewport window          │
│    │#######│     │#######│     │.......│                              │
│    │.......│     │#######│     │#######│                              │
│    │.......│     │.......│     │#######│                              │
│    └───────┘     └───────┘     └───────┘                              │
│    top of        middle of      bottom of                             │
│    history       history        history (current)                     │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Summary

┌───────────────────────────────────────────────────────────────────────┐
│  BUFFER MODEL SUMMARY                                                 │
├───────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  [OK] WHAT IT SOLVES:                                                 │
│       - xterm.js scroll/buffer desync                                 │
│       - Unpredictable buffer growth                                   │
│       - Scrollbar calculation errors                                  │
│                                                                       │
│  [OK] HOW IT WORKS:                                                   │
│       - Buffer = Content (H+V) + Padding (V)                          │
│       - Padding is always viewport-sized                              │
│       - scrollArea height matches buffer length                       │
│                                                                       │
│  [OK] WHY IT'S SAFE:                                                  │
│       - Max access: H + V - 1                                         │
│       - Buffer size: H + 2V                                           │
│       - H + V - 1 < H + 2V (always true for V > 0)                    │
│                                                                       │
│  [OK] WHY IT'S EFFICIENT:                                             │
│       - Padding = V (constant ~40 lines)                              │
│       - Not H (could be 100,000 lines!)                               │
│       - Memory grows linearly with content, not doubled               │
│                                                                       │
└───────────────────────────────────────────────────────────────────────┘

Memory Efficiency

Constant padding of ~40 lines regardless of scrollback size. With 100K history: saves 99,960 empty lines compared to alternative models.