Skip to content

Commit 851cb6d

Browse files
committed
fix: leaf reuse correctness + lint
1 parent ce56631 commit 851cb6d

File tree

19 files changed

+342
-213
lines changed

19 files changed

+342
-213
lines changed

packages/bench/src/blessed-backend.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ export function createBlessedContext(cols: number, rows: number): BlessedContext
5757
_encoding: BufferEncoding,
5858
callback: (error?: Error | null) => void,
5959
) {
60-
totalBytes +=
61-
typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
60+
totalBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.byteLength;
6261
callback();
6362
},
6463
highWaterMark: 1024 * 1024,
@@ -69,10 +68,12 @@ export function createBlessedContext(cols: number, rows: number): BlessedContext
6968
Object.defineProperty(measureStream, "rows", { value: rows, writable: true });
7069
Object.defineProperty(measureStream, "isTTY", { value: true });
7170

72-
const nullInput = new Readable({ read() {} });
71+
const nullInput = new Readable({ read() {} }) as Readable & {
72+
setRawMode?: (mode: boolean) => unknown;
73+
};
7374
Object.defineProperty(nullInput, "isTTY", { value: false });
7475
// blessed calls setRawMode on stdin
75-
(nullInput as unknown as Record<string, unknown>)["setRawMode"] = () => nullInput;
76+
nullInput.setRawMode = () => nullInput;
7677

