use std::sync::{Arc, RwLock, mpsc};
use std::time::{Duration, Instant};
use audiorouter_core::monitor::{ConfigFileWatcher, DevicePoller};
use crate::audio::{AudioEngine, EngineState};
use crate::meter::MeterBank;
use crate::{RuntimeSnapshot, devices::ResolvedAudioDevices, validate::ValidatedConfig};
pub struct EngineView {
pub plan: Arc<ValidatedConfig>,
pub resolved: Arc<ResolvedAudioDevices>,
pub meter_bank: Arc<MeterBank>,
pub snapshot: RuntimeSnapshot,
}
pub enum EngineCmd {
Reload,
Stop,
ResetPeaks,
}
pub struct EngineHandle {
pub shared: Arc<RwLock<EngineView>>,
pub cmd_tx: mpsc::SyncSender<EngineCmd>,
pub log_rx: mpsc::Receiver<String>,
}
pub fn spawn_engine_actor(engine: AudioEngine) -> (EngineHandle, std::thread::JoinHandle<()>) {
let (cmd_tx, cmd_rx) = mpsc::sync_channel::<EngineCmd>(16);
let (log_tx, log_rx) = mpsc::channel::<String>();
let initial_snapshot = engine.runtime_snapshot();
let shared = Arc::new(RwLock::new(EngineView {
plan: Arc::new(engine.plan().clone()),
resolved: Arc::new(engine.resolved().clone()),
meter_bank: engine.meter_bank().clone(),
snapshot: initial_snapshot,
}));
let shared_for_thread = shared.clone();
let thread = std::thread::spawn(move || {
engine_loop(engine, cmd_rx, log_tx, shared_for_thread);
});
(
EngineHandle {
shared,
cmd_tx,
log_rx,
},
thread,
)
}
fn engine_loop(
mut engine: AudioEngine,
cmd_rx: mpsc::Receiver<EngineCmd>,
log_tx: mpsc::Sender<String>,
shared: Arc<RwLock<EngineView>>,
) {
let config_watcher = ConfigFileWatcher::new(engine.config_path());
let mut device_poller = DevicePoller::new(Duration::from_secs(1));
let mut reload_deadline: Option<Instant> = None;
loop {
loop {
match cmd_rx.try_recv() {
Ok(EngineCmd::Stop) => {
engine.stop();
update_shared(&engine, &shared);
return;
}
Ok(EngineCmd::Reload) => {
if reload_deadline.is_none() {
reload_deadline = Some(Instant::now() + Duration::from_millis(100));
}
}
Ok(EngineCmd::ResetPeaks) => {
engine.meter_bank().reset_all_peaks();
}
Err(mpsc::TryRecvError::Empty) => break,
Err(mpsc::TryRecvError::Disconnected) => {
engine.stop();
return;
}
}
}
if config_watcher.poll() {
let _ = log_tx.send("config file changed on disk".to_string());
if reload_deadline.is_none() {
reload_deadline = Some(Instant::now() + Duration::from_millis(500));
}
}
if reload_deadline.is_some_and(|d| Instant::now() >= d) {
reload_deadline = None;
match engine.reload() {
Ok(()) => {
let _ = log_tx.send("config reloaded".to_string());
}
Err(e) => {
let _ = log_tx.send(format!("reload error: {}", e.message));
}
}
update_shared(&engine, &shared);
}
if let Some(events) = device_poller.poll() {
for event in &events {
let _ = log_tx.send(event.clone());
}
match engine.refresh_devices() {
Ok(_) => {}
Err(e) => {
let _ = log_tx.send(format!("device refresh error: {}", e.message));
}
}
update_shared(&engine, &shared);
}
match engine.state() {
EngineState::Running => {}
EngineState::FatalError | EngineState::Stopped => {
update_shared(&engine, &shared);
return;
}
}
std::thread::sleep(Duration::from_millis(50));
}
}
fn update_shared(engine: &AudioEngine, shared: &Arc<RwLock<EngineView>>) {
let snapshot = engine.runtime_snapshot();
if let Ok(mut view) = shared.write() {
view.plan = Arc::new(engine.plan().clone());
view.resolved = Arc::new(engine.resolved().clone());
view.meter_bank = engine.meter_bank().clone();
view.snapshot = snapshot;
}
}