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 writes

You 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) | fg

Cell index for (x, y):

const idx = y * cols + x;
  • fg / bg: ANSI 256 color (0-255)
  • style bits:
BitStyle
1Bold
2Dim
4Italic
8Underline
16Blink
32Invert
64Hidden
128Strikethrough

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 bit

Resize 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 frame

2. 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 (default 10)
  • fps (default 60)
  • 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
Support

Contribute to our work and keep us going

Community is the heart of open source. The success of our packages wouldn't be possible without the incredible contributions of users, testers, and developers who collaborate with us every day.Want to get involved? Here are some tips on how you can make a meaningful impact on our open source projects.

Ready to help us out?

Be sure to check out the package's contribution guidelines first. They'll walk you through the process on how to properly submit an issue or pull request to our repositories.

Submit a pull request

Found something to improve? Fork the repo, make your changes, and open a PR. We review every contribution and provide feedback to help you get merged.

Good first issues

Simple issues suited for people new to open source development, and often a good place to start working on a package.
View good first issues