[perf](trx-client): three quick-win optimizations
State deduplication via PartialEq + send_if_modified: Derive PartialEq on the full RigState / RigSnapshot type tree (Freq, Band, RigInfo, RigCapabilities, RigStatus, RigTxStatus, RigRxStatus, RigControl, RigVfo, RigVfoEntry, RigFilterState, RdsData, SpectrumData, RigState, RigSnapshot). Use state_tx.send_if_modified() in refresh_remote_snapshot() so WatchStream only wakes SSE /events subscribers when state actually changed; with a stable rig this eliminates ~1.3 spurious JSON serialisations per second per connected client. Cache-remote-rigs skip on unchanged list: cache_remote_rigs() was rebuilding the Vec and cloning every field on every 750 ms poll. Add a structural check (rig_id, display_name, initialized, audio_port) and return early when nothing has changed — the common steady-state case. RDS JSON pre-serialised at ingestion: SharedSpectrum.replace() now serialises the optional RDS object once and stores it alongside the Arc<SpectrumData> frame. Each /spectrum SSE client's 40 ms tick reads the cached string instead of calling serde_json::to_string() per-client per-tick. Add serde_json to trx-frontend Cargo.toml to support this. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
const SPEED_OF_LIGHT_M_PER_S: f64 = 299_792_458.0;
|
||||
|
||||
/// Supported band range in Hz.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Band {
|
||||
pub low_hz: u64,
|
||||
pub high_hz: u64,
|
||||
@@ -23,7 +23,7 @@ impl Band {
|
||||
}
|
||||
|
||||
/// Frequency wrapper (Hz).
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct Freq {
|
||||
pub hz: u64,
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ pub mod response;
|
||||
pub mod state;
|
||||
|
||||
/// How this backend communicates with the rig.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum RigAccessMethod {
|
||||
Serial { path: String, baud: u32 },
|
||||
Tcp { addr: String },
|
||||
}
|
||||
|
||||
/// Static info describing a rig backend.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigInfo {
|
||||
pub manufacturer: String,
|
||||
pub model: String,
|
||||
@@ -37,7 +37,7 @@ pub struct RigInfo {
|
||||
pub access: RigAccessMethod,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct RigCapabilities {
|
||||
#[serde(default = "default_min_freq_step_hz")]
|
||||
pub min_freq_step_hz: u64,
|
||||
@@ -233,7 +233,7 @@ pub trait RigCat: Rig + Send {
|
||||
}
|
||||
|
||||
/// Snapshot of a rig's status that every backend can expose.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigStatus {
|
||||
pub freq: Freq,
|
||||
pub mode: RigMode,
|
||||
@@ -249,21 +249,21 @@ pub trait RigStatusProvider {
|
||||
fn status(&self) -> RigStatus;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigVfo {
|
||||
pub entries: Vec<RigVfoEntry>,
|
||||
/// Index into `entries` for the active VFO, if known.
|
||||
pub active: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigVfoEntry {
|
||||
pub name: String,
|
||||
pub freq: Freq,
|
||||
pub mode: Option<RigMode>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigTxStatus {
|
||||
pub power: Option<u8>,
|
||||
pub limit: Option<u8>,
|
||||
@@ -271,13 +271,13 @@ pub struct RigTxStatus {
|
||||
pub alc: Option<u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct RigRxStatus {
|
||||
pub sig: Option<i32>,
|
||||
}
|
||||
|
||||
/// Configurable control settings that can be pushed to the rig.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
pub struct RigControl {
|
||||
pub enabled: Option<bool>,
|
||||
pub lock: Option<bool>,
|
||||
|
||||
@@ -8,7 +8,7 @@ use crate::radio::freq::Freq;
|
||||
use crate::rig::{RigControl, RigInfo, RigRxStatus, RigStatus, RigStatusProvider, RigTxStatus};
|
||||
|
||||
/// Simple transceiver state representation held by the rig task.
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq)]
|
||||
pub struct RigState {
|
||||
#[serde(skip_deserializing)]
|
||||
pub rig_info: Option<RigInfo>,
|
||||
@@ -280,7 +280,7 @@ impl RigState {
|
||||
}
|
||||
|
||||
/// Current filter/DSP state for backends that support runtime filter adjustment.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigFilterState {
|
||||
pub bandwidth_hz: u32,
|
||||
pub fir_taps: u32,
|
||||
@@ -324,7 +324,7 @@ fn default_wfm_denoise_level() -> WfmDenoiseLevel {
|
||||
}
|
||||
|
||||
/// Spectrum data from SDR backends (FFT magnitude over the full capture bandwidth).
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct SpectrumData {
|
||||
/// FFT magnitude bins in dBFS, FFT-shifted so DC (centre frequency) is at index N/2.
|
||||
pub bins: Vec<f32>,
|
||||
@@ -338,7 +338,7 @@ pub struct SpectrumData {
|
||||
}
|
||||
|
||||
/// Live RDS metadata decoded from a WFM broadcast.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RdsData {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub pi: Option<u16>,
|
||||
@@ -371,7 +371,7 @@ pub struct RdsData {
|
||||
}
|
||||
|
||||
/// Read-only projection of state shared with clients.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub struct RigSnapshot {
|
||||
pub info: RigInfo,
|
||||
pub status: RigStatus,
|
||||
|
||||
Reference in New Issue
Block a user