Skip to content

Commit d373668

Browse files
committed
feat(opentui): implement threadsafe callbacks and performance APIs
Threadsafe callbacks (no more TODOs/no-ops): - setLogCallback: TSFN bridge for log messages from native render thread - setEventCallback: TSFN bridge for native events (input, resize, etc.) - streamSetCallback: TSFN bridge for NativeSpanFeed stream notifications All three properly create/release napi_threadsafe_function, handle undefined/null to clear callbacks, and use nonblocking queue mode. Performance APIs for zero-copy buffer access: - bufferGetCharArrayBuffer/FgArrayBuffer/BgArrayBuffer/AttributesArrayBuffer: Return external ArrayBuffers backed by native memory (no copy) - bufferDrawTextEncoded: Accept pre-encoded Uint8Array to skip per-call UTF-8 conversion overhead - editBufferGetCursorInto/editorViewGetCursorInto/getCursorStateInto: Write cursor state into reusable typed arrays (no object allocation) JS wrapper additions: - BufferView class: Lazy typed array views over native buffer memory with direct cell writes (bypass FFI for bulk updates) - CursorState class: Reusable cursor reader (zero allocation per read) - encodeText(): Cached TextEncoder for pre-encoding text napi.zig additions: - createThreadsafeFunction/callThreadsafeFunction/releaseThreadsafeFunction - acquireThreadsafeFunction for multi-thread access - getTypedArrayInfo for reading typed array data pointers
1 parent 93fe72f commit d373668

File tree

6 files changed

+593
-11
lines changed

6 files changed

+593
-11
lines changed

packages/opentui-builder/lib/index.d.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,31 @@ export declare const TargetChannel: {
6060
readonly BG: 2
6161
readonly BOTH: 3
6262
}
63+
64+
export declare function encodeText(text: string): Uint8Array
65+
66+
export type NativePointer = import('./native.js').NativePointer
67+
68+
export declare class BufferView {
69+
constructor(bufferPtr: NativePointer)
70+
readonly width: number
71+
readonly height: number
72+
readonly chars: Uint32Array
73+
readonly fg: Float32Array
74+
readonly bg: Float32Array
75+
readonly attributes: Uint32Array
76+
setCell(x: number, y: number, char: number, fgR: number, fgG: number, fgB: number, fgA: number, bgR: number, bgG: number, bgB: number, bgA: number, attrs: number): void
77+
invalidate(): void
78+
}
79+
80+
export declare class CursorState {
81+
constructor()
82+
readEditBuffer(editBufferPtr: NativePointer): this
83+
readEditorView(editorViewPtr: NativePointer): this
84+
readRenderer(rendererPtr: NativePointer): this
85+
readonly row: number
86+
readonly col: number
87+
readonly x: number
88+
readonly y: number
89+
readonly visible: boolean
90+
}

