122 lines
4.2 KiB
JavaScript
122 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import { access, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { analyzeGlbFile } from "./glb-performance.mjs";
|
|
|
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const baselineFile = path.join(
|
|
projectRoot,
|
|
"scripts/asset-pipeline/environment-performance-baseline.json",
|
|
);
|
|
const defaultRoots = [
|
|
path.join(projectRoot, "public/assets/game/manastorm"),
|
|
path.join(projectRoot, "src/assets/game/dungeons"),
|
|
];
|
|
const defaultThresholds = {
|
|
maxEstimatedDrawCalls: 1_500,
|
|
maxUnbatchedDrawCallSavings: 250,
|
|
};
|
|
|
|
async function exists(file) {
|
|
try {
|
|
await access(file);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function collectVisualGlbs(root, files = []) {
|
|
if (!await exists(root)) return files;
|
|
const entry = await stat(root);
|
|
if (entry.isFile()) {
|
|
if (/(?:^|[-_])visual\.glb$/i.test(path.basename(root))) files.push(root);
|
|
return files;
|
|
}
|
|
for (const child of await readdir(root, { withFileTypes: true })) {
|
|
const target = path.join(root, child.name);
|
|
if (child.isDirectory()) await collectVisualGlbs(target, files);
|
|
else if (/(?:^|[-_])visual\.glb$/i.test(child.name)) files.push(target);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function projectPath(file) {
|
|
return path.relative(projectRoot, file).split(path.sep).join("/");
|
|
}
|
|
|
|
function exceeds(report, limits) {
|
|
return report.estimatedDrawCalls > limits.maxEstimatedDrawCalls
|
|
|| report.unbatchedDrawCallSavings > limits.maxUnbatchedDrawCallSavings;
|
|
}
|
|
|
|
const args = process.argv.slice(2);
|
|
const writeBaseline = args.includes("--write-baseline");
|
|
const roots = args.filter((argument) => argument !== "--write-baseline")
|
|
.map((argument) => path.resolve(projectRoot, argument));
|
|
const files = [...new Set((await Promise.all(
|
|
(roots.length ? roots : defaultRoots).map((root) => collectVisualGlbs(root)),
|
|
)).flat())].sort();
|
|
const reports = await Promise.all(files.map(async (file) => ({
|
|
file: projectPath(file),
|
|
byteLength: (await stat(file)).size,
|
|
...(await analyzeGlbFile(file)),
|
|
})));
|
|
|
|
if (writeBaseline) {
|
|
const baseline = {
|
|
schemaVersion: 1,
|
|
thresholds: defaultThresholds,
|
|
exceptions: Object.fromEntries(
|
|
reports
|
|
.filter((report) => exceeds(report, defaultThresholds))
|
|
.map((report) => [report.file, {
|
|
maxEstimatedDrawCalls: Math.max(defaultThresholds.maxEstimatedDrawCalls, report.estimatedDrawCalls),
|
|
maxUnbatchedDrawCallSavings: Math.max(
|
|
defaultThresholds.maxUnbatchedDrawCallSavings,
|
|
report.unbatchedDrawCallSavings,
|
|
),
|
|
}]),
|
|
),
|
|
};
|
|
await mkdir(path.dirname(baselineFile), { recursive: true });
|
|
await writeFile(baselineFile, `${JSON.stringify(baseline, null, 2)}\n`, "utf8");
|
|
console.log(`Wrote ${Object.keys(baseline.exceptions).length} performance exceptions to ${projectPath(baselineFile)}.`);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (!await exists(baselineFile)) {
|
|
throw new Error(`Missing ${projectPath(baselineFile)}. Run this script with --write-baseline.`);
|
|
}
|
|
const baseline = JSON.parse(await readFile(baselineFile, "utf8"));
|
|
const thresholds = { ...defaultThresholds, ...baseline.thresholds };
|
|
const regressions = reports.filter((report) => {
|
|
const limits = { ...thresholds, ...baseline.exceptions?.[report.file] };
|
|
return exceeds(report, limits);
|
|
});
|
|
const debt = reports.filter((report) => exceeds(report, thresholds));
|
|
const top = [...reports]
|
|
.sort((left, right) => right.estimatedDrawCalls - left.estimatedDrawCalls)
|
|
.slice(0, 12)
|
|
.map((report) => ({
|
|
asset: report.file,
|
|
calls: report.estimatedDrawCalls,
|
|
avoidable: report.unbatchedDrawCallSavings,
|
|
gpuBatches: report.gpuInstanceBatches,
|
|
nodes: report.nodes,
|
|
}));
|
|
|
|
console.table(top);
|
|
console.log(JSON.stringify({
|
|
status: regressions.length ? "blocked" : "green",
|
|
assets: reports.length,
|
|
knownBudgetExceptions: debt.length,
|
|
regressions: regressions.map((report) => ({
|
|
file: report.file,
|
|
estimatedDrawCalls: report.estimatedDrawCalls,
|
|
unbatchedDrawCallSavings: report.unbatchedDrawCallSavings,
|
|
})),
|
|
}, null, 2));
|
|
if (regressions.length) process.exitCode = 1;
|