mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-12 22:32:19 +00:00
Navara now matches Cesium where the review found it diverging: a flight holds the camera inputs, camera.changed counts turns as Cesium does, render requests made during a frame survive it, follow pinches zoom, ground widths follow the pixel ratio and Cyber sonar reaches glyph caches and models. On Cesium, a staged imagery swap keeps loading tiles, setView replaces a running flight, and a cleared weather detail goes away. The contract gains MoverSet.failed, maps.terrainRevision, render.maxTextureSize and surface.sampleTerrainMostDetailed on every engine, so layers retry failed models, key road heights by terrain, size weather textures to the GPU and place Bhote Koshi cards on detailed terrain. Engine switches keep the live view, follows, focus, layer options and cards: a pick hands over the exact pose, applies no address options and pans no carried-over card; a history visit still applies its entry. Layers put GhostMaps pins, cable landings, OSM Places and directions on the measured surface without per-frame sampling, draw the Cockpit contact dot, hide disabled CCTV on attach, and keep ALPR running outside secure contexts. KML imports draw raised polygon fills, rotated and coloured ground overlays and north-referenced icon headings, and report files with nothing to draw. QA scripts read the contract instead of Cesium internals (test:track is 109/109 again), wait for the camera to rest before clicking, and give cold switches and parked engines realistic time. Docs follow the code.
186 lines
6.3 KiB
JavaScript
186 lines
6.3 KiB
JavaScript
// The production build replaces every `cesium` import with the global that
|
|
// /cesium/Cesium.js defines, and that script loads only when a Cesium globe
|
|
// first starts (src/app/loadCesium.js). A module that reads a Cesium binding
|
|
// while it loads would therefore throw on a page that starts on Navara. This
|
|
// check lists such reads: a reference to a binding imported from `cesium`, or
|
|
// to the `Cesium` global, that runs when its module is evaluated (top level,
|
|
// class heritage, static members, immediately invoked functions) rather than
|
|
// inside a function that runs later.
|
|
//
|
|
// node scripts/check-cesium-load-time.mjs [--json]
|
|
|
|
import { existsSync, readFileSync } from 'node:fs';
|
|
import { execFileSync } from 'node:child_process';
|
|
import { parse } from '@babel/parser';
|
|
import { vueScripts } from './vue-scripts.mjs';
|
|
|
|
const root = new URL('..', import.meta.url);
|
|
|
|
/** Files under src/ that the app ships (tests and test support excluded). */
|
|
export function sourceFiles() {
|
|
return execFileSync('git', ['ls-files', '--cached', '--others', '--exclude-standard', 'src'], {
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
})
|
|
.split('\n')
|
|
.filter(
|
|
(path) =>
|
|
/\.(?:js|mjs|vue)$/.test(path) &&
|
|
!/\.test\.m?js$/.test(path) &&
|
|
!path.startsWith('src/testSupport/') &&
|
|
!path.includes('/testing/') &&
|
|
existsSync(new URL(path, root)),
|
|
);
|
|
}
|
|
|
|
const FUNCTION_TYPES = new Set([
|
|
'FunctionDeclaration',
|
|
'FunctionExpression',
|
|
'ArrowFunctionExpression',
|
|
'ObjectMethod',
|
|
'ClassMethod',
|
|
'ClassPrivateMethod',
|
|
]);
|
|
|
|
/** Names a module binds to `cesium` imports. */
|
|
function cesiumBindings(program) {
|
|
const names = new Set();
|
|
for (const node of program.body) {
|
|
if (node.type !== 'ImportDeclaration' || node.source.value !== 'cesium')
|
|
continue;
|
|
for (const specifier of node.specifiers) names.add(specifier.local.name);
|
|
}
|
|
return names;
|
|
}
|
|
|
|
function isReference(node, parent, key) {
|
|
if (node.type !== 'Identifier') return false;
|
|
if (!parent) return true;
|
|
if (
|
|
(parent.type === 'MemberExpression' ||
|
|
parent.type === 'OptionalMemberExpression') &&
|
|
key === 'property' &&
|
|
!parent.computed
|
|
)
|
|
return false;
|
|
if (
|
|
(parent.type === 'ObjectProperty' ||
|
|
parent.type === 'ClassProperty' ||
|
|
parent.type === 'ObjectMethod' ||
|
|
parent.type === 'ClassMethod') &&
|
|
key === 'key' &&
|
|
!parent.computed
|
|
)
|
|
return false;
|
|
if (
|
|
parent.type === 'ImportSpecifier' ||
|
|
parent.type === 'ImportNamespaceSpecifier' ||
|
|
parent.type === 'ImportDefaultSpecifier' ||
|
|
(parent.type === 'ExportSpecifier' && key !== 'local')
|
|
)
|
|
return false;
|
|
if (parent.type === 'LabeledStatement' || parent.type === 'BreakStatement')
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Load-time references in one module. `deferred` is true below a function
|
|
* body that runs later; an immediately invoked function and a class's static
|
|
* members and heritage run at load and stay not deferred.
|
|
*/
|
|
export function loadTimeReferences(code, path = '<module>') {
|
|
if (path.endsWith('.vue'))
|
|
return vueScripts(code, path).flatMap((script) =>
|
|
loadTimeReferences(script).map((reference) => ({ ...reference, path })),
|
|
);
|
|
const program = parse(code, {
|
|
sourceType: 'module',
|
|
plugins: ['classProperties', 'classStaticBlock', 'importAttributes'],
|
|
}).program;
|
|
const names = cesiumBindings(program);
|
|
const found = [];
|
|
// A re-export from `cesium` becomes a read of the global's property when
|
|
// the module loads.
|
|
for (const node of program.body)
|
|
if (
|
|
(node.type === 'ExportNamedDeclaration' ||
|
|
node.type === 'ExportAllDeclaration') &&
|
|
node.source?.value === 'cesium'
|
|
)
|
|
found.push({
|
|
path,
|
|
line: node.loc.start.line,
|
|
name: 'export from cesium',
|
|
});
|
|
const hasGlobal = /\bCesium\b/.test(code);
|
|
if (!names.size && !hasGlobal) return found;
|
|
// A `Cesium` global reference counts when the module does not bind it.
|
|
const watched = (name) =>
|
|
names.has(name) || (name === 'Cesium' && !names.has('Cesium'));
|
|
const visit = (node, parent, key, deferred) => {
|
|
if (!node || typeof node.type !== 'string') return;
|
|
if (node.type === 'ImportDeclaration') return;
|
|
if (!deferred && isReference(node, parent, key) && watched(node.name))
|
|
found.push({ path, line: node.loc.start.line, name: node.name });
|
|
let childDeferred = deferred;
|
|
if (FUNCTION_TYPES.has(node.type)) {
|
|
const invoked =
|
|
parent &&
|
|
(parent.type === 'CallExpression' || parent.type === 'NewExpression') &&
|
|
key === 'callee';
|
|
childDeferred = deferred || !invoked;
|
|
}
|
|
if (
|
|
(node.type === 'ClassProperty' || node.type === 'ClassPrivateProperty') &&
|
|
!node.static
|
|
)
|
|
childDeferred = true;
|
|
for (const [childKey, value] of Object.entries(node)) {
|
|
if (
|
|
childKey === 'loc' ||
|
|
childKey === 'leadingComments' ||
|
|
childKey === 'trailingComments' ||
|
|
childKey === 'innerComments'
|
|
)
|
|
continue;
|
|
// Computed keys and parameter defaults of a deferred function: its key
|
|
// is evaluated with the enclosing scope, its body later.
|
|
const keyDeferred =
|
|
FUNCTION_TYPES.has(node.type) && childKey === 'key'
|
|
? deferred
|
|
: childDeferred;
|
|
if (Array.isArray(value))
|
|
for (const child of value) visit(child, node, childKey, keyDeferred);
|
|
else if (value && typeof value === 'object')
|
|
visit(value, node, childKey, keyDeferred);
|
|
}
|
|
};
|
|
visit(program, null, null, false);
|
|
return found;
|
|
}
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
const found = [];
|
|
for (const path of sourceFiles()) {
|
|
const code = readFileSync(new URL(path, root), 'utf8');
|
|
try {
|
|
found.push(...loadTimeReferences(code, path));
|
|
} catch (error) {
|
|
console.error(`${path}: ${error.message}`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
if (process.argv.includes('--json'))
|
|
console.log(JSON.stringify(found, null, 2));
|
|
else
|
|
for (const { path, line, name } of found)
|
|
console.log(`${path}:${line} reads ${name} while the module loads`);
|
|
if (found.length) {
|
|
console.error(
|
|
`${found.length} load-time Cesium read(s); a page that starts on Navara has no Cesium global yet.`,
|
|
);
|
|
process.exitCode = 1;
|
|
} else console.log('No module reads Cesium while it loads.');
|
|
}
|