mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-15 15:52:21 +00:00
engine.imagery gains what these rasters need, on every engine: - geographic (EPSG:4326) tile schemes and a transform applied to each decoded tile (the satellite infrared mask); - staged source changes, which keep the drawn source until the new one has loaded (the weather frame swap), and credit-only changes in place; - raised layers that draw on a shell above terrain and 3D tiles, rising with the camera, with a detail image inside a rectangle. On Cesium this is the weather shell primitive, moved into the engine; on Navara the pixel's ray meets the ellipsoid grown by the height. Rain radar, satellite clouds, lightning, the wind field and the Bhote Koshi event and locator each draw through one render/display.js on both engines; their Navara renderers and Navara's weather surface pass go. Cesium's pixels are unchanged in every compared scene. On Navara the Bhote Koshi images are no longer upside down. Two engine-specific parts stay behind one backend option each: the wind's streamlines and the Bhote Koshi story entities. The coupling report now counts defineGroundEffect calls, not its own declaration. Coupling: Navara renderer lines fall from 2304 to 1695, Navara ground passes from 3 to 1 (the Global Context pulses), engine kind checks from 5 to 2. The contract suite passes on both engines (Cesium 71/71, Navara 74/74).
235 lines
7.5 KiB
JavaScript
235 lines
7.5 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* How tightly the application is still tied to one globe engine: counts of
|
|
* engine-specific code outside the engines (src/globe/cesium, src/globe/navara).
|
|
* The renderer-abstraction work drives every number down; none may grow.
|
|
* engineCoupling.test.mjs (npm test) compares a fresh count with
|
|
* scripts/engine-coupling-baseline.json.
|
|
*
|
|
* Only production sources count: `.js` and `.vue` under src. Tests, fixtures
|
|
* and test doubles are `.mjs`.
|
|
*
|
|
* node scripts/report-engine-coupling.mjs # table
|
|
* node scripts/report-engine-coupling.mjs --json
|
|
* node scripts/report-engine-coupling.mjs --write-baseline
|
|
*/
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
|
const ROOT = fileURLToPath(new URL('..', import.meta.url));
|
|
export const BASELINE_PATH = path.join(
|
|
ROOT,
|
|
'scripts',
|
|
'engine-coupling-baseline.json',
|
|
);
|
|
|
|
const inEngine = (file) =>
|
|
file.startsWith('src/globe/cesium/') || file.startsWith('src/globe/navara/');
|
|
const inGlobe = (file) => file.startsWith('src/globe/');
|
|
const isLayerRenderer = (engine) => (file) =>
|
|
file.startsWith('src/layers/') &&
|
|
new RegExp(`/render/${engine}([^/]*\\.js|/.+\\.js)$`).test(file);
|
|
const isRenderer = (file) => /\/render\//.test(file);
|
|
|
|
const lines = (text) => text.split('\n');
|
|
const countLines = (text, pattern) =>
|
|
lines(text).filter((line) => pattern.test(line)).length;
|
|
const countMatches = (text, pattern) =>
|
|
(text.match(new RegExp(pattern.source, 'g')) ?? []).length;
|
|
|
|
/**
|
|
* The metrics, in report order. `measure(files)` gets every production
|
|
* source as `{ file, text }` (file relative to the repository, with `/`).
|
|
*/
|
|
export const ENGINE_COUPLING_METRICS = Object.freeze([
|
|
{
|
|
id: 'cesiumLayerRendererLines',
|
|
label: 'Lines in src/layers/**/render/cesium*',
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => isLayerRenderer('cesium')(file))
|
|
.reduce((sum, { text }) => sum + lines(text).length, 0),
|
|
},
|
|
{
|
|
id: 'navaraLayerRendererLines',
|
|
label: 'Lines in src/layers/**/render/navara*',
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => isLayerRenderer('navara')(file))
|
|
.reduce((sum, { text }) => sum + lines(text).length, 0),
|
|
},
|
|
{
|
|
id: 'cesiumImportFiles',
|
|
label: "Files importing 'cesium' outside src/globe/cesium and render/",
|
|
measure: (files) =>
|
|
files.filter(
|
|
({ file, text }) =>
|
|
!file.startsWith('src/globe/cesium/') &&
|
|
!isRenderer(file) &&
|
|
/from\s+['"]cesium['"]|import\(\s*['"]cesium['"]\s*\)/.test(text),
|
|
).length,
|
|
},
|
|
{
|
|
id: 'viewerLines',
|
|
label: 'Lines naming `viewer` outside src/globe/cesium',
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => !file.startsWith('src/globe/cesium/'))
|
|
.reduce((sum, { text }) => sum + countLines(text, /\bviewer\b/), 0),
|
|
},
|
|
{
|
|
id: 'navaraKindChecks',
|
|
label: "`kind === 'navara'` checks outside src/globe",
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => !inGlobe(file))
|
|
.reduce(
|
|
(sum, { text }) =>
|
|
sum + countMatches(text, /kind\s*[!=]==?\s*['"]navara['"]/),
|
|
0,
|
|
),
|
|
},
|
|
{
|
|
id: 'engineSniffCalls',
|
|
label: 'isGlobeEngine / engineFromViewer calls outside src/globe',
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => !inGlobe(file))
|
|
.reduce(
|
|
(sum, { text }) =>
|
|
sum + countMatches(text, /\b(?:isGlobeEngine|engineFromViewer)\(/),
|
|
0,
|
|
),
|
|
},
|
|
{
|
|
id: 'engineExtensionReads',
|
|
label: 'Reads of engine.extensions (cesium, navara) outside src/globe',
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => !inGlobe(file))
|
|
.reduce(
|
|
(sum, { text }) =>
|
|
sum + countMatches(text, /\bextensions\??\.(?:navara|cesium)\b/),
|
|
0,
|
|
),
|
|
},
|
|
{
|
|
id: 'groundEffectPasses',
|
|
label: 'Navara ground passes (defineGroundEffect calls)',
|
|
measure: (files) =>
|
|
files.reduce(
|
|
(sum, { text }) =>
|
|
// Calls only: the helper's own declaration is not a pass.
|
|
sum + countMatches(text, /(?<!function )\bdefineGroundEffect\(\s*\{/),
|
|
0,
|
|
),
|
|
},
|
|
{
|
|
id: 'cesiumSdkLoads',
|
|
label: 'Cesium SDK loads outside the engines and loadCesium.js',
|
|
measure: (files) =>
|
|
files
|
|
.filter(
|
|
({ file }) => !inEngine(file) && file !== 'src/app/loadCesium.js',
|
|
)
|
|
.reduce(
|
|
(sum, { text }) =>
|
|
sum + countMatches(text, /\b(?:loadCesiumSdk|whenCesiumSdk)\(/),
|
|
0,
|
|
),
|
|
},
|
|
{
|
|
id: 'screenSpaceHandlerFiles',
|
|
label: 'Files in src/layers and src/data using ScreenSpaceEventHandler',
|
|
measure: (files) =>
|
|
files.filter(
|
|
({ file, text }) =>
|
|
(file.startsWith('src/layers/') || file.startsWith('src/data/')) &&
|
|
/ScreenSpaceEventHandler/.test(text),
|
|
).length,
|
|
},
|
|
{
|
|
id: 'engineNameLines',
|
|
label:
|
|
"Lines with an engine name literal ('navara', 'cesium') outside src/globe",
|
|
measure: (files) =>
|
|
files
|
|
.filter(({ file }) => !inGlobe(file))
|
|
.reduce(
|
|
(sum, { text }) =>
|
|
sum + countLines(text, /['"](?:navara|cesium)['"]/),
|
|
0,
|
|
),
|
|
},
|
|
]);
|
|
|
|
/** Production sources under `root`/src: `.js` and `.vue`. */
|
|
export function readProductionSources(root = ROOT) {
|
|
const files = [];
|
|
const visit = (directory) => {
|
|
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
const absolute = path.join(directory, entry.name);
|
|
if (entry.isDirectory()) visit(absolute);
|
|
else if (entry.isFile() && /\.(?:js|vue)$/.test(entry.name))
|
|
files.push({
|
|
file: path.relative(root, absolute).split(path.sep).join('/'),
|
|
text: fs.readFileSync(absolute, 'utf8'),
|
|
});
|
|
}
|
|
};
|
|
visit(path.join(root, 'src'));
|
|
return files.sort((a, b) => a.file.localeCompare(b.file));
|
|
}
|
|
|
|
/** Every metric's value for the sources under `root`. */
|
|
export function measureEngineCoupling(root = ROOT) {
|
|
const files = readProductionSources(root);
|
|
return Object.fromEntries(
|
|
ENGINE_COUPLING_METRICS.map(({ id, measure }) => [id, measure(files)]),
|
|
);
|
|
}
|
|
|
|
/** The metrics that grew past `baseline`, with both values. */
|
|
export function couplingGrowth(measured, baseline) {
|
|
return ENGINE_COUPLING_METRICS.filter(
|
|
({ id }) => !(measured[id] <= (baseline[id] ?? -Infinity)),
|
|
).map(({ id, label }) => ({
|
|
id,
|
|
label,
|
|
measured: measured[id],
|
|
baseline: baseline[id],
|
|
}));
|
|
}
|
|
|
|
export function readBaseline(file = BASELINE_PATH) {
|
|
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
}
|
|
|
|
function main(args) {
|
|
const measured = measureEngineCoupling();
|
|
if (args.includes('--write-baseline')) {
|
|
const note = args.find((arg) => arg.startsWith('--note='))?.slice(7);
|
|
fs.writeFileSync(
|
|
BASELINE_PATH,
|
|
`${JSON.stringify({ metrics: measured, ...(note ? { measuredAt: note } : {}) }, null, 2)}\n`,
|
|
);
|
|
}
|
|
if (args.includes('--json')) {
|
|
console.log(JSON.stringify(measured, null, 2));
|
|
return;
|
|
}
|
|
const baseline = fs.existsSync(BASELINE_PATH) ? readBaseline().metrics : {};
|
|
for (const { id, label } of ENGINE_COUPLING_METRICS) {
|
|
const was = baseline[id];
|
|
const change =
|
|
was === undefined
|
|
? ''
|
|
: ` (baseline ${was}, ${measured[id] - was >= 0 ? '+' : ''}${measured[id] - was})`;
|
|
console.log(`${String(measured[id]).padStart(7)} ${label}${change}`);
|
|
}
|
|
}
|
|
|
|
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href)
|
|
main(process.argv.slice(2));
|