[refactor](trx-rs): inject runtime contexts for io paths

Phase 3: replace frontend/backend hot-path globals with explicit runtime/registration context wiring while keeping plugin compatibility adapters.

Co-authored-by: Codex <codex@openai.com>,
Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-02-12 21:18:42 +01:00
parent 410fc89185
commit b7fb9adef7
12 changed files with 220 additions and 236 deletions
+18 -15
View File
@@ -7,7 +7,6 @@ mod config;
mod decode;
mod error;
mod listener;
mod plugins;
mod rig_task;
use std::collections::HashSet;
@@ -23,10 +22,9 @@ use tracing::{error, info};
use trx_core::audio::AudioStreamInfo;
use trx_app::normalize_name;
use trx_app::{init_logging, load_plugins, normalize_name};
use trx_backend::{
is_backend_registered, register_builtin_backends, register_builtin_backends_on,
registered_backends, RegistrationContext, RigAccess,
register_builtin_backends_on, snapshot_bootstrap_context, RegistrationContext, RigAccess,
};
use trx_core::rig::controller::{AdaptivePolling, ExponentialBackoff};
use trx_core::rig::request::RigRequest;
@@ -107,7 +105,11 @@ struct ResolvedConfig {
longitude: Option<f64>,
}
fn resolve_config(cli: &Cli, cfg: &ServerConfig) -> DynResult<ResolvedConfig> {
fn resolve_config(
cli: &Cli,
cfg: &ServerConfig,
registry: &RegistrationContext,
) -> DynResult<ResolvedConfig> {
let rig_str = cli.rig.clone().or_else(|| cfg.rig.model.clone());
let rig = match rig_str.as_deref() {
Some(name) => normalize_name(name),
@@ -117,11 +119,11 @@ fn resolve_config(cli: &Cli, cfg: &ServerConfig) -> DynResult<ResolvedConfig> {
)
}
};
if !is_backend_registered(&rig) {
if !registry.is_backend_registered(&rig) {
return Err(format!(
"Unknown rig model: {} (available: {})",
rig,
registered_backends().join(", ")
registry.registered_backends().join(", ")
)
.into());
}
@@ -186,8 +188,10 @@ fn resolve_config(cli: &Cli, cfg: &ServerConfig) -> DynResult<ResolvedConfig> {
fn build_rig_task_config(
resolved: &ResolvedConfig,
cfg: &ServerConfig,
registry: std::sync::Arc<RegistrationContext>,
) -> rig_task::RigTaskConfig {
rig_task::RigTaskConfig {
registry,
rig_model: resolved.rig.clone(),
access: resolved.access.clone(),
polling: AdaptivePolling::new(
@@ -210,18 +214,12 @@ fn build_rig_task_config(
#[tokio::main]
async fn main() -> DynResult<()> {
tracing_subscriber::fmt().with_target(false).init();
// Phase 3B: Create bootstrap context for explicit initialization.
// This replaces reliance on global mutable state, though currently
// built-in backends still register on globals for plugin compatibility.
// Full de-globalization would require threading context through rig_task and listener.
let mut bootstrap_ctx = RegistrationContext::new();
register_builtin_backends_on(&mut bootstrap_ctx);
info!("Bootstrap context initialized with {} backends", bootstrap_ctx.registered_backends().len());
register_builtin_backends();
let _plugin_libs = plugins::load_plugins();
let cli = Cli::parse();
@@ -237,11 +235,16 @@ async fn main() -> DynResult<()> {
ServerConfig::load_from_default_paths()?
};
init_logging(cfg.general.log_level.as_deref());
let _plugin_libs = load_plugins();
bootstrap_ctx.extend_from(&snapshot_bootstrap_context());
if let Some(ref path) = config_path {
info!("Loaded configuration from {}", path.display());
}
let resolved = resolve_config(&cli, &cfg)?;
let resolved = resolve_config(&cli, &cfg, &bootstrap_ctx)?;
match &resolved.access {
RigAccess::Serial { path, baud } => {
@@ -275,7 +278,7 @@ async fn main() -> DynResult<()> {
// Keep receivers alive so channels don't close prematurely
let _state_rx = state_rx;
let rig_task_config = build_rig_task_config(&resolved, &cfg);
let rig_task_config = build_rig_task_config(&resolved, &cfg, std::sync::Arc::new(bootstrap_ctx));
let _rig_handle = tokio::spawn(rig_task::run_rig_task(rig_task_config, rx, state_tx));
if cfg.listen.enabled {
+7 -2
View File
@@ -5,12 +5,13 @@
//! Rig task implementation using controller components.
use std::time::Duration;
use std::sync::Arc;
use tokio::sync::{mpsc, watch};
use tokio::time::{self, Instant};
use tracing::{debug, error, info, warn};
use trx_backend::{build_rig, RigAccess};
use trx_backend::{RegistrationContext, RigAccess};
use trx_core::radio::freq::Freq;
use trx_core::rig::command::RigCommand;
use trx_core::rig::controller::{
@@ -28,6 +29,7 @@ use crate::error::is_invalid_bcd_error;
/// Configuration for the rig task.
pub struct RigTaskConfig {
pub registry: Arc<RegistrationContext>,
pub rig_model: String,
pub access: RigAccess,
pub polling: AdaptivePolling,
@@ -42,7 +44,10 @@ pub struct RigTaskConfig {
impl Default for RigTaskConfig {
fn default() -> Self {
let mut registry = RegistrationContext::new();
trx_backend::register_builtin_backends_on(&mut registry);
Self {
registry: Arc::new(registry),
rig_model: "ft817".to_string(),
access: RigAccess::Serial {
path: "/dev/ttyUSB0".to_string(),
@@ -83,7 +88,7 @@ pub async fn run_rig_task(
RigAccess::Tcp { addr } => info!("TCP CAT: {}", addr),
}
let mut rig: Box<dyn RigCat> = build_rig(&config.rig_model, config.access)?;
let mut rig: Box<dyn RigCat> = config.registry.build_rig(&config.rig_model, config.access)?;
info!("Rig backend ready");
// Initialize state machine and state
+16
View File
@@ -25,6 +25,7 @@ pub enum RigAccess {
pub type BackendFactory = fn(RigAccess) -> DynResult<Box<dyn RigCat>>;
/// Context for registering and instantiating rig backends.
#[derive(Clone)]
pub struct RegistrationContext {
factories: HashMap<String, BackendFactory>,
}
@@ -65,6 +66,13 @@ impl RegistrationContext {
.ok_or_else(|| format!("Unknown rig backend: {}", name))?;
factory(access)
}
/// Merge another registration context into this one.
pub fn extend_from(&mut self, other: &RegistrationContext) {
for (name, factory) in &other.factories {
self.factories.insert(name.clone(), *factory);
}
}
}
impl Default for RegistrationContext {
@@ -86,6 +94,14 @@ fn bootstrap_context() -> &'static Arc<Mutex<RegistrationContext>> {
BOOTSTRAP_CONTEXT.get_or_init(|| Arc::new(Mutex::new(RegistrationContext::new())))
}
/// Snapshot current plugin/bootstrap registrations into an owned context.
pub fn snapshot_bootstrap_context() -> RegistrationContext {
let ctx = bootstrap_context()
.lock()
.expect("backend context mutex poisoned");
ctx.clone()
}
/// Register a backend factory under a stable name (e.g. "ft817").
/// Plugin compatibility: delegates to bootstrap context.
pub fn register_backend(name: &str, factory: BackendFactory) {