[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:
+18
-15
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user