import * as Cesium from 'cesium'; import { TRAFFIC_TIMING_ENABLED, MAX_WAYPOINTS_PER_ROAD, DOT_HEIGHT_OFFSET, } from './policy.js'; export function createTiming({ state: layerState, services, parts, source }) { /** * Return development timing counters for the capture harness and inertness test. * This named export is unused by the application and removed from production. * @returns {{enabled:boolean, marksInstalled:number, traceObjectsCreated:number, * uncorrelatedTracesDropped:number}} */ function getTrafficTimingDiagnostics() { return { enabled: Boolean(TRAFFIC_TIMING_ENABLED), marksInstalled: layerState._trafficTimingMoveEndRemover ? 1 : 0, traceObjectsCreated: layerState._trafficTimingTracesCreated, uncorrelatedTracesDropped: layerState._trafficTimingDroppedTraces, }; } // ─── Development-only causal timing ─────────────────────── /** * Return (and optionally update) the current trace's state for a render pass. * This function is only reachable when `TRAFFIC_TIMING_ENABLED` is true. * * @param {Object|null} trace - Correlated load trace. * @param {'major'|'full'} pass - Road-fetch/render pass. * @param {string} [source] - Client cache or proxy/network source. * @returns {Object|null} Mutable pass timing state. */ function trafficTimingPass(trace, pass, source) { if (!trace) return null; let state = trace.passes.get(pass); if (!state) { state = { trace, pass, source: source || 'unknown', proxyCache: null, proxyUpstream: null, }; trace.passes.set(pass, state); } else if (source) { state.source = source; } return state; } /** Build a structured-clone-safe detail object for User Timing entries. */ function trafficTimingDetail(state, segment, extra = {}) { return { trafficTiming: true, segment, traceId: state?.trace?.id ?? null, interactionId: state?.trace?.interactionId ?? null, cameraChangeTimestamp: state?.trace?.cameraChangeTimestamp ?? null, generation: state?.trace?.generation ?? null, cacheKey: state?.trace?.cacheKey ?? null, pass: state?.pass || 'load', source: state?.source || 'unknown', proxyCache: state?.proxyCache || null, proxyUpstream: state?.proxyUpstream || null, ...extra, }; } /** Add a uniquely named User Timing mark and return its name. */ function trafficTimingMark(state, phase, extra = {}, startTime) { const traceId = state?.trace?.id ?? 'interaction'; const pass = state?.pass || 'load'; const name = `traffic:${traceId}:${pass}:${phase}:${++layerState._trafficTimingSequence}`; const options = { detail: trafficTimingDetail(state, phase, extra) }; if (Number.isFinite(startTime)) options.startTime = startTime; performance.mark(name, options); return name; } /** Emit a named User Timing measure between two marks. */ function trafficTimingMeasure(segment, state, start, end, extra = {}) { performance.measure(`traffic:${segment}:${state?.pass || 'load'}`, { start, end, detail: trafficTimingDetail(state, segment, extra), }); } /** Emit an aggregate-duration measure without pretending its work was contiguous. */ function trafficTimingAggregate( segment, state, anchorTime, duration, extra = {}, ) { const start = trafficTimingMark( state, `${segment}-aggregate-start`, extra, anchorTime, ); const end = trafficTimingMark( state, `${segment}-aggregate-end`, extra, anchorTime + duration, ); trafficTimingMeasure(segment, state, start, end, { aggregate: true, ...extra, }); } /** Clear only this module's stale User Timing entries before a new debug run. */ function clearTrafficTimingEntries() { const markNames = new Set( performance .getEntriesByType('mark') .filter((entry) => entry.name.startsWith('traffic:')) .map((entry) => entry.name), ); const measureNames = new Set( performance .getEntriesByType('measure') .filter((entry) => entry.name.startsWith('traffic:')) .map((entry) => entry.name), ); for (const name of markNames) performance.clearMarks(name); for (const name of measureNames) performance.clearMeasures(name); } /** Return the stable User Timing mark name for a scheduling interaction. */ function trafficTimingCameraChangeMarkName(interactionId) { return `traffic:interaction:${interactionId}:last-camera-change`; } /** Mint and mark the exact camera-change interaction that armed a debounced load. */ function markTrafficTimingCameraChange() { const interactionId = ++layerState._trafficTimingSequence; const timestamp = performance.now(); const anchor = { interactionId, timestamp }; layerState._trafficTimingCurrentAnchor = anchor; performance.mark(trafficTimingCameraChangeMarkName(interactionId), { startTime: timestamp, detail: { trafficTiming: true, segment: 'last-camera-change', interactionId, timestamp, }, }); return anchor; } /** * Mark Cesium's diagnostic moveEnd notification. Cesium normally emits this * about 500 ms after stillness, so fetch has typically already started and * never waits for this boundary. */ function markTrafficTimingMoveEnd() { const diagnosticId = ++layerState._trafficTimingSequence; const timestamp = performance.now(); const name = `traffic:diagnostic:${diagnosticId}:camera-move-end`; performance.mark(name, { startTime: timestamp, detail: { trafficTiming: true, segment: 'camera-move-end', diagnosticOnly: true, cameraEventWaitTimeMs: 500, fetchWaitsForMoveEnd: false, timestamp, }, }); } /** * Instrumented twin of `parseRoads`. Operation ordering and road output match * the normal function; debug-only clocks accumulate synchronous height and * waypoint-materialization time independently. */ function parseRoadsTimed(overpassData, trace) { /* TRACE_ONLY_BEGIN */ const _trafficTimingState = trafficTimingPass( trace, trace?.currentPass || 'full', ); const _trafficTimingParseStartTime = performance.now(); const _trafficTimingParseStart = trafficTimingMark( _trafficTimingState, 'road-parse-start', {}, _trafficTimingParseStartTime, ); /* TRACE_ONLY_END */ if (!overpassData || !overpassData.elements) { /* TRACE_ONLY_BEGIN */ const _trafficTimingParseEnd = trafficTimingMark( _trafficTimingState, 'road-parse-end', { roadCount: 0 }, ); trafficTimingMeasure( 'road-parse-total', _trafficTimingState, _trafficTimingParseStart, _trafficTimingParseEnd, { roadCount: 0 }, ); trafficTimingAggregate( 'sample-height-total', _trafficTimingState, _trafficTimingParseStartTime, 0, { sampleHeightCalls: 0, sampleHeightMeanMs: 0, distinctCells: 0, roadCount: 0, }, ); trafficTimingAggregate( 'waypoint-materialization', _trafficTimingState, _trafficTimingParseStartTime, 0, { roadCount: 0 }, ); /* TRACE_ONLY_END */ return []; } const roads = []; /* TRACE_ONLY_BEGIN */ const _trafficTimingSampledCells = new Set(); let _trafficTimingSampleHeightCalls = 0; let _trafficTimingSampleHeightMs = 0; let _trafficTimingWaypointMaterializationMs = 0; /* TRACE_ONLY_END */ for (const el of overpassData.elements) { if (el.type !== 'way' || !el.geometry || el.geometry.length < 2) continue; const rawCoords = el.geometry.map((g) => [g.lon, g.lat]); const simplifyStep = rawCoords.length > MAX_WAYPOINTS_PER_ROAD ? Math.ceil(rawCoords.length / MAX_WAYPOINTS_PER_ROAD) : 1; const coords = []; for (let i = 0; i < rawCoords.length; i += simplifyStep) { coords.push(rawCoords[i]); } const last = rawCoords[rawCoords.length - 1]; const tail = coords[coords.length - 1]; if (!tail || tail[0] !== last[0] || tail[1] !== last[1]) { coords.push(last); } if (coords.length < 2) continue; const type = el.tags?.highway || 'unclassified'; const onewayTag = el.tags?.oneway; const oneway = onewayTag === 'yes' || onewayTag === '1' || onewayTag === 'true' || el.tags?.junction === 'roundabout' ? 1 : onewayTag === '-1' ? -1 : 0; let baseHeight = 0; const firstCoord = coords[0]; if (layerState._viewer?.scene?.sampleHeightSupported && firstCoord) { /* TRACE_ONLY_BEGIN */ _trafficTimingSampleHeightCalls += 1; _trafficTimingSampledCells.add( `${firstCoord[1].toFixed(3)},${firstCoord[0].toFixed(3)}`, ); /* TRACE_ONLY_END */ const carto = Cesium.Cartographic.fromDegrees( firstCoord[0], firstCoord[1], ); /* TRACE_ONLY_BEGIN */ const _trafficTimingSampleStart = performance.now(); /* TRACE_ONLY_END */ const sampled = layerState._viewer.scene.sampleHeight(carto); /* TRACE_ONLY_BEGIN */ _trafficTimingSampleHeightMs += performance.now() - _trafficTimingSampleStart; /* TRACE_ONLY_END */ if (Number.isFinite(sampled)) baseHeight = sampled; } /* TRACE_ONLY_BEGIN */ const _trafficTimingMaterializeStart = performance.now(); /* TRACE_ONLY_END */ const waypoints = coords.map(([lng, lat]) => { const h = baseHeight + DOT_HEIGHT_OFFSET; return Cesium.Cartesian3.fromDegrees(lng, lat, h); }); const segmentDist = []; for (let i = 0; i < waypoints.length - 1; i++) { segmentDist.push( Cesium.Cartesian3.distance(waypoints[i], waypoints[i + 1]), ); } /* TRACE_ONLY_BEGIN */ _trafficTimingWaypointMaterializationMs += performance.now() - _trafficTimingMaterializeStart; /* TRACE_ONLY_END */ roads.push({ coords, type, oneway, waypoints, segmentDist }); } /* TRACE_ONLY_BEGIN */ const _trafficTimingMetrics = { roadCount: roads.length, sampleHeightCalls: _trafficTimingSampleHeightCalls, sampleHeightMeanMs: _trafficTimingSampleHeightCalls ? _trafficTimingSampleHeightMs / _trafficTimingSampleHeightCalls : 0, distinctCells: _trafficTimingSampledCells.size, }; trafficTimingAggregate( 'sample-height-total', _trafficTimingState, _trafficTimingParseStartTime, _trafficTimingSampleHeightMs, _trafficTimingMetrics, ); trafficTimingAggregate( 'waypoint-materialization', _trafficTimingState, _trafficTimingParseStartTime, _trafficTimingWaypointMaterializationMs, _trafficTimingMetrics, ); const _trafficTimingParseEnd = trafficTimingMark( _trafficTimingState, 'road-parse-end', _trafficTimingMetrics, ); trafficTimingMeasure( 'road-parse-total', _trafficTimingState, _trafficTimingParseStart, _trafficTimingParseEnd, _trafficTimingMetrics, ); /* TRACE_ONLY_END */ return roads; } /** Resolve a render label into its correlated pass and data source. */ function trafficTimingRenderState(trace, label) { const pass = label.toLowerCase().includes('major') ? 'major' : 'full'; const source = label.startsWith('Cache') ? 'client-cache' : 'proxy'; return trafficTimingPass(trace, pass, source); } /** Record the first Cesium postRender following a completed dot render. */ function scheduleTrafficTimingPostRender( state, renderEnd, renderId, renderMetrics, ) { if (!layerState._viewer?.scene) return; let remove = null; remove = layerState._viewer.scene.postRender.addEventListener(() => { remove?.(); layerState._trafficTimingPostRenderRemovers?.delete(remove); const visibleTime = performance.now(); const postRender = trafficTimingMark( state, 'next-post-render', { renderId, ...renderMetrics, }, visibleTime, ); const firstVisible = trafficTimingMark( state, 'first-visible-pixel', { renderId, visibleBoundary: 'next-postRender', ...renderMetrics, }, visibleTime, ); trafficTimingMeasure( 'render-to-post-render', state, renderEnd, postRender, { renderId, visibleBoundary: 'next-postRender', ...renderMetrics, }, ); trafficTimingMeasure( 'last-camera-change-to-first-visible', state, state.trace.cameraChangeMark, firstVisible, { renderId, visibleBoundary: 'next-postRender', ...renderMetrics }, ); }); layerState._trafficTimingPostRenderRemovers?.add(remove); } /** Start a scheduling-correlated debug trace, or count and drop an unpaired load. */ async function loadRoadsForBoundsTimed(bounds, altitude, expectedAnchor) { const generation = layerState._loadGeneration + 1; if ( !expectedAnchor || expectedAnchor !== layerState._trafficTimingCurrentAnchor ) { layerState._trafficTimingDroppedTraces += 1; await parts.ingestion.loadRoadsForBounds(bounds, altitude); return; } layerState._trafficTimingCurrentAnchor = null; const clamped = parts.viewport.clampBounds(bounds); const trace = { id: ++layerState._trafficTimingSequence, interactionId: expectedAnchor.interactionId, cameraChangeTimestamp: expectedAnchor.timestamp, cameraChangeMark: trafficTimingCameraChangeMarkName( expectedAnchor.interactionId, ), generation, cacheKey: `${clamped.south.toFixed(4)},${clamped.west.toFixed(4)},${clamped.north.toFixed(4)},${clamped.east.toFixed(4)}`, currentPass: null, renderSequence: 0, passes: new Map(), }; layerState._trafficTimingTracesCreated += 1; await parts.ingestion.loadRoadsForBounds(bounds, altitude, trace); } return { getTrafficTimingDiagnostics, trafficTimingPass, trafficTimingDetail, trafficTimingMark, trafficTimingMeasure, trafficTimingAggregate, clearTrafficTimingEntries, trafficTimingCameraChangeMarkName, markTrafficTimingCameraChange, markTrafficTimingMoveEnd, parseRoadsTimed, trafficTimingRenderState, scheduleTrafficTimingPostRender, loadRoadsForBoundsTimed, }; }