mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
perf(awareness): skip echo work for the whole frame while pulses rest
The detection overlay asked the echo field for every visible flight, military aircraft and vessel on every frame while a subject was selected, projecting each to the ground before learning the pulses were resting. It now checks awarenessEchoActive once per frame and builds no echo paths at rest, and awarenessEchoAt checks the level before the projection. A detectionHost test with six echo-eligible flights checks that a resting frame asks for the level fewer times than there are contacts and paints no echo; it fails on the per-contact path. The pulse and echo policy doc comments are attached to their constants again, and one long comment line is re-wrapped.
This commit is contained in:
1 parent
686bc4fd0e
commit
3c6c342b2b
5 files changed
+82
-9
No files matched your search
+13
-2
@@ -45,6 +45,17 @@ export function getAwarenessEchoField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any echo can light at `nowMs`: a field is set and its pulses are
|
||||
* running. The overlay checks this once per frame, so resting pulses cost no
|
||||
* per-contact work.
|
||||
* @param {number} nowMs Monotonic clock in milliseconds.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function awarenessEchoActive(nowMs) {
|
||||
return Boolean(field) && (field.level?.(nowMs) ?? 1) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* How lit a contact's echo is at `nowMs`: 1 the moment a pulse front reaches
|
||||
* its distance, fading as (1 - elapsed / `fadeMs`) squared, 0 otherwise. The
|
||||
@@ -99,13 +110,13 @@ export function awarenessEchoStrength(nowMs, distanceM, radiusM, pulse, echo) {
|
||||
*/
|
||||
export function awarenessEchoAt(layerId, position, nowMs) {
|
||||
if (!field || !position || !layerIds.has(layerId)) return 0;
|
||||
const level = field.level?.(nowMs) ?? 1;
|
||||
if (!(level > 0)) return 0;
|
||||
const ground = Cesium.Ellipsoid.WGS84.scaleToGeodeticSurface(
|
||||
position,
|
||||
groundScratch,
|
||||
);
|
||||
if (!ground) return 0;
|
||||
const level = field.level?.(nowMs) ?? 1;
|
||||
if (!(level > 0)) return 0;
|
||||
return (
|
||||
level *
|
||||
awarenessEchoStrength(
|
||||
|
||||
@@ -4,6 +4,7 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import * as Cesium from 'cesium';
|
||||
import {
|
||||
awarenessEchoActive,
|
||||
awarenessEchoAt,
|
||||
awarenessEchoStrength,
|
||||
clearAwarenessEchoField,
|
||||
@@ -114,8 +115,13 @@ test('echoes scale with the field level and rest with the pulses', (t) => {
|
||||
assert.ok(
|
||||
Math.abs(awarenessEchoAt('flights', contact, t0) - full / 2) < 1e-9,
|
||||
);
|
||||
assert.equal(awarenessEchoActive(t0), true);
|
||||
level = 0;
|
||||
assert.equal(awarenessEchoAt('flights', contact, t0), 0);
|
||||
// At rest the overlay skips echoes for the whole frame.
|
||||
assert.equal(awarenessEchoActive(t0), false);
|
||||
clearAwarenessEchoField();
|
||||
assert.equal(awarenessEchoActive(t0), false);
|
||||
});
|
||||
|
||||
test('near the subject an echo is no brighter than the fading-in front', () => {
|
||||
|
||||
@@ -54,6 +54,7 @@ import {
|
||||
isCyberContactThemeActive as isCyberMapThemeActive,
|
||||
} from '../cyberSonar.js';
|
||||
import {
|
||||
awarenessEchoActive,
|
||||
awarenessEchoBracket,
|
||||
awarenessEchoColor,
|
||||
getAwarenessEchoField,
|
||||
@@ -1344,7 +1345,8 @@ function _drawOverlay(frame) {
|
||||
const bracketPaths = new Map();
|
||||
// Global Context sonar echoes: a second bracket, banded by strength, around
|
||||
// each flight or vessel a pulse front has just crossed.
|
||||
const echoField = getAwarenessEchoField();
|
||||
// Resting pulses light nothing, so the per-contact echo work is skipped.
|
||||
const echoField = awarenessEchoActive(now) ? getAwarenessEchoField() : null;
|
||||
const echoPaths = echoField ? new Array(ECHO_ALPHA_STEPS + 1) : null;
|
||||
const pathFor = (map, color, alpha) => {
|
||||
const band = Math.max(
|
||||
|
||||
@@ -673,6 +673,60 @@ test('Global Context echo brackets reach the canvas in the detection style colou
|
||||
`${style} echoes take the style line colour, got ${strokes}`,
|
||||
);
|
||||
}
|
||||
|
||||
} finally {
|
||||
clearAwarenessEchoField();
|
||||
env.cleanup();
|
||||
}
|
||||
});
|
||||
|
||||
test('resting Global Context pulses skip echo work for the whole frame, not per contact', () => {
|
||||
const env = installEnvironment();
|
||||
// Six echo-eligible flights on screen; the identity camera maps x/y
|
||||
// straight to device coordinates.
|
||||
const flights = Array.from({ length: 6 }, (_, index) => ({
|
||||
position: new Cesium.Cartesian3(-0.5 + index * 0.2, 0, 6_356_752),
|
||||
sourceId: `flight-${index}`,
|
||||
id: `ECHO${index}`,
|
||||
metric: `FL${300 + index}`,
|
||||
type: 'AIR',
|
||||
}));
|
||||
const layer = { id: 'flights', getDetectableObjects: () => flights };
|
||||
let levelCalls = 0;
|
||||
try {
|
||||
setAwarenessEchoField({
|
||||
center: Cesium.Cartesian3.fromDegrees(0, 89, 0),
|
||||
radiusM: 250_000,
|
||||
pulse: { count: 1000, periodMs: 1000, startFraction: 0 },
|
||||
echo: {
|
||||
layerIds: ['flights'],
|
||||
fadeMs: 60_000,
|
||||
color: 'rgb(1, 2, 3)',
|
||||
spreadPx: 8,
|
||||
lineWidth: 3.25,
|
||||
},
|
||||
level: () => {
|
||||
levelCalls++;
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
initWorldOverlay(env.viewer);
|
||||
initDetection(env.viewer, [layer], () => {});
|
||||
setMode('DENSE');
|
||||
settleFrame(env);
|
||||
levelCalls = 0;
|
||||
settleFrame(env);
|
||||
const painted = getDetectionDiagnostics()?.visibleCount ?? flights.length;
|
||||
assert.ok(painted >= 2, `contacts on screen: ${painted}`);
|
||||
// One check per painted frame, however many contacts are on screen.
|
||||
assert.ok(levelCalls >= 1, 'the frame asks whether echoes are live');
|
||||
assert.ok(levelCalls < painted, `${levelCalls} level checks for ${painted} contacts`);
|
||||
assert.ok(
|
||||
!env.detectionCtx.calls.some(
|
||||
([name, , style]) => name === 'stroke' && style === 'rgb(1, 2, 3)',
|
||||
),
|
||||
'no echoes while the pulses rest',
|
||||
);
|
||||
} finally {
|
||||
clearAwarenessEchoField();
|
||||
env.cleanup();
|
||||
|
||||
@@ -76,15 +76,17 @@ export const AWARENESS_RANGE_RING_STYLE = Object.freeze({
|
||||
* `fadeOutPower` down to `endOpacityFraction` of `peakOpacity`, so a pulse
|
||||
* is dim but still visible when it reaches the ring and the ring flashes.
|
||||
* Each line is `width` CSS pixels wide, its outer `outlineWidth` a dark
|
||||
* halo, in the ring's colours, as Cesium's polyline outline material draws. Behind each line a radial gradient in the same blue
|
||||
* fades inward over `gradientDepthFraction` of the radius, at most
|
||||
* halo, in the ring's colours, as Cesium's polyline outline material draws.
|
||||
* Behind each line a radial gradient in the same blue fades inward over
|
||||
* `gradientDepthFraction` of the radius, at most
|
||||
* `gradientOpacity` strong. One unpickable disc draws the lines and the
|
||||
* gradient, so pulses never catch a click meant for a contact; its shader
|
||||
* takes at most four pulses. The disc is rebuilt only after the subject
|
||||
* moves `gradientRebuildM`, so the pulses sit at most that far off the ring's
|
||||
* centre, about a pixel when the ring fills the screen; while the pulses
|
||||
* rest the hidden disc is left alone and catches up when they wake. The disc is hidden
|
||||
* while the camera is below its height, where it would sweep the sky.
|
||||
* rest the hidden disc is left alone and catches up when they wake. The
|
||||
* disc is hidden while the camera is below its height, where it would sweep
|
||||
* the sky.
|
||||
* The pulses run for `activeMs` (three ring flashes) after a selection or
|
||||
* a camera move, fading in and out over `settleMs`, then rest: the ring
|
||||
* stays on screen, static, and nothing asks for frames, so a parked view
|
||||
@@ -95,7 +97,6 @@ export const AWARENESS_RANGE_RING_STYLE = Object.freeze({
|
||||
* Cyber sonar's 80 ms cadence, so the two never beat. When something else
|
||||
* already renders continuously they update every frame. Skipped under
|
||||
* prefers-reduced-motion. */
|
||||
|
||||
export const AWARENESS_RANGE_PULSE = Object.freeze({
|
||||
count: 3,
|
||||
periodMs: 9000,
|
||||
@@ -120,7 +121,6 @@ export const AWARENESS_RANGE_PULSE = Object.freeze({
|
||||
* bracket. It starts on the bracket and spreads outward by up to `spreadPx`
|
||||
* as it fades over `fadeMs`, so the frame pulses. Only `layerIds` echo;
|
||||
* mapped installations have no detection bracket. */
|
||||
|
||||
export const AWARENESS_RANGE_ECHO = Object.freeze({
|
||||
layerIds: ['flights', 'military', 'ais-live-vessels'],
|
||||
fadeMs: 1400,
|
||||
|
||||
Reference in new issue
Block a user