7778
const screen = blessed.screen({
7879
output: measureStream,
@@ -91,7 +92,7 @@ export function createBlessedContext(cols: number, rows: number): BlessedContext
9192
screen,
9293
blessed,
9394
flush() {
94-
if (screen.program && screen.program.flush) {
95+
if (screen.program?.flush) {
9596
screen.program.flush();
9697
}
9798
},

packages/bench/src/profile-construction.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Usage: REZI_PERF=1 REZI_PERF_DETAIL=1 npx tsx src/profile-construction.ts
44
*/
55

6-
import { createApp, perfSnapshot, perfReset } from "@rezi-ui/core";
6+
import { createApp, perfReset, perfSnapshot } from "@rezi-ui/core";
77
import { BenchBackend } from "./backends.js";
88
import { buildReziTree } from "./scenarios/treeBuilders.js";
99

@@ -53,9 +53,9 @@ async function main() {
5353
if (!stats || stats.count === 0) continue;
5454
console.log(
5555
` ${phase.padEnd(22)} avg=${(stats.avg * 1000).toFixed(0).padStart(6)}µs ` +
56-
`p95=${(stats.p95 * 1000).toFixed(0).padStart(6)}µs ` +
57-
`p99=${(stats.p99 * 1000).toFixed(0).padStart(6)}µs ` +
58-
`count=${String(stats.count).padStart(5)}`
56+
`p95=${(stats.p95 * 1000).toFixed(0).padStart(6)}µs ` +
57+
`p99=${(stats.p99 * 1000).toFixed(0).padStart(6)}µs ` +
58+
`count=${String(stats.count).padStart(5)}`,
5959
);
6060
}
6161

packages/bench/src/profile-phases.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Usage: REZI_PERF=1 REZI_PERF_DETAIL=1 npx tsx src/profile-phases.ts
44
*/
55

6-
import { createApp, perfSnapshot, perfReset, ui, type VNode } from "@rezi-ui/core";
6+
import { type VNode, createApp, perfReset, perfSnapshot, ui } from "@rezi-ui/core";
77
import { BenchBackend } from "./backends.js";
88

99
const LIST_SIZE = 500;
@@ -80,9 +80,9 @@ async function main() {
8080
if (!stats || stats.count === 0) continue;
8181
console.log(
8282
` ${phase.padEnd(22)} avg=${(stats.avg * 1000).toFixed(0).padStart(6)}µs ` +
83-
`p95=${(stats.p95 * 1000).toFixed(0).padStart(6)}µs ` +
84-
`p99=${(stats.p99 * 1000).toFixed(0).padStart(6)}µs ` +
85-
`count=${String(stats.count).padStart(5)}`
83+
`p95=${(stats.p95 * 1000).toFixed(0).padStart(6)}µs ` +
84+
`p99=${(stats.p99 * 1000).toFixed(0).padStart(6)}µs ` +
85+
`count=${String(stats.count).padStart(5)}`,
8686
);
8787
}
8888

packages/bench/src/ratatui-runner.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* back into BenchMetrics.
77
*/
88

9-
import { execSync, execFileSync } from "node:child_process";
9+
import { execFileSync, execSync } from "node:child_process";
1010
import { existsSync } from "node:fs";
1111
import { resolve } from "node:path";
1212
import { computeStats, takeMemory } from "./measure.js";
@@ -87,7 +87,7 @@ interface RatatuiOutput {
8787
export function runRatatui(
8888
scenario: string,
8989
config: ScenarioConfig,
90-
params: Record<string, number | string> = {},
90+
params: Readonly<{ items?: number | string }> = {},
9191
): BenchMetrics {
9292
ensureBuilt();
9393

@@ -100,8 +100,8 @@ export function runRatatui(
100100
String(config.iterations),
101101
];
102102

103-
if (params["items"] !== undefined) {
104-
args.push("--items", String(params["items"]));
103+
if (params.items !== undefined) {
104+
args.push("--items", String(params.items));
105105
}
106106

107107
const stdout = execFileSync(BINARY, args, {

packages/bench/src/report.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,14 @@ export function toMarkdown(results: readonly BenchResult[]): string {
123123
);
124124
lines.push("|---|---:|---:|---:|---:|---:|---:|---:|---:|");
125125

126-
const order: Framework[] = ["rezi-native", "ink-compat", "ink", "terminal-kit", "blessed", "ratatui"];
126+
const order: Framework[] = [
127+
"rezi-native",
128+
"ink-compat",
129+
"ink",
130+
"terminal-kit",
131+
"blessed",
132+
"ratatui",
133+
];
127134
const sorted = [...items].sort(
128135
(a, b) => order.indexOf(a.framework) - order.indexOf(b.framework),
129136
);
@@ -139,7 +146,9 @@ export function toMarkdown(results: readonly BenchResult[]): string {
139146

140147
// Summary: relative performance (ratio = other / rezi; >1 means rezi is faster)
141148
lines.push("## Relative Performance (vs Rezi native)\n");
142-
lines.push("> \"Xx slower\" = Rezi native is X times faster. \"Xx faster\" = other framework is faster.\n");
149+
lines.push(
150+
'> "Xx slower" = Rezi native is X times faster. "Xx faster" = other framework is faster.\n',
151+
);
143152

144153
// Determine which frameworks have results
145154
const allFws: Framework[] = ["ink", "ink-compat", "terminal-kit", "blessed", "ratatui"];

packages/bench/src/scenarios/construction.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@
1313
import { BenchBackend, MeasuringStream, NullReadable } from "../backends.js";
1414
import { benchAsync, benchSync, tryGc } from "../measure.js";
1515
import type { BenchMetrics, Framework, Scenario, ScenarioConfig } from "../types.js";
16-
import { buildBlessedTree, buildReactTree, buildReziTree, buildTermkitTree } from "./treeBuilders.js";
16+
import {
17+
buildBlessedTree,
18+
buildReactTree,
19+
buildReziTree,
20+
buildTermkitTree,
21+
} from "./treeBuilders.js";
1722

1823
async function runRezi(config: ScenarioConfig, n: number): Promise<BenchMetrics> {
1924
const { createApp } = await import("@rezi-ui/core");
@@ -225,10 +230,7 @@ async function runBlessed(config: ScenarioConfig, n: number): Promise<BenchMetri
225230
return metrics;
226231
}
227232

228-
async function runRatatui(
229-
config: ScenarioConfig,
230-
n: number,
231-
): Promise<BenchMetrics> {
233+
async function runRatatui(config: ScenarioConfig, n: number): Promise<BenchMetrics> {
232234
const { runRatatui: exec } = await import("../ratatui-runner.js");
233235
return exec("construction", config, { items: n });
234236
}

packages/bench/src/scenarios/content-update.ts

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,7 @@ function reactListTree(
6868
{ key: String(i), flexDirection: "row", gap: 1 },
6969
h(C.Text as string, { bold: isSelected }, isSelected ? ">" : " "),
7070
h(C.Text as string, { dimColor: !isSelected }, `${String(i).padStart(3, " ")}.`),
71-
h(
72-
C.Text as string,
73-
{ bold: isSelected, inverse: isSelected },
74-
`entry-${i}.log`,
75-
),
71+
h(C.Text as string, { bold: isSelected, inverse: isSelected }, `entry-${i}.log`),
7672
h(C.Text as string, { dimColor: true }, `${(i * 1024 + 512).toLocaleString()} B`),
7773
),
7874
);
@@ -106,14 +102,8 @@ function termkitListTree(
106102
const y = 2 + i;
107103
const isSelected = i === selected;
108104
buffer.put({ x: 0, y, attr: { bold: isSelected } }, isSelected ? ">" : " ");
109-
buffer.put(
110-
{ x: 2, y, attr: { dim: !isSelected } },
111-
`${String(i).padStart(3, " ")}.`,
112-
);
113-
buffer.put(
114-
{ x: 8, y, attr: { bold: isSelected, inverse: isSelected } },
115-
`entry-${i}.log`,
116-
);
105+
buffer.put({ x: 2, y, attr: { dim: !isSelected } }, `${String(i).padStart(3, " ")}.`);
106+
buffer.put({ x: 8, y, attr: { bold: isSelected, inverse: isSelected } }, `entry-${i}.log`);
117107
buffer.put({ x: 25, y, attr: { dim: true } }, `${(i * 1024 + 512).toLocaleString()} B`);
118108
}
119109
}
@@ -184,9 +174,7 @@ async function runInkCompat(config: ScenarioConfig): Promise<BenchMetrics> {
184174
for (let i = 0; i < config.warmup; i++) {
185175
selected = (selected + 1) % LIST_SIZE;
186176
const frameP = backend.waitForFrame();
187-
instance.rerender(
188-
reactListTree(React, InkCompat, selected) as React.ReactNode,
189-
);
177+
instance.rerender(reactListTree(React, InkCompat, selected) as React.ReactNode);
190178
await frameP;
191179
}
192180

@@ -197,9 +185,7 @@ async function runInkCompat(config: ScenarioConfig): Promise<BenchMetrics> {
197185
async () => {
198186
selected = (selected + 1) % LIST_SIZE;
199187
const frameP = backend.waitForFrame();
200-
instance.rerender(
201-
reactListTree(React, InkCompat, selected) as React.ReactNode,
202-
);
188+
instance.rerender(reactListTree(React, InkCompat, selected) as React.ReactNode);
203189
await frameP;
204190
},
205191
0,
@@ -223,24 +209,19 @@ async function runInk(config: ScenarioConfig): Promise<BenchMetrics> {
223209

224210
let selected = 0;
225211
const initialWrite = stdout.waitForWrite();
226-
const instance = Ink.render(
227-
reactListTree(React, Ink, selected) as React.ReactNode,
228-
{
229-
stdout: stdout as unknown as NodeJS.WriteStream,
230-
stdin: stdin as unknown as NodeJS.ReadStream,
231-
patchConsole: false,
232-
exitOnCtrlC: false,
233-
},
234-
);
212+
const instance = Ink.render(reactListTree(React, Ink, selected) as React.ReactNode, {
213+
stdout: stdout as unknown as NodeJS.WriteStream,
214+
stdin: stdin as unknown as NodeJS.ReadStream,
215+
patchConsole: false,
216+
exitOnCtrlC: false,
217+
});
235218
await initialWrite;
236219

237220
try {
238221
for (let i = 0; i < config.warmup; i++) {
239222
selected = (selected + 1) % LIST_SIZE;
240223
const writeP = stdout.waitForWrite();
241-
instance.rerender(
242-
reactListTree(React, Ink, selected) as React.ReactNode,
243-
);
224+
instance.rerender(reactListTree(React, Ink, selected) as React.ReactNode);
244225
await writeP;
245226
}
246227

@@ -251,9 +232,7 @@ async function runInk(config: ScenarioConfig): Promise<BenchMetrics> {
251232
async () => {
252233
selected = (selected + 1) % LIST_SIZE;
253234
const writeP = stdout.waitForWrite();
254-
instance.rerender(
255-
reactListTree(React, Ink, selected) as React.ReactNode,
256-
);
235+
instance.rerender(reactListTree(React, Ink, selected) as React.ReactNode);
257236
await writeP;
258237
},
259238
0,
@@ -315,16 +294,23 @@ function buildInitialBlessedList(
315294
blessed: { text: (opts: Record<string, unknown>) => unknown },
316295
screen: { append: (el: unknown) => void },
317296
selected: number,
318-
): { headerSel: BlessedWidget; markers: BlessedWidget[]; indices: BlessedWidget[]; names: BlessedWidget[] } {
297+
): {
298+
headerSel: BlessedWidget;
299+
markers: BlessedWidget[];
300+
indices: BlessedWidget[];
301+
names: BlessedWidget[];
302+
} {
319303
// Header
320304
screen.append(
321305
blessed.text({ top: 0, left: 0, content: "Files", style: { bold: true }, tags: false }),
322306
);
323-
screen.append(
324-
blessed.text({ top: 0, left: 8, content: `${LIST_SIZE} items`, tags: false }),
325-
);
307+
screen.append(blessed.text({ top: 0, left: 8, content: `${LIST_SIZE} items`, tags: false }));
326308
const headerSel = blessed.text({
327-
top: 0, left: 22, content: `Selected: ${selected}`, style: { fg: "grey" }, tags: false,
309+
top: 0,
310+
left: 22,
311+
content: `Selected: ${selected}`,
312+
style: { fg: "grey" },
313+
tags: false,
328314
});
329315
screen.append(headerSel);
330316

@@ -335,10 +321,34 @@ function buildInitialBlessedList(
335321
for (let i = 0; i < LIST_SIZE; i++) {
336322
const y = 2 + i;
337323
const isSel = i === selected;
338-
const marker = blessed.text({ top: y, left: 0, content: isSel ? ">" : " ", style: { bold: isSel }, tags: false });
339-
const idx = blessed.text({ top: y, left: 2, content: `${String(i).padStart(3, " ")}.`, style: { fg: isSel ? "white" : "grey" }, tags: false });
340-
const name = blessed.text({ top: y, left: 8, content: `entry-${i}.log`, style: { bold: isSel, inverse: isSel }, tags: false });
341-
const size = blessed.text({ top: y, left: 25, content: `${(i * 1024 + 512).toLocaleString()} B`, style: { fg: "grey" }, tags: false });
324+
const marker = blessed.text({
325+
top: y,
326+
left: 0,
327+
content: isSel ? ">" : " ",
328+
style: { bold: isSel },
329+
tags: false,
330+
});
331+
const idx = blessed.text({
332+
top: y,
333+
left: 2,
334+
content: `${String(i).padStart(3, " ")}.`,
335+
style: { fg: isSel ? "white" : "grey" },
336+
tags: false,
337+
});
338+
const name = blessed.text({
339+
top: y,
340+
left: 8,
341+
content: `entry-${i}.log`,
342+
style: { bold: isSel, inverse: isSel },
343+
tags: false,
344+
});
345+
const size = blessed.text({
346+
top: y,
347+
left: 25,
348+
content: `${(i * 1024 + 512).toLocaleString()} B`,
349+
style: { fg: "grey" },
350+
tags: false,
351+
});
342352
screen.append(marker);
343353
screen.append(idx);
344354
screen.append(name);
@@ -356,7 +366,12 @@ function buildInitialBlessedList(
356366
* matching the partial-update behavior measured by other frameworks.
357367
*/
358368
function updateBlessedSelection(
359-
refs: { headerSel: BlessedWidget; markers: BlessedWidget[]; indices: BlessedWidget[]; names: BlessedWidget[] },
369+
refs: {
370+
headerSel: BlessedWidget;
371+
markers: BlessedWidget[];
372+
indices: BlessedWidget[];
373+
names: BlessedWidget[];
374+
},
360375
oldSel: number,
361376
newSel: number,
362377
): void {
@@ -424,8 +439,7 @@ async function runRatatui(config: ScenarioConfig): Promise<BenchMetrics> {
424439

425440
export const contentUpdateScenario: Scenario = {
426441
name: "content-update",
427-
description:
428-
"Partial screen update: move selection in a 500-row list (measures diff efficiency)",
442+
description: "Partial screen update: move selection in a 500-row list (measures diff efficiency)",
429443
defaultConfig: { warmup: 50, iterations: 500 },
430444
paramSets: [{}],
431445
frameworks: ["rezi-native", "ink-compat", "ink", "terminal-kit", "blessed", "ratatui"],

packages/bench/src/scenarios/memory.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,7 @@
1313

1414
import { type VNode, ui } from "@rezi-ui/core";
1515
import { BenchBackend, MeasuringStream, NullReadable } from "../backends.js";
16-
import {
17-
computeStats,
18-
diffCpu,
19-
peakMemory,
20-
takeCpu,
21-
takeMemory,
22-
tryGc,
23-
} from "../measure.js";
16+
import { computeStats, diffCpu, peakMemory, takeCpu, takeMemory, tryGc } from "../measure.js";
2417
import type {
2518
BenchMetrics,
2619
Framework,
@@ -345,10 +338,7 @@ function termkitMemTree(
345338
const bar = `[${"#".repeat(filled)}${".".repeat(20 - filled)}] ${pct}%`;
346339
buffer.put({ x: 1, y: 1 }, bar);
347340
for (let j = 0; j < 20; j++) {
348-
buffer.put(
349-
{ x: 1, y: 3 + j, attr: { dim: j % 2 === 0 } },
350-
` Line ${j}: value=${i * 20 + j}`,
351-
);
341+
buffer.put({ x: 1, y: 3 + j, attr: { dim: j % 2 === 0 } }, ` Line ${j}: value=${i * 20 + j}`);
352342
}
353343
}
354344

@@ -437,9 +427,7 @@ function blessedMemTree(
437427
const pct = i % 100;
438428
const filled = Math.floor(pct / 5);
439429
const bar = `[${"#".repeat(filled)}${".".repeat(20 - filled)}] ${pct}%`;
440-
screen.append(
441-
blessed.text({ top: 1, left: 1, content: bar, tags: false }),
442-
);
430+
screen.append(blessed.text({ top: 1, left: 1, content: bar, tags: false }));
443431
for (let j = 0; j < 20; j++) {
444432
screen.append(
445433
blessed.text({

0 commit comments

Comments
 (0)