Custom TUI Engine
Panix uses a custom-built terminal rendering engine, zeroterm (the pkg/tui and pkg/buffer packages), instead of existing TUI frameworks. The engine is designed for speed and near zero-allocation rendering.
The four-phase journey that produced it: Performance Optimization.
Why a Custom Engine
Section titled “Why a Custom Engine”Bubble Tea re-renders entire views every frame through string intermediates, producing thousands of short-lived allocations per frame and heavy GC pressure. Panix renders a tree of nested viewports that shrink when commands finish, with dozens of machines updating at once, so full-frame re-rendering is wasted work at fleet scale. zeroterm bounds rendering work and terminal output by the changed lines, not the frame size.
How It Works
Section titled “How It Works”- Contiguous line buffer (
LinesBuf): one[]bytewith[]intline offsets;Line(i)is zero-copy and bulk ops (AppendFrom,WritePaddedView) copy visible regions in a single append. Pooled viasync.Pool, reset withbuf[:0]retaining capacity. - Versioned buffer (
LinesBufVer): thread-safeLinesBufwrapper with a version counter for cache invalidation;Snapshot()provides consistent copies. - In-place styling (
AppendStyledLine/AppendStyledPad): writes prefix + content + reset directly into the caller’s buffer using pre-rendered ANSI prefixes and a padding pool. - Double-buffered frame diffing: two
LinesBufDiffbuffers alternate as current/previous;Diff(prev)returns changed line indices viabytes.Equal, unchanged lines produce zero output. - Incremental caching: tables re-render only dirty rows, viewports append when only new lines arrive, and components cache output keyed on content/version/width/selection.
- Pre-rendered everything: border bytes, connector chars, scrollbar cells, zone markers (
\x1b[<id>z), and ANSI prefixes, computed once as package-level vars. - Stack-allocated scratch: tree prefixes use
var pfxBuf [1024]byte, the table a single partitionedwidthsBuf []int; no heap scratch allocations. - Zero-copy string and byte conversion (
stringbyte.StringByte): a namedstringtype withBytes()viaunsafe.Slice; comparable, JSON-serializable, no[]byte(s)copies in the hot path. - Zero-CGO: pure Go, no C dependencies.
Benchmarks
Section titled “Benchmarks”Full-pipeline benchmarks (render + diff, composite layout with viewports, tree, and table at terminal size of 200×50) against Bubble Tea and cview (a fork of tview):
| Feature | Ours | Bubble Tea +262× | Cview +605× |
|---|---|---|---|
| Pipeline_EveryFrameUpdate | 8.7 µs (0 B, 0 allocs) | 1.9 ms (206.3 KB, 5476 allocs) +215× | 2.4 ms (1.1 MB, 27891 allocs) +279× |
| Pipeline_NoChange | 2.5 µs (0 B, 0 allocs) | 749.2 µs (189.3 KB, 4781 allocs) +297× | 2.1 ms (854.4 KB, 22533 allocs) +823× |
| Pipeline_QuarterFrameUpdate | 2.6 µs (0 B, 0 allocs) | 715.6 µs (183.5 KB, 4133 allocs) +276× | 1.9 ms (819.9 KB, 20464 allocs) +715× |
goos: linux goarch: amd64 cpu: AMD Ryzen 9 5900HX with Radeon Graphics, benchtime: 1s, ran: 2026-06-24