packages/opentui-builder/lib/index.mjs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,104 @@ export const TargetChannel = {
218218
BG: 2,
219219
BOTH: 3,
220220
}
221+
222+
// ── Performance helpers ──
223+
224+
const textEncoder = new TextEncoder()
225+
226+
export function encodeText(text) {
227+
return textEncoder.encode(text)
228+
}
229+
230+
export class BufferView {
231+
constructor(bufferPtr) {
232+
this._ptr = bufferPtr
233+
this._chars = undefined
234+
this._fg = undefined
235+
this._bg = undefined
236+
this._attrs = undefined
237+
this._width = native.getBufferWidth(bufferPtr)
238+
this._height = native.getBufferHeight(bufferPtr)
239+
}
240+
241+
get width() { return this._width }
242+
get height() { return this._height }
243+
244+
get chars() {
245+
if (!this._chars) {
246+
this._chars = new Uint32Array(native.bufferGetCharArrayBuffer(this._ptr))
247+
}
248+
return this._chars
249+
}
250+
251+
get fg() {
252+
if (!this._fg) {
253+
this._fg = new Float32Array(native.bufferGetFgArrayBuffer(this._ptr))
254+
}
255+
return this._fg
256+
}
257+
258+
get bg() {
259+
if (!this._bg) {
260+
this._bg = new Float32Array(native.bufferGetBgArrayBuffer(this._ptr))
261+
}
262+
return this._bg
263+
}
264+
265+
get attributes() {
266+
if (!this._attrs) {
267+
this._attrs = new Uint32Array(native.bufferGetAttributesArrayBuffer(this._ptr))
268+
}
269+
return this._attrs
270+
}
271+
272+
setCell(x, y, char, fgR, fgG, fgB, fgA, bgR, bgG, bgB, bgA, attrs) {
273+
const idx = y * this._width + x
274+
this.chars[idx] = char
275+
const ci = idx * 4
276+
this.fg[ci] = fgR
277+
this.fg[ci + 1] = fgG
278+
this.fg[ci + 2] = fgB
279+
this.fg[ci + 3] = fgA
280+
this.bg[ci] = bgR
281+
this.bg[ci + 1] = bgG
282+
this.bg[ci + 2] = bgB
283+
this.bg[ci + 3] = bgA
284+
this.attributes[idx] = attrs
285+
}
286+
287+
invalidate() {
288+
this._chars = undefined
289+
this._fg = undefined
290+
this._bg = undefined
291+
this._attrs = undefined
292+
}
293+
}
294+
295+
export class CursorState {
296+
constructor() {
297+
this._buf = new Uint32Array(2)
298+
this._i32buf = new Int32Array(3)
299+
}
300+
301+
readEditBuffer(editBufferPtr) {
302+
native.editBufferGetCursorInto(editBufferPtr, this._buf)
303+
return this
304+
}
305+
306+
readEditorView(editorViewPtr) {
307+
native.editorViewGetCursorInto(editorViewPtr, this._buf)
308+
return this
309+
}
310+
311+
readRenderer(rendererPtr) {
312+
native.getCursorStateInto(rendererPtr, this._i32buf)
313+
return this
314+
}
315+
316+
get row() { return this._buf[0] }
317+
get col() { return this._buf[1] }
318+
get x() { return this._i32buf[0] }
319+
get y() { return this._i32buf[1] }
320+
get visible() { return this._i32buf[2] !== 0 }
321+
}

packages/opentui-builder/lib/native.d.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,17 @@ interface OpenTUIBindings {
287287
streamGetStats(stream: NativePointer): { totalBytesWritten: number; totalSpansEmitted: number; totalCommits: number; chunksAllocated: number; chunksInUse: number; currentChunkUsed: number; pendingSpanBytes: number; attached: boolean }
288288
streamDrainSpans(stream: NativePointer, outPtr: ArrayBuffer, maxSpans: number): number
289289
streamSetCallback(stream: NativePointer, callback: (() => void) | undefined): void
290+
291+
// ── Performance: batch/direct buffer access ──
292+
293+
bufferDrawTextEncoded(buffer: NativePointer, data: Uint8Array, x: number, y: number, fgR: number, fgG: number, fgB: number, fgA: number, bgR: number, bgG: number, bgB: number, bgA: number, attributes: number): void
294+
bufferGetCharArrayBuffer(buffer: NativePointer): ArrayBuffer
295+
bufferGetFgArrayBuffer(buffer: NativePointer): ArrayBuffer
296+
bufferGetBgArrayBuffer(buffer: NativePointer): ArrayBuffer
297+
bufferGetAttributesArrayBuffer(buffer: NativePointer): ArrayBuffer
298+
editBufferGetCursorInto(editBuffer: NativePointer, out: Uint32Array): void
299+
editorViewGetCursorInto(view: NativePointer, out: Uint32Array): void
300+
getCursorStateInto(renderer: NativePointer, out: Int32Array): void
290301
}
291302

292303
export type { NativePointer }

0 commit comments

Comments
 (0)