[fix](trx-frontend-http): skip redundant DOM writes in render() hot path
The render() function runs on every SSE event (5-20×/sec) and was unconditionally writing to decoder toggle buttons and About-tab decoder status elements — 8 getElementById calls + 32 DOM property writes per frame — even when values hadn't changed. This caused unnecessary style recalculation overhead on every SSE frame, contributing to spectrum stuttering. Changes: - Cache all 7 decoder toggle button elements at module init instead of calling getElementById on every render() call - Track last-written enabled state per button; skip DOM writes when the value is unchanged (steady-state cost: 0 DOM writes per frame) - Same pattern for 8 About-tab decoder status elements - Gate updateSatLiveState className/textContent writes on value change Net effect: eliminates ~50 unnecessary DOM operations per SSE frame during normal operation (decoders rarely toggle). https://claude.ai/code/session_01G6wuNCkckbHHsU7w5zCtW2 Signed-off-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -363,6 +363,41 @@ const headerStylePickSelect = document.getElementById("header-style-pick-select"
|
|||||||
const rdsPsOverlay = document.getElementById("rds-ps-overlay");
|
const rdsPsOverlay = document.getElementById("rds-ps-overlay");
|
||||||
let overviewPeakHoldMs = Number(loadSetting("overviewPeakHoldMs", 2000));
|
let overviewPeakHoldMs = Number(loadSetting("overviewPeakHoldMs", 2000));
|
||||||
let decodeHistoryRetentionMin = 24 * 60;
|
let decodeHistoryRetentionMin = 24 * 60;
|
||||||
|
|
||||||
|
// Cached decoder toggle buttons — avoids 8× getElementById per render() call.
|
||||||
|
const _decoderToggles = {
|
||||||
|
ft8: { el: document.getElementById("ft8-decode-toggle-btn"), last: null },
|
||||||
|
ft4: { el: document.getElementById("ft4-decode-toggle-btn"), last: null },
|
||||||
|
ft2: { el: document.getElementById("ft2-decode-toggle-btn"), last: null },
|
||||||
|
wspr: { el: document.getElementById("wspr-decode-toggle-btn"), last: null },
|
||||||
|
hfAprs: { el: document.getElementById("hf-aprs-decode-toggle-btn"), last: null },
|
||||||
|
sat: { el: document.getElementById("sat-decode-toggle-btn"), last: null },
|
||||||
|
lrpt: { el: document.getElementById("lrpt-decode-toggle-btn"), last: null },
|
||||||
|
};
|
||||||
|
|
||||||
|
function syncDecoderToggle(entry, enabled, label) {
|
||||||
|
if (!entry.el || entry.last === enabled) return;
|
||||||
|
entry.last = enabled;
|
||||||
|
entry.el.dataset.enabled = enabled ? "true" : "false";
|
||||||
|
entry.el.textContent = enabled ? `Disable ${label}` : `Enable ${label}`;
|
||||||
|
entry.el.style.borderColor = enabled ? "#00d17f" : "";
|
||||||
|
entry.el.style.color = enabled ? "#00d17f" : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached About-tab decoder status elements — avoids 8× getElementById per render().
|
||||||
|
const _aboutDecEls = [
|
||||||
|
"about-dec-ft8", "about-dec-ft4", "about-dec-ft2", "about-dec-wspr",
|
||||||
|
"about-dec-cw", "about-dec-aprs", "about-dec-sat", "about-dec-lrpt",
|
||||||
|
].map((id) => ({ el: document.getElementById(id), last: null }));
|
||||||
|
|
||||||
|
function syncAboutDecoder(idx, enabled) {
|
||||||
|
const entry = _aboutDecEls[idx];
|
||||||
|
if (!entry || !entry.el || entry.last === enabled) return;
|
||||||
|
entry.last = enabled;
|
||||||
|
entry.el.textContent = enabled ? "Active" : "Off";
|
||||||
|
entry.el.className = enabled ? "about-status-on" : "about-status-off";
|
||||||
|
}
|
||||||
|
|
||||||
let primaryRds = null;
|
let primaryRds = null;
|
||||||
let vchanRdsById = new Map();
|
let vchanRdsById = new Map();
|
||||||
let vchanSignalDbById = new Map();
|
let vchanSignalDbById = new Map();
|
||||||
@@ -3229,62 +3264,14 @@ function render(update) {
|
|||||||
pttBtn.style.color = "";
|
pttBtn.style.color = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const ft8ToggleBtn = document.getElementById("ft8-decode-toggle-btn");
|
// Decoder toggle buttons: only write DOM when the enabled flag actually changes.
|
||||||
if (ft8ToggleBtn) {
|
syncDecoderToggle(_decoderToggles.ft8, !!update.ft8_decode_enabled, "FT8");
|
||||||
const ft8On = !!update.ft8_decode_enabled;
|
syncDecoderToggle(_decoderToggles.ft4, !!update.ft4_decode_enabled, "FT4");
|
||||||
ft8ToggleBtn.dataset.enabled = ft8On ? "true" : "false";
|
syncDecoderToggle(_decoderToggles.ft2, !!update.ft2_decode_enabled, "FT2");
|
||||||
ft8ToggleBtn.textContent = ft8On ? "Disable FT8" : "Enable FT8";
|
syncDecoderToggle(_decoderToggles.wspr, !!update.wspr_decode_enabled, "WSPR");
|
||||||
ft8ToggleBtn.style.borderColor = ft8On ? "#00d17f" : "";
|
syncDecoderToggle(_decoderToggles.hfAprs, !!update.hf_aprs_decode_enabled, "HF APRS");
|
||||||
ft8ToggleBtn.style.color = ft8On ? "#00d17f" : "";
|
syncDecoderToggle(_decoderToggles.sat, !!update.wxsat_decode_enabled, "NOAA APT");
|
||||||
}
|
syncDecoderToggle(_decoderToggles.lrpt, !!update.lrpt_decode_enabled, "Meteor LRPT");
|
||||||
const ft4ToggleBtn = document.getElementById("ft4-decode-toggle-btn");
|
|
||||||
if (ft4ToggleBtn) {
|
|
||||||
const ft4On = !!update.ft4_decode_enabled;
|
|
||||||
ft4ToggleBtn.dataset.enabled = ft4On ? "true" : "false";
|
|
||||||
ft4ToggleBtn.textContent = ft4On ? "Disable FT4" : "Enable FT4";
|
|
||||||
ft4ToggleBtn.style.borderColor = ft4On ? "#00d17f" : "";
|
|
||||||
ft4ToggleBtn.style.color = ft4On ? "#00d17f" : "";
|
|
||||||
}
|
|
||||||
const ft2ToggleBtn = document.getElementById("ft2-decode-toggle-btn");
|
|
||||||
if (ft2ToggleBtn) {
|
|
||||||
const ft2On = !!update.ft2_decode_enabled;
|
|
||||||
ft2ToggleBtn.dataset.enabled = ft2On ? "true" : "false";
|
|
||||||
ft2ToggleBtn.textContent = ft2On ? "Disable FT2" : "Enable FT2";
|
|
||||||
ft2ToggleBtn.style.borderColor = ft2On ? "#00d17f" : "";
|
|
||||||
ft2ToggleBtn.style.color = ft2On ? "#00d17f" : "";
|
|
||||||
}
|
|
||||||
const wsprToggleBtn = document.getElementById("wspr-decode-toggle-btn");
|
|
||||||
if (wsprToggleBtn) {
|
|
||||||
const wsprOn = !!update.wspr_decode_enabled;
|
|
||||||
wsprToggleBtn.dataset.enabled = wsprOn ? "true" : "false";
|
|
||||||
wsprToggleBtn.textContent = wsprOn ? "Disable WSPR" : "Enable WSPR";
|
|
||||||
wsprToggleBtn.style.borderColor = wsprOn ? "#00d17f" : "";
|
|
||||||
wsprToggleBtn.style.color = wsprOn ? "#00d17f" : "";
|
|
||||||
}
|
|
||||||
const hfAprsToggleBtn = document.getElementById("hf-aprs-decode-toggle-btn");
|
|
||||||
if (hfAprsToggleBtn) {
|
|
||||||
const hfAprsOn = !!update.hf_aprs_decode_enabled;
|
|
||||||
hfAprsToggleBtn.dataset.enabled = hfAprsOn ? "true" : "false";
|
|
||||||
hfAprsToggleBtn.textContent = hfAprsOn ? "Disable HF APRS" : "Enable HF APRS";
|
|
||||||
hfAprsToggleBtn.style.borderColor = hfAprsOn ? "#00d17f" : "";
|
|
||||||
hfAprsToggleBtn.style.color = hfAprsOn ? "#00d17f" : "";
|
|
||||||
}
|
|
||||||
const satToggleBtn = document.getElementById("sat-decode-toggle-btn");
|
|
||||||
if (satToggleBtn) {
|
|
||||||
const satOn = !!update.wxsat_decode_enabled;
|
|
||||||
satToggleBtn.dataset.enabled = satOn ? "true" : "false";
|
|
||||||
satToggleBtn.textContent = satOn ? "Disable NOAA APT" : "Enable NOAA APT";
|
|
||||||
satToggleBtn.style.borderColor = satOn ? "#00d17f" : "";
|
|
||||||
satToggleBtn.style.color = satOn ? "#00d17f" : "";
|
|
||||||
}
|
|
||||||
const lrptToggleBtn = document.getElementById("lrpt-decode-toggle-btn");
|
|
||||||
if (lrptToggleBtn) {
|
|
||||||
const lrptOn = !!update.lrpt_decode_enabled;
|
|
||||||
lrptToggleBtn.dataset.enabled = lrptOn ? "true" : "false";
|
|
||||||
lrptToggleBtn.textContent = lrptOn ? "Disable Meteor LRPT" : "Enable Meteor LRPT";
|
|
||||||
lrptToggleBtn.style.borderColor = lrptOn ? "#00d17f" : "";
|
|
||||||
lrptToggleBtn.style.color = lrptOn ? "#00d17f" : "";
|
|
||||||
}
|
|
||||||
if (window.updateSatLiveState) window.updateSatLiveState(update);
|
if (window.updateSatLiveState) window.updateSatLiveState(update);
|
||||||
const cwAutoEl = document.getElementById("cw-auto");
|
const cwAutoEl = document.getElementById("cw-auto");
|
||||||
const cwWpmEl = document.getElementById("cw-wpm");
|
const cwWpmEl = document.getElementById("cw-wpm");
|
||||||
@@ -3453,24 +3440,15 @@ function render(update) {
|
|||||||
document.getElementById("about-audio-streams").textContent = update.audio_clients;
|
document.getElementById("about-audio-streams").textContent = update.audio_clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
// About — Decoders card
|
// About — Decoders card (only update when values change)
|
||||||
const decMap = [
|
syncAboutDecoder(0, !!update.ft8_decode_enabled);
|
||||||
["about-dec-ft8", update.ft8_decode_enabled],
|
syncAboutDecoder(1, !!update.ft4_decode_enabled);
|
||||||
["about-dec-ft4", update.ft4_decode_enabled],
|
syncAboutDecoder(2, !!update.ft2_decode_enabled);
|
||||||
["about-dec-ft2", update.ft2_decode_enabled],
|
syncAboutDecoder(3, !!update.wspr_decode_enabled);
|
||||||
["about-dec-wspr", update.wspr_decode_enabled],
|
syncAboutDecoder(4, !!update.cw_decode_enabled);
|
||||||
["about-dec-cw", update.cw_decode_enabled],
|
syncAboutDecoder(5, !!(update.aprs_decode_enabled || update.hf_aprs_decode_enabled));
|
||||||
["about-dec-aprs", update.aprs_decode_enabled || update.hf_aprs_decode_enabled],
|
syncAboutDecoder(6, !!update.wxsat_decode_enabled);
|
||||||
["about-dec-sat", update.wxsat_decode_enabled],
|
syncAboutDecoder(7, !!update.lrpt_decode_enabled);
|
||||||
["about-dec-lrpt", update.lrpt_decode_enabled],
|
|
||||||
];
|
|
||||||
for (const [id, enabled] of decMap) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (el) {
|
|
||||||
el.textContent = enabled ? "Active" : "Off";
|
|
||||||
el.className = enabled ? "about-status-on" : "about-status-off";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// About — Integrations card
|
// About — Integrations card
|
||||||
if (update.pskreporter_status) {
|
if (update.pskreporter_status) {
|
||||||
|
|||||||
@@ -73,15 +73,21 @@ satViewPredictionsBtn?.addEventListener("click", () => switchSatView("prediction
|
|||||||
|
|
||||||
// ── Live view: decoder state ────────────────────────────────────────
|
// ── Live view: decoder state ────────────────────────────────────────
|
||||||
// Updated from app.js render() via window.updateSatLiveState
|
// Updated from app.js render() via window.updateSatLiveState
|
||||||
|
let _lastSatAptOn = null, _lastSatLrptOn = null;
|
||||||
window.updateSatLiveState = function (update) {
|
window.updateSatLiveState = function (update) {
|
||||||
if (!satAptState || !satLrptState) return;
|
if (!satAptState || !satLrptState) return;
|
||||||
const aptOn = !!update.wxsat_decode_enabled;
|
const aptOn = !!update.wxsat_decode_enabled;
|
||||||
const lrptOn = !!update.lrpt_decode_enabled;
|
const lrptOn = !!update.lrpt_decode_enabled;
|
||||||
|
if (aptOn !== _lastSatAptOn) {
|
||||||
satAptState.textContent = aptOn ? "Listening" : "Idle";
|
_lastSatAptOn = aptOn;
|
||||||
satAptState.className = "sat-live-value " + (aptOn ? "sat-state-listening" : "sat-state-idle");
|
satAptState.textContent = aptOn ? "Listening" : "Idle";
|
||||||
satLrptState.textContent = lrptOn ? "Listening" : "Idle";
|
satAptState.className = "sat-live-value " + (aptOn ? "sat-state-listening" : "sat-state-idle");
|
||||||
satLrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle");
|
}
|
||||||
|
if (lrptOn !== _lastSatLrptOn) {
|
||||||
|
_lastSatLrptOn = lrptOn;
|
||||||
|
satLrptState.textContent = lrptOn ? "Listening" : "Idle";
|
||||||
|
satLrptState.className = "sat-live-value " + (lrptOn ? "sat-state-listening" : "sat-state-idle");
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderSatLatestCard() {
|
function renderSatLatestCard() {
|
||||||
|
|||||||
Reference in New Issue
Block a user