[fix](trx-frontend-http): make setRigFrequency fire-and-forget
The HTTP round-trip for set_freq blocks on the server processing the command (mpsc → TCP → rig hardware → response). With optimistic local updates, CSS overlay, and SSE snap-back guard already in place, there is no reason to await the network call. All callers (jog, freq input, spectrum click, RDS AF tune) now return immediately after the optimistic update. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -1943,32 +1943,32 @@ async function ensureTunedBandwidthCoverage(freqHz, bandwidthHz = coverageGuardB
|
|||||||
let _freqOptimisticHz = null;
|
let _freqOptimisticHz = null;
|
||||||
let _freqOptimisticSeq = 0;
|
let _freqOptimisticSeq = 0;
|
||||||
|
|
||||||
async function setRigFrequency(freqHz) {
|
function setRigFrequency(freqHz) {
|
||||||
const targetHz = Math.round(freqHz);
|
const targetHz = Math.round(freqHz);
|
||||||
if (!freqAllowed(targetHz)) {
|
if (!freqAllowed(targetHz)) {
|
||||||
showUnsupportedFreqPopup(targetHz);
|
showUnsupportedFreqPopup(targetHz);
|
||||||
throw new Error(`Unsupported frequency: ${targetHz}`);
|
throw new Error(`Unsupported frequency: ${targetHz}`);
|
||||||
}
|
}
|
||||||
// Optimistic local update before any network round-trip.
|
// Optimistic local update — visual is instant via CSS overlay + guard.
|
||||||
const prevFreqHz = lastFreqHz;
|
const prevFreqHz = lastFreqHz;
|
||||||
const seq = ++_freqOptimisticSeq;
|
const seq = ++_freqOptimisticSeq;
|
||||||
_freqOptimisticHz = targetHz;
|
_freqOptimisticHz = targetHz;
|
||||||
applyLocalTunedFrequency(targetHz);
|
applyLocalTunedFrequency(targetHz);
|
||||||
try {
|
// Fire-and-forget: network calls run in background. The SSE stream will
|
||||||
// set_freq and set_center_freq are independent server operations — run in parallel.
|
// push the confirmed frequency; the optimistic guard prevents snap-back.
|
||||||
await Promise.all([
|
Promise.all([
|
||||||
postPath(`/set_freq?hz=${targetHz}`),
|
postPath(`/set_freq?hz=${targetHz}`),
|
||||||
ensureTunedBandwidthCoverage(targetHz),
|
ensureTunedBandwidthCoverage(targetHz),
|
||||||
]);
|
]).catch((err) => {
|
||||||
} catch (err) {
|
// Roll back only if no newer optimistic call has superseded this one.
|
||||||
// Roll back the optimistic update so the spectrum view stays on the
|
if (_freqOptimisticSeq === seq && prevFreqHz != null) {
|
||||||
// actual server-side frequency (e.g. when the user loses control access).
|
_freqOptimisticHz = null;
|
||||||
if (prevFreqHz != null) applyLocalTunedFrequency(prevFreqHz, true);
|
applyLocalTunedFrequency(prevFreqHz, true);
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
// Only clear the guard if no newer optimistic call has superseded us.
|
|
||||||
if (_freqOptimisticSeq === seq) _freqOptimisticHz = null;
|
|
||||||
}
|
}
|
||||||
|
console.warn("setRigFrequency failed:", err);
|
||||||
|
}).finally(() => {
|
||||||
|
if (_freqOptimisticSeq === seq) _freqOptimisticHz = null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function spectrumBinIndexForHz(data, hz) {
|
function spectrumBinIndexForHz(data, hz) {
|
||||||
@@ -3509,7 +3509,7 @@ pttBtn.addEventListener("click", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function applyFreqFromInput() {
|
function applyFreqFromInput() {
|
||||||
const parsedRaw = parseFreqInput(freqEl.value, jogUnit);
|
const parsedRaw = parseFreqInput(freqEl.value, jogUnit);
|
||||||
const parsed = alignFreqToRigStep(parsedRaw);
|
const parsed = alignFreqToRigStep(parsedRaw);
|
||||||
if (parsed === null) {
|
if (parsed === null) {
|
||||||
@@ -3521,17 +3521,8 @@ async function applyFreqFromInput() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
freqDirty = false;
|
freqDirty = false;
|
||||||
freqEl.disabled = true;
|
// setRigFrequency is fire-and-forget; visual update is instant.
|
||||||
showHint("Setting frequency…");
|
setRigFrequency(parsed);
|
||||||
try {
|
|
||||||
await setRigFrequency(parsed);
|
|
||||||
showHint("Freq set", 1500);
|
|
||||||
} catch (err) {
|
|
||||||
showHint("Set freq failed", 2000);
|
|
||||||
console.error(err);
|
|
||||||
} finally {
|
|
||||||
freqEl.disabled = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function applyCenterFreqFromInput() {
|
async function applyCenterFreqFromInput() {
|
||||||
@@ -3616,7 +3607,7 @@ function setJogDivisor(divisor) {
|
|||||||
applyJogStep();
|
applyJogStep();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function jogFreq(direction) {
|
function jogFreq(direction) {
|
||||||
if (lastLocked) { showHint("Locked", 1500); return; }
|
if (lastLocked) { showHint("Locked", 1500); return; }
|
||||||
if (lastFreqHz === null) return;
|
if (lastFreqHz === null) return;
|
||||||
const newHz = alignFreqToRigStep(lastFreqHz + direction * jogStep);
|
const newHz = alignFreqToRigStep(lastFreqHz + direction * jogStep);
|
||||||
@@ -3626,22 +3617,8 @@ async function jogFreq(direction) {
|
|||||||
}
|
}
|
||||||
jogAngle = (jogAngle + direction * 15) % 360;
|
jogAngle = (jogAngle + direction * 15) % 360;
|
||||||
jogIndicator.style.transform = `translateX(-50%) rotate(${jogAngle}deg)`;
|
jogIndicator.style.transform = `translateX(-50%) rotate(${jogAngle}deg)`;
|
||||||
showHint("Setting frequency…");
|
// setRigFrequency is fire-and-forget; visual update is instant.
|
||||||
// Optimistic local state update — visible to the user before the network call.
|
setRigFrequency(newHz);
|
||||||
// Set the guard BEFORE yielding so SSE cannot snap back during the yield.
|
|
||||||
++_freqOptimisticSeq;
|
|
||||||
_freqOptimisticHz = newHz;
|
|
||||||
applyLocalTunedFrequency(newHz);
|
|
||||||
// Yield so the browser paints the updated freq display before the network
|
|
||||||
// round-trip begins. Chrome's INP tracking ends at the yield point.
|
|
||||||
await yieldToMain();
|
|
||||||
try {
|
|
||||||
await setRigFrequency(newHz);
|
|
||||||
showHint("Freq set", 1000);
|
|
||||||
} catch (err) {
|
|
||||||
showHint("Set freq failed", 2000);
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jogDownBtn.addEventListener("click", () => jogFreq(-1));
|
jogDownBtn.addEventListener("click", () => jogFreq(-1));
|
||||||
@@ -8837,15 +8814,11 @@ function formatRdsAfMHz(hz) {
|
|||||||
return `${(hz / 1_000_000).toFixed(1)} MHz`;
|
return `${(hz / 1_000_000).toFixed(1)} MHz`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function tuneRdsAlternativeFrequency(hz) {
|
function tuneRdsAlternativeFrequency(hz) {
|
||||||
if (!Number.isFinite(hz) || hz <= 0) return;
|
if (!Number.isFinite(hz) || hz <= 0) return;
|
||||||
const targetHz = Math.round(hz);
|
const targetHz = Math.round(hz);
|
||||||
try {
|
setRigFrequency(targetHz);
|
||||||
await setRigFrequency(targetHz);
|
|
||||||
showHint(`Tuned ${formatRdsAfMHz(targetHz)}`, 1200);
|
showHint(`Tuned ${formatRdsAfMHz(targetHz)}`, 1200);
|
||||||
} catch (_) {
|
|
||||||
showHint("Set freq failed", 1500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderRdsAlternativeFrequencies(list) {
|
function renderRdsAlternativeFrequencies(list) {
|
||||||
@@ -9688,7 +9661,7 @@ function handleSpectrumClick(e, canvasEl) {
|
|||||||
const cssX = e.clientX - rect.left;
|
const cssX = e.clientX - rect.left;
|
||||||
const targetHz = spectrumTargetHzAt(cssX, rect.width, lastSpectrumData);
|
const targetHz = spectrumTargetHzAt(cssX, rect.width, lastSpectrumData);
|
||||||
if (!Number.isFinite(targetHz)) return;
|
if (!Number.isFinite(targetHz)) return;
|
||||||
setRigFrequency(targetHz).catch(() => {});
|
setRigFrequency(targetHz);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spectrumCanvas) {
|
if (spectrumCanvas) {
|
||||||
|
|||||||
Reference in New Issue
Block a user