Grid Storage Layout & Memory Architecture
This is an internal storage guide, not a public encoding contract. Consumers use
TerminalReader, TerminalLine, and render-frame copies; raw arrays and cluster
handles remain private to core.
1. Parallel Array Cell Storage (Line)
Each physical Line holds parallel primitive arrays:
| Storage | Type | Contents |
|---|---|---|
codepoints |
IntArray |
Scalar, empty cell, wide spacer, or cluster handle |
attrs |
LongArray |
Primary packed attributes |
extendedAttrs |
LongArray? |
Extended attributes; absent storage reads as zero |
extendedAttrs is allocated on the first nonzero extended write and retained
through clears for reuse. A full-row zero overwrite clears the retained array
once, then marks it dormant. Subsequent zero writes, erases, and shifts skip
extended-array work until a nonzero write reactivates it. Partial zero edits
conservatively keep active storage; there is no per-cell count or scan to detect
the last nonzero word. Stored values remain zero while dormant, so reads and
partial reactivation cannot expose old styling.
An exact live-payload count lets scalar-only erases skip cluster accounting.
Invariants:
- Terminal grids have at least two columns, so a wide occupant always fits on one row.
0denotes an empty cell.- Positive values hold direct Unicode scalars, including supplementary values.
-1is the trailing spacer of a two-cell occupant.- Values
<= -2identify references into that row's own cluster payload array. - Attributes use the same column index as the cell value. Overwriting or erasing a cluster marks its payload dead. Shifts within a row move its references; copies between rows copy the actual codepoints.
Rows also carry a soft-wrap flag, wide-wrap padding provenance, authored-output provenance, line identity, and render generation. These are distinct facts: erasing text can retain a row's identity and authored blank output, while replacing the row creates a new identity. Reflow excludes artificial wrap padding from logical text.
A public TerminalLine decodes cluster handles to their base codepoint.
readCluster copies the complete payload into caller-owned storage. Render
copies translate internal packed attributes and cluster references into the
stable encodings in ketraterm-render-api.
2. Off-Screen History Ring (HistoryRing)
A screen's HistoryRing
contains both retained history and live viewport rows, oldest first. Primary
capacity is maxHistory + height; alternate capacity is height.
The reference table is allocated at full capacity, while row objects are
constructed for the initial viewport plus a batch of spare rows. Additional
batches are allocated as output fills the ring. Once capacity is reached,
push() reuses the oldest row and advances eviction accounting. The caller
clears recycled contents, resets the payload append cursor, and assigns a new
identity before exposing the row again.
Full-width region scrolling rotates row references rather than copying each cell. Partial-width scrolling copies only the selected cell slices and does not admit rows to history. History destruction drops logical reachability without scanning discarded cells or releasing external resources. Spare rows retain their own arrays and stale contents for reuse, bounded by the ring's allocated row count. All spare rows are fully initialized before becoming live again. Retaining the newest rows swaps only surviving row references; it is linear in the retained row count.
Height-only primary resize adjusts the reference table while retaining surviving row arrays and payloads. A width change reconstructs logical lines into new rows. Its temporary builder preserves source-row and source-column provenance until payloads are copied; equal raw references in different rows are unrelated. See the resize contract.
3. Row-owned cluster payloads
Line lazily allocates one flat IntArray when it first stores a cluster. A raw
reference packs the sign bit, a 25-bit codepoint offset, and five bits for
length - 1. Bit 30 stays clear, so a reference cannot equal the -1 spacer.
Lengths therefore decode without a metadata-table lookup. The shared
TerminalTextLimits
defines the 32-codepoint bound; the writer contract
defines validation and overflow behavior.
Writes copy borrowed input synchronously. A replacement reuses its region when the new payload fits. Other writes append and mark replaced regions dead. The exact live-codepoint count resets the append cursor as soon as no clusters remain. Full clear and fill reset both counters directly, without a cluster scan.
When append space is exhausted, compaction copies only live payloads in column order and updates their references. A second lazy array is retained and swapped with the active array. This is safe even after shifts reorder payload offsets; an in-place column-order copy could overwrite a later source. Both arrays are reused once their capacities are sufficient, including through row recycling. Rendering only reads storage and never triggers compaction.
Compaction reserves twice the required live size, with a minimum of 16 integers,
to amortize scans during sustained overwrites. Each array's capacity is bounded
by max(16, 2 * width * 32) and the encoding's checked limit of 2^25 integers.
Live-size arithmetic uses Long before allocation to prevent overflow. Dead
regions are reclaimed during ordinary mutation; array capacity is deliberately
retained for reuse. A row becomes collectible with all its payload storage when
the ring stops retaining it. Scalar-only rows allocate neither payload array.
All row access requires serialization with terminal mutation. The assembled session supplies it; direct core callers provide their own boundary.