Raw Buffer API
Deep dive into the Uint32Array buffer format, Renderer API, and rendering primitives in @visulima/tui/core
Raw Buffer API
The Rust renderer in @visulima/tui/core accepts a Uint32Array back buffer and emits minimal ANSI updates.
Core Contract
Uint32Array(cols × rows × 2) -> Rust diff -> ANSI writesYou control how the buffer is filled.
Buffer Format
Each cell uses two u32 values:
buffer[idx * 2] = Unicode codepoint
buffer[idx * 2 + 1] = attr code = (styles << 16) | (bg << 8) | fgCell index for (x, y):
const idx = y * cols + x;fg/bg: ANSI 256 color (0-255)- style bits:
| Bit | Style |
|---|---|
| 1 | Bold |
| 2 | Dim |
| 4 | Italic |
| 8 | Underline |
| 16 | Blink |
| 32 | Invert |
| 64 | Hidden |
| 128 | Strikethrough |
Minimal Setup
import { Renderer, TerminalGuard, terminalSize } from "@visulima/tui/core";
const guard = new TerminalGuard();
const { cols, rows } = terminalSize();
const renderer = new Renderer(cols, rows);
let buf = new Uint32Array(cols * rows * 2);
function paint() {
buf.fill(0);
// 'H' at (5, 3), bright red fg
const idx = (3 * cols + 5) * 2;
buf[idx] = "H".codePointAt(0)!;
buf[idx + 1] = 196;
renderer.render(buf);
}
const timer = setInterval(paint, 16);
process.on("SIGINT", () => {
clearInterval(timer);
guard.leave();
process.exit(0);
});Convenience Helpers
Cell.pack
import { Cell, StyleMasks } from "@visulima/tui/core";
const [charCode, attrCode] = Cell.pack("A", 196, 0, StyleMasks.BOLD);
buf[idx * 2] = charCode;
buf[idx * 2 + 1] = attrCode;setCell Helper from Repo Examples
import { setCell } from "./examples-raw/harness";
setCell(buf, cols, x, y, "A", 196, 0, 1); // 1 = bold style bitResize Handling
process.on("SIGWINCH", () => {
const { cols, rows } = terminalSize();
renderer.resize(cols, rows);
buf = new Uint32Array(cols * rows * 2);
});Clearing Strategy
Use one of these patterns per frame:
1. Full clear + repaint
buf.fill(0);
// repaint everything you want visible this frame2. Sparse updates with explicit stale clears
Track previously painted cells and write spaces into cells that are no longer active.
Useful for pointer/cursor/sprite style renderers.
Character Caveat
Block glyphs (█, ▓, ▒, ░) can visually look like background fills.
For moving marks, narrow foreground glyphs (▪, ·, │, ─) are often easier to clear cleanly.
Harness Pattern in Repo Examples
examples-raw/harness.ts wraps common loop boilerplate (guard, renderer, resize, Ctrl+C):
import { createLoop } from "./examples-raw/harness";
const loop = createLoop((buf, cols, rows, frame) => {
// paint frame
}, 60);
loop.start();Inline Mode: createInlineLoop()
createInlineLoop draws a fixed-height region below the current cursor, without alternate screen takeover.
import { createInlineLoop } from "@visulima/tui/core";
const loop = createInlineLoop(
(buf, cols, rows, frame) => {
buf.fill(0);
const text = `frame ${frame}`;
for (let i = 0; i < text.length; i++) {
const idx = i * 2;
buf[idx] = text.codePointAt(i)!;
buf[idx + 1] = 51;
}
},
{ rows: 5, fps: 30, onExit: "preserve" },
);
loop.start();Options:
rows(default10)fps(default60)onExit: 'preserve' | 'destroy'
Current behavior: stopping the inline loop exits the process.
Example Commands
node --import @oxc-node/core/register examples-raw/ascii-3d.ts
node --import @oxc-node/core/register examples-raw/conway.ts
node --import @oxc-node/core/register examples-raw/fire.ts
node --import @oxc-node/core/register examples-raw/matrix.ts
node --import @oxc-node/core/register examples-raw/jitter.ts
node --import @oxc-node/core/register examples-raw/scope.ts
node --import @oxc-node/core/register examples-raw/plasma.ts
node --import @oxc-node/core/register examples-raw/inline-picker.ts