mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Extract military-flight components and shared aircraft mechanics
This commit is contained in:
1 parent
b5e546daf1
commit
cb790ddea1
25 files changed
+4849
-3930
No files matched your search
@@ -0,0 +1,143 @@
|
||||
/** Instance-owned military identity and active-layer classification.
|
||||
* Both aircraft layers share the same application-supplied registry. */
|
||||
export function createMilitaryRegistry({ source, now = Date.now } = {}) {
|
||||
const MIL_POLL_INTERVAL_MS = 60000;
|
||||
|
||||
/** @type {Set<string>} Lowercase ICAO24 hexes known to be military. */
|
||||
const _milIcaos = new Set();
|
||||
/** @type {boolean} True while the dedicated military layer is enabled. */
|
||||
let _militaryLayerActive = false;
|
||||
/** @type {Set<(active: boolean) => void>} Fired on active-state TRANSITIONS. */
|
||||
const _activeChangeListeners = new Set();
|
||||
/** @type {number} Epoch ms of the last registry refresh (any source). */
|
||||
let _lastRefreshMs = 0;
|
||||
/** @type {boolean} A self-poll fetch is in flight. */
|
||||
let _polling = false;
|
||||
let currentSource = null;
|
||||
let lifetime = null;
|
||||
let releaseSource = null;
|
||||
|
||||
/** Replace the classification source and cancel its outstanding work. */
|
||||
function configureSource(nextSource, { signal } = {}) {
|
||||
if (typeof nextSource?.getSnapshot !== 'function') throw new TypeError('A snapshot source is required');
|
||||
releaseSource?.();
|
||||
const controller = new AbortController();
|
||||
lifetime = controller;
|
||||
currentSource = nextSource;
|
||||
_milIcaos.clear();
|
||||
_lastRefreshMs = 0;
|
||||
_polling = false;
|
||||
const release = () => {
|
||||
controller.abort();
|
||||
signal?.removeEventListener('abort', release);
|
||||
if (lifetime !== controller) return;
|
||||
currentSource = null;
|
||||
lifetime = null;
|
||||
releaseSource = null;
|
||||
_milIcaos.clear();
|
||||
_lastRefreshMs = 0;
|
||||
_polling = false;
|
||||
};
|
||||
releaseSource = release;
|
||||
if (signal?.aborted) release();
|
||||
else signal?.addEventListener('abort', release, { once: true });
|
||||
return release;
|
||||
}
|
||||
|
||||
/**
|
||||
* True when the dedicated military layer currently renders these aircraft
|
||||
* (the flights layer should suppress duplicates rather than restyle them).
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isMilitaryLayerActive() {
|
||||
return _militaryLayerActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the dedicated military layer enabled/disabled. On a TRANSITION
|
||||
* (value actually changed) the registered change listeners fire so the
|
||||
* flights layer can reconcile its duplicates immediately instead of waiting
|
||||
* out its 30 s poll (pre-ship audit M2).
|
||||
* @param {boolean} active - Whether the military layer renders.
|
||||
* @returns {void}
|
||||
*/
|
||||
function setMilitaryLayerActive(active) {
|
||||
const next = !!active;
|
||||
if (next === _militaryLayerActive) return;
|
||||
_militaryLayerActive = next;
|
||||
for (const listener of _activeChangeListeners) {
|
||||
try {
|
||||
listener(next);
|
||||
} catch {
|
||||
// a broken listener must never break the layer toggle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribes to military-layer active-state transitions (fired only when the
|
||||
* value changes, AFTER the new state is committed).
|
||||
* @param {(active: boolean) => void} listener - Change callback.
|
||||
* @returns {() => void} Unsubscribe function.
|
||||
*/
|
||||
function onMilitaryLayerActiveChange(listener) {
|
||||
if (typeof listener !== 'function') return () => {};
|
||||
_activeChangeListeners.add(listener);
|
||||
return () => _activeChangeListeners.delete(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces/extends the known-military set from a fresh poll.
|
||||
* Adds only — transient dropouts from one poll must not declassify an
|
||||
* aircraft mid-session (the set stays small: a few hundred hexes).
|
||||
* @param {Iterable<string>} icaos - ICAO24 hexes from a /v2/mil response.
|
||||
* @returns {void}
|
||||
*/
|
||||
function registerMilitaryIcaos(icaos) {
|
||||
for (const icao of icaos || []) {
|
||||
const hex = String(icao || '').trim().toLowerCase();
|
||||
if (hex) _milIcaos.add(hex);
|
||||
}
|
||||
_lastRefreshMs = now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an aircraft is known military.
|
||||
* @param {string} icao24 - ICAO24 hex (any case).
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isMilitaryIcao(icao24) {
|
||||
return _milIcaos.has(String(icao24 || '').toLowerCase());
|
||||
}
|
||||
|
||||
/** Refresh known identities while the dedicated layer is disabled.
|
||||
* A failed or cancelled request retains the current set and never swaps sources. */
|
||||
async function refreshMilitaryRegistryIfStale() {
|
||||
if (!currentSource || !lifetime || lifetime.signal.aborted || _militaryLayerActive) return;
|
||||
if (_polling || now() - _lastRefreshMs < MIL_POLL_INTERVAL_MS) return;
|
||||
const owner = lifetime;
|
||||
const requestSource = currentSource;
|
||||
const signal = AbortSignal.any([owner.signal, AbortSignal.timeout(10000)]);
|
||||
_polling = true;
|
||||
try {
|
||||
const snapshot = await requestSource.getSnapshot({}, { signal });
|
||||
signal.throwIfAborted();
|
||||
if (lifetime !== owner) return;
|
||||
registerMilitaryIcaos(snapshot.records.map((record) => record.id));
|
||||
} catch {
|
||||
// Keep the existing classification through source outages.
|
||||
} finally {
|
||||
if (lifetime === owner) _polling = false;
|
||||
}
|
||||
}
|
||||
function dispose() {
|
||||
releaseSource?.();
|
||||
_milIcaos.clear();
|
||||
_lastRefreshMs = 0;
|
||||
_activeChangeListeners.clear();
|
||||
_militaryLayerActive = false;
|
||||
}
|
||||
if (source) configureSource(source);
|
||||
return { configureSource, dispose, isMilitaryLayerActive, setMilitaryLayerActive,
|
||||
onMilitaryLayerActiveChange, registerMilitaryIcaos, isMilitaryIcao, refreshMilitaryRegistryIfStale };
|
||||
}
|
||||
Reference in new issue
Block a user