use std::{
sync::{
Arc, Mutex,
atomic::{AtomicBool, Ordering},
},
thread,
};
use crate::{
bus::{Bus, BusEvent, BusReceiver},
element::Element,
error::Result,
};
#[derive(Clone)]
pub struct StopReceiver {
flag: Arc<AtomicBool>,
}
impl StopReceiver {
pub fn is_stopped(&self) -> bool {
self.flag.load(Ordering::Acquire)
}
}
pub trait Driver: Element {
fn run(&mut self, stop: &StopReceiver, bus: &Bus) -> Result<()>;
}
pub struct DriverRunner {
driver: Mutex<Option<Box<dyn Driver>>>,
bus: Mutex<Option<Bus>>,
stop_flag: Arc<AtomicBool>,
bus_rx: BusReceiver,
running: AtomicBool,
}
impl DriverRunner {
pub fn new(driver: impl Driver + 'static) -> Arc<Self> {
let (bus, bus_rx) = Bus::new();
Arc::new(DriverRunner {
driver: Mutex::new(Some(Box::new(driver))),
bus: Mutex::new(Some(bus)),
stop_flag: Arc::new(AtomicBool::new(false)),
bus_rx,
running: AtomicBool::new(false),
})
}
pub fn bus(&self) -> &BusReceiver {
&self.bus_rx
}
pub fn run(self: &Arc<Self>) {
let Some(mut driver) = self.driver.lock().unwrap().take() else {
return;
};
let Some(bus) = self.bus.lock().unwrap().take() else {
return;
};
self.running.store(true, Ordering::Release);
let stop = StopReceiver {
flag: self.stop_flag.clone(),
};
let this = Arc::downgrade(self);
thread::Builder::new()
.name("driver".into())
.spawn(move || {
let name = driver.name();
let element_type = driver.element_type();
if let Err(error) = driver.run(&stop, &bus) {
bus.post(
driver.pp_log(),
BusEvent::Error {
element_type,
name,
error,
},
);
}
if let Some(this) = this.upgrade() {
this.running.store(false, Ordering::Release);
}
})
.expect("failed to spawn driver thread");
}
pub fn stop(&self) {
if !self.running.load(Ordering::Acquire) {
return;
}
self.stop_flag.store(true, Ordering::Release);
}
}
impl Drop for DriverRunner {
fn drop(&mut self) {
self.stop_flag.store(true, Ordering::Release);
}
}
#[cfg(test)]
mod tests {
use std::{sync::mpsc, time::Duration};
use crate::pp_log::PpLog;
use super::*;
use crate::element::{Element, ElementType, element_pp_log};
struct LoopingDriver {
pp_log: PpLog,
started: mpsc::Sender<()>,
stopped: mpsc::Sender<()>,
}
impl Element for LoopingDriver {
fn name(&self) -> Arc<str> {
"looping".into()
}
fn element_type(&self) -> ElementType {
ElementType::Other
}
fn pp_log(&self) -> &PpLog {
&self.pp_log
}
fn pp_log_mut(&mut self) -> &mut PpLog {
&mut self.pp_log
}
}
impl Driver for LoopingDriver {
fn run(&mut self, stop: &StopReceiver, _bus: &Bus) -> Result<()> {
let _ = self.started.send(());
while !stop.is_stopped() {
thread::sleep(Duration::from_millis(5));
}
let _ = self.stopped.send(());
Ok(())
}
}
#[test]
fn dropping_the_last_handle_stops_the_background_thread() {
let (started_tx, started_rx) = mpsc::channel();
let (stopped_tx, stopped_rx) = mpsc::channel();
let runner = DriverRunner::new(LoopingDriver {
started: started_tx,
stopped: stopped_tx,
pp_log: element_pp_log(ElementType::Other, "looping", None),
});
runner.run();
started_rx
.recv_timeout(Duration::from_secs(1))
.expect("driver should start");
drop(runner);
stopped_rx
.recv_timeout(Duration::from_secs(1))
.expect("dropping the last DriverRunner handle should stop the background thread");
}
}