Pause BT Locate processing when mode is hidden

This commit is contained in:
Smittix
2026-02-20 17:48:22 +00:00
parent c3bf30b49c
commit aec925753e

View File

@@ -31,7 +31,7 @@ const BtLocate = (function() {
let movementHeadMarker = null;
let strongestMarker = null;
let confidenceCircle = null;
let heatmapEnabled = true;
let heatmapEnabled = false;
let movementEnabled = true;
let autoFollowEnabled = true;
let smoothingEnabled = true;
@@ -39,6 +39,10 @@ const BtLocate = (function() {
let pendingHeatSync = false;
let mapStabilizeTimer = null;
let modeActive = false;
let queuedDetection = null;
let queuedDetectionOptions = null;
let queuedDetectionTimer = null;
let lastDetectionRenderAt = 0;
const MAX_HEAT_POINTS = 1200;
const MAX_TRAIL_POINTS = 1200;
@@ -46,8 +50,9 @@ const BtLocate = (function() {
const OUTLIER_HARD_JUMP_METERS = 2000;
const OUTLIER_SOFT_JUMP_METERS = 450;
const OUTLIER_MAX_SPEED_MPS = 50;
const MAP_STABILIZE_INTERVAL_MS = 150;
const MAP_STABILIZE_ATTEMPTS = 28;
const MAP_STABILIZE_INTERVAL_MS = 220;
const MAP_STABILIZE_ATTEMPTS = 8;
const MIN_DETECTION_RENDER_MS = 220;
const OVERLAY_STORAGE_KEYS = {
heatmap: 'btLocateHeatmapEnabled',
movement: 'btLocateMovementEnabled',
@@ -94,7 +99,7 @@ const BtLocate = (function() {
// Re-invalidate map on re-entry and ensure tiles are present
if (map) {
setTimeout(() => {
safeInvalidateMap(true);
safeInvalidateMap();
// Re-apply user's tile layer if tiles were lost
let hasTiles = false;
map.eachLayer(layer => {
@@ -144,7 +149,7 @@ const BtLocate = (function() {
flushPendingHeatSync();
});
setTimeout(() => {
safeInvalidateMap(true);
safeInvalidateMap();
flushPendingHeatSync();
}, 100);
scheduleMapStabilization();
@@ -255,15 +260,21 @@ const BtLocate = (function() {
});
}
function stop() {
fetch('/bt_locate/stop', { method: 'POST' })
.then(r => r.json())
.then(() => {
showIdleUI();
disconnectSSE();
stopAudio();
})
.catch(err => console.error('[BtLocate] Stop error:', err));
function stop() {
fetch('/bt_locate/stop', { method: 'POST' })
.then(r => r.json())
.then(() => {
if (queuedDetectionTimer) {
clearTimeout(queuedDetectionTimer);
queuedDetectionTimer = null;
}
queuedDetection = null;
queuedDetectionOptions = null;
showIdleUI();
disconnectSSE();
stopAudio();
})
.catch(err => console.error('[BtLocate] Stop error:', err));
}
function showActiveUI() {
@@ -274,12 +285,18 @@ const BtLocate = (function() {
show('btLocateHud');
}
function showIdleUI() {
const startBtn = document.getElementById('btLocateStartBtn');
const stopBtn = document.getElementById('btLocateStopBtn');
if (startBtn) startBtn.style.display = 'inline-block';
if (stopBtn) stopBtn.style.display = 'none';
hide('btLocateHud');
function showIdleUI() {
if (queuedDetectionTimer) {
clearTimeout(queuedDetectionTimer);
queuedDetectionTimer = null;
}
queuedDetection = null;
queuedDetectionOptions = null;
const startBtn = document.getElementById('btLocateStartBtn');
const stopBtn = document.getElementById('btLocateStopBtn');
if (startBtn) startBtn.style.display = 'inline-block';
if (stopBtn) stopBtn.style.display = 'none';
hide('btLocateHud');
hide('btLocateScanStatus');
}
@@ -475,7 +492,42 @@ const BtLocate = (function() {
}
}
function flushQueuedDetection() {
if (!queuedDetection) return;
const event = queuedDetection;
const options = queuedDetectionOptions || {};
queuedDetection = null;
queuedDetectionOptions = null;
queuedDetectionTimer = null;
renderDetection(event, options);
}
function handleDetection(event, options = {}) {
if (!modeActive) {
return;
}
const now = Date.now();
if (options.force || (now - lastDetectionRenderAt) >= MIN_DETECTION_RENDER_MS) {
if (queuedDetectionTimer) {
clearTimeout(queuedDetectionTimer);
queuedDetectionTimer = null;
}
queuedDetection = null;
queuedDetectionOptions = null;
renderDetection(event, options);
return;
}
// Keep only the freshest event while throttled.
queuedDetection = event;
queuedDetectionOptions = options;
if (!queuedDetectionTimer) {
queuedDetectionTimer = setTimeout(flushQueuedDetection, MIN_DETECTION_RENDER_MS);
}
}
function renderDetection(event, options = {}) {
lastDetectionRenderAt = Date.now();
const d = event?.data || event;
if (!d) return;
const detectionKey = buildDetectionKey(d);
@@ -906,7 +958,7 @@ const BtLocate = (function() {
}
function ensureHeatLayer() {
if (!map || typeof L === 'undefined' || typeof L.heatLayer !== 'function') return;
if (!map || !heatmapEnabled || typeof L === 'undefined' || typeof L.heatLayer !== 'function') return;
if (!heatLayer) {
heatLayer = L.heatLayer([], HEAT_LAYER_OPTIONS);
}
@@ -914,6 +966,13 @@ const BtLocate = (function() {
function syncHeatLayer() {
if (!map) return;
if (!heatmapEnabled) {
if (heatLayer && map.hasLayer(heatLayer)) {
map.removeLayer(heatLayer);
}
pendingHeatSync = false;
return;
}
ensureHeatLayer();
if (!heatLayer) return;
if (!modeActive || !isMapContainerVisible()) {
@@ -930,6 +989,13 @@ const BtLocate = (function() {
return;
}
}
if (!Array.isArray(heatPoints) || heatPoints.length === 0) {
if (map.hasLayer(heatLayer)) {
map.removeLayer(heatLayer);
}
pendingHeatSync = false;
return;
}
try {
heatLayer.setLatLngs(heatPoints);
if (heatmapEnabled) {
@@ -955,6 +1021,14 @@ const BtLocate = (function() {
if (!modeActive) {
stopMapStabilization();
if (queuedDetectionTimer) {
clearTimeout(queuedDetectionTimer);
queuedDetectionTimer = null;
}
queuedDetection = null;
queuedDetectionOptions = null;
// Pause BT Locate frontend work when mode is hidden.
disconnectSSE();
if (heatLayer && map.hasLayer(heatLayer)) {
map.removeLayer(heatLayer);
}
@@ -964,23 +1038,23 @@ const BtLocate = (function() {
setTimeout(() => {
if (!modeActive) return;
safeInvalidateMap(true);
if (typeof window.requestAnimationFrame === 'function') {
window.requestAnimationFrame(() => {
if (!modeActive) return;
safeInvalidateMap(true);
window.requestAnimationFrame(() => {
if (!modeActive) return;
safeInvalidateMap(true);
});
});
}
safeInvalidateMap();
flushPendingHeatSync();
syncHeatLayer();
syncMovementLayer();
syncStrongestMarker();
updateConfidenceLayer();
scheduleMapStabilization(14);
scheduleMapStabilization(8);
checkStatus();
}, 80);
// A second pass after layout settles (sidebar/visual transitions).
setTimeout(() => {
if (!modeActive) return;
safeInvalidateMap();
flushPendingHeatSync();
syncHeatLayer();
}, 260);
}
function isMapRenderable() {
@@ -992,26 +1066,9 @@ const BtLocate = (function() {
return true;
}
function refreshBaseTiles() {
if (!map || typeof L === 'undefined' || typeof map.eachLayer !== 'function') return;
map.eachLayer((layer) => {
if (layer instanceof L.TileLayer && typeof layer.redraw === 'function') {
try {
layer.redraw();
} catch (_) {}
}
});
}
function safeInvalidateMap(forceRecenter = false) {
function safeInvalidateMap() {
if (!map || !isMapContainerVisible()) return false;
map.invalidateSize({ pan: !!forceRecenter, animate: false });
if (forceRecenter) {
const center = map.getCenter();
const zoom = map.getZoom();
map.setView(center, zoom, { animate: false });
}
refreshBaseTiles();
map.invalidateSize({ pan: false, animate: false });
return true;
}
@@ -1032,7 +1089,7 @@ const BtLocate = (function() {
stopMapStabilization();
return;
}
if (safeInvalidateMap(true)) {
if (safeInvalidateMap()) {
flushPendingHeatSync();
syncMovementLayer();
syncStrongestMarker();
@@ -1706,7 +1763,7 @@ const BtLocate = (function() {
}
function invalidateMap() {
if (safeInvalidateMap(true)) {
if (safeInvalidateMap()) {
flushPendingHeatSync();
syncMovementLayer();
syncStrongestMarker();