140 lines
4.2 KiB
JavaScript
140 lines
4.2 KiB
JavaScript
#!/usr/bin/env node
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
advanceSpriteFocus,
|
|
} from '../src/data/focusDeemphasis.js';
|
|
import {
|
|
applyAircraftBillboardTreatment,
|
|
} from '../src/data/aircraftRecession.js';
|
|
|
|
if (typeof global.gc !== 'function') {
|
|
throw new Error('focus-allocation-check.mjs requires node --expose-gc');
|
|
}
|
|
|
|
const CALLS = 100_000;
|
|
const TRIALS = 9;
|
|
const WARMUP_CALLS = 200_000;
|
|
const PURE_CALL_BUDGET = 16;
|
|
const PRODUCTION_SPRITE_TICK_BUDGET = 212;
|
|
|
|
function median(values) {
|
|
const sorted = [...values].sort((a, b) => a - b);
|
|
return sorted[Math.floor(sorted.length / 2)];
|
|
}
|
|
|
|
function bytesPerCall(fn) {
|
|
for (let index = 0; index < WARMUP_CALLS; index += 1) fn();
|
|
const samples = [];
|
|
for (let trial = 0; trial < TRIALS; trial += 1) {
|
|
global.gc();
|
|
const before = process.memoryUsage().heapUsed;
|
|
let checksum = 0;
|
|
for (let index = 0; index < CALLS; index += 1) checksum += fn();
|
|
const after = process.memoryUsage().heapUsed;
|
|
// Keep the returned scalar observable so V8 cannot delete the calls.
|
|
assert.ok(Number.isFinite(checksum));
|
|
samples.push((after - before) / CALLS);
|
|
}
|
|
const rawMedian = median(samples);
|
|
return { rawMedian, samples };
|
|
}
|
|
|
|
function netOfLoopBaseline(observed, baseline) {
|
|
// V8's callback/checksum loop itself retains roughly one boxed-number word
|
|
// per call until the next collection. Subtract the identically measured
|
|
// no-op loop so this gate reports allocation attributable to the treatment.
|
|
const rawMedian = Math.max(0, observed.rawMedian - baseline.rawMedian);
|
|
return {
|
|
rawMedian,
|
|
roundedMedian: Math.round(rawMedian),
|
|
observedRawMedian: observed.rawMedian,
|
|
baselineRawMedian: baseline.rawMedian,
|
|
samples: observed.samples,
|
|
};
|
|
}
|
|
|
|
const target = {
|
|
screenRect: { left: 0, top: 0, right: 10, bottom: 10 },
|
|
paddingPx: 18,
|
|
cameraDistance: 1000,
|
|
};
|
|
const sprite = {};
|
|
const focusInput = {
|
|
screenPosition: { x: 5, y: 5 },
|
|
cameraDistance: 1200,
|
|
nowMs: 1000,
|
|
target,
|
|
};
|
|
|
|
const baseColor = { withAlpha: (alpha) => ({ alpha }) };
|
|
const billboard = { scale: 1, color: { alpha: 1 } };
|
|
const aircraftInput = {
|
|
billboard,
|
|
baseScale: 1,
|
|
baseAlpha: 1,
|
|
baseColor,
|
|
focusFactor: 1,
|
|
cameraDistanceM: 100,
|
|
cameraHeightM: 1000,
|
|
};
|
|
|
|
const baseline = bytesPerCall(() => focusInput.nowMs);
|
|
const focus = netOfLoopBaseline(
|
|
bytesPerCall(() => advanceSpriteFocus(sprite, focusInput).factor),
|
|
baseline,
|
|
);
|
|
const aircraft = netOfLoopBaseline(
|
|
bytesPerCall(() => applyAircraftBillboardTreatment(aircraftInput).alpha),
|
|
baseline,
|
|
);
|
|
const productionSpriteTick = netOfLoopBaseline(
|
|
bytesPerCall(() => {
|
|
// Match the hot civilian/military sprite path: the caller constructs fresh
|
|
// option literals, consumes the shared focus result immediately, and then
|
|
// constructs the composed aircraft-treatment options for the same sprite.
|
|
const focusResult = advanceSpriteFocus(sprite, {
|
|
screenPosition: focusInput.screenPosition,
|
|
cameraDistance: focusInput.cameraDistance,
|
|
nowMs: focusInput.nowMs,
|
|
target,
|
|
});
|
|
const treatment = applyAircraftBillboardTreatment({
|
|
billboard,
|
|
baseScale: aircraftInput.baseScale,
|
|
baseAlpha: aircraftInput.baseAlpha,
|
|
baseColor,
|
|
focusFactor: focusResult.factor,
|
|
cameraDistanceM: aircraftInput.cameraDistanceM,
|
|
cameraHeightM: aircraftInput.cameraHeightM,
|
|
});
|
|
return focusResult.factor + treatment.alpha;
|
|
}),
|
|
baseline,
|
|
);
|
|
|
|
assert.ok(
|
|
focus.roundedMedian <= PURE_CALL_BUDGET,
|
|
`advanceSpriteFocus allocated ${focus.rawMedian.toFixed(3)} B/call median`,
|
|
);
|
|
assert.ok(
|
|
aircraft.roundedMedian <= PURE_CALL_BUDGET,
|
|
`applyAircraftBillboardTreatment allocated ${aircraft.rawMedian.toFixed(3)} B/call median`,
|
|
);
|
|
assert.ok(
|
|
productionSpriteTick.roundedMedian <= PRODUCTION_SPRITE_TICK_BUDGET,
|
|
`production sprite tick allocated ${productionSpriteTick.rawMedian.toFixed(3)} B/call median`,
|
|
);
|
|
|
|
console.log(JSON.stringify({
|
|
callsPerTrial: CALLS,
|
|
trials: TRIALS,
|
|
budgets: {
|
|
pureCall: PURE_CALL_BUDGET,
|
|
productionSpriteTick: PRODUCTION_SPRITE_TICK_BUDGET,
|
|
},
|
|
loopBaseline: baseline,
|
|
advanceSpriteFocus: focus,
|
|
applyAircraftBillboardTreatment: aircraft,
|
|
productionSpriteTick,
|
|
}));
|