115 lines
4.8 KiB
JavaScript
115 lines
4.8 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { Worker } from 'node:worker_threads';
|
|
import { generateMap } from './mapGenerator.js';
|
|
import { createWorldMap } from './worldMap.js';
|
|
import { generatePatch } from './mapPatch.js';
|
|
import { collectTransferableBuffers } from './transferUtils.js';
|
|
|
|
const BASE_SEED = 24681357;
|
|
const PATCH_SEED = 0x4a35b921;
|
|
|
|
function centeredRect(world, width, height) {
|
|
return {
|
|
x0: Math.floor((world.width - width) / 2),
|
|
y0: Math.floor((world.height - height) / 2),
|
|
x1: Math.floor((world.width - width) / 2) + width,
|
|
y1: Math.floor((world.height - height) / 2) + height,
|
|
};
|
|
}
|
|
|
|
function workerAdapter(url) {
|
|
const code = `import { parentPort } from 'node:worker_threads';\n`
|
|
+ `globalThis.self = { onmessage: null, postMessage(message, transfer) { parentPort.postMessage(message, transfer); } };\n`
|
|
+ `await import(${JSON.stringify(url.href)});\n`
|
|
+ `parentPort.on('message', data => self.onmessage?.({ data }));\n`;
|
|
return new Worker(new URL(`data:text/javascript,${encodeURIComponent(code)}`), { type: 'module', execArgv: [] });
|
|
}
|
|
|
|
async function runDirect(width, height) {
|
|
const initial = generateMap(BASE_SEED, { terrainType: 'auto', onProgress() {} });
|
|
const world = createWorldMap(initial);
|
|
const rect = centeredRect(world, width, height);
|
|
const startedAt = Date.now();
|
|
const result = generatePatch(world, rect, {
|
|
patchMode: 'expansion', terrainType: 'auto', seed: PATCH_SEED, variant: 1,
|
|
maxQualityRetries: 0, qualityTerrainAttempts: 1, acceptBestAvailableQuality: true,
|
|
onProgress() {},
|
|
});
|
|
const row = {
|
|
mode: 'direct', size: `${width}x${height}`, ms: Date.now() - startedAt,
|
|
ok: result?.ok === true, code: result?.code || null,
|
|
tileCount: result?.tileCount || 1,
|
|
seam: result?.seamDiagnostics?.status || null,
|
|
hardPass: result?.seamDiagnostics?.hardPass ?? null,
|
|
};
|
|
assert.equal(row.ok, true, `${row.size}: ${result?.reason || result?.code}`);
|
|
assert.equal(row.hardPass, true, `${row.size}: seam hard gate failed`);
|
|
return row;
|
|
}
|
|
|
|
async function runWorker(width, height) {
|
|
const initial = generateMap(BASE_SEED, { terrainType: 'auto', onProgress() {} });
|
|
const world = createWorldMap(initial);
|
|
const rect = centeredRect(world, width, height);
|
|
const preview = structuredClone(world);
|
|
const transfer = Array.from(collectTransferableBuffers(preview));
|
|
const worker = workerAdapter(new URL('./mapPatchWorker.js', import.meta.url));
|
|
const startedAt = Date.now();
|
|
const message = await new Promise((resolve, reject) => {
|
|
const timer = setTimeout(() => reject(new Error(`${width}x${height}: worker timeout`)), 120000);
|
|
worker.on('message', message => {
|
|
if (message?.type === 'progress') return;
|
|
clearTimeout(timer);
|
|
resolve(message);
|
|
});
|
|
worker.on('error', error => { clearTimeout(timer); reject(error); });
|
|
worker.postMessage({
|
|
id: 1, world: preview, rect,
|
|
options: {
|
|
patchMode: 'expansion', terrainType: 'auto', seed: PATCH_SEED, variant: 1,
|
|
maxQualityRetries: 0, qualityTerrainAttempts: 1, acceptBestAvailableQuality: true,
|
|
},
|
|
}, transfer);
|
|
});
|
|
const row = {
|
|
mode: 'worker', size: `${width}x${height}`, ms: Date.now() - startedAt,
|
|
outerOk: message?.ok === true, ok: message?.result?.ok === true,
|
|
code: message?.result?.code || null, tileCount: message?.result?.tileCount || 1,
|
|
seam: message?.result?.seamDiagnostics?.status || null,
|
|
hardPass: message?.result?.seamDiagnostics?.hardPass ?? null,
|
|
};
|
|
worker.unref();
|
|
void worker.terminate();
|
|
assert.equal(row.outerOk, true, `${row.size}: worker outer failure ${message?.error || ''}`);
|
|
assert.equal(row.ok, true, `${row.size}: ${message?.result?.reason || message?.result?.code}`);
|
|
assert.equal(row.hardPass, true, `${row.size}: seam hard gate failed`);
|
|
return row;
|
|
}
|
|
|
|
const args = process.argv.slice(2);
|
|
if (args[0] === '--direct') {
|
|
console.log(JSON.stringify(await runDirect(Number(args[1]), Number(args[2]))));
|
|
process.exit(0);
|
|
}
|
|
if (args[0] === '--worker') {
|
|
console.log(JSON.stringify(await runWorker(Number(args[1]), Number(args[2]))));
|
|
process.exit(0);
|
|
}
|
|
|
|
const specs = [
|
|
['--direct', 300, 339],
|
|
['--direct', 500, 350],
|
|
['--direct', 600, 400],
|
|
['--worker', 600, 400],
|
|
];
|
|
const results = [];
|
|
for (const spec of specs) {
|
|
const child = spawnSync(process.execPath, [new URL(import.meta.url).pathname, ...spec.map(String)], {
|
|
cwd: process.cwd(), encoding: 'utf8', timeout: 130000, maxBuffer: 4 * 1024 * 1024,
|
|
});
|
|
assert.equal(child.status, 0, `${spec.slice(1).join('x')} ${spec[0]} failed:\n${child.stderr || child.stdout}`);
|
|
const lines = child.stdout.trim().split(/\r?\n/).filter(Boolean);
|
|
results.push(JSON.parse(lines.at(-1)));
|
|
}
|
|
console.log(JSON.stringify({ ok: true, results }, null, 2));
|