use std::sync::mpsc::{self, RecvTimeoutError};
use std::thread::JoinHandle;
use super::{Pace, SimLoop};
use crate::fault::{Fault, STEPPING, catching};
pub struct Driver<L: SimLoop> {
cmd_tx: mpsc::Sender<L::Command>,
handle: Option<JoinHandle<()>>,
}
impl<L: SimLoop> std::fmt::Debug for Driver<L> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Driver")
.field("thread", &self.handle.is_some())
.finish_non_exhaustive()
}
}
impl<L> Driver<L>
where
L: SimLoop + Send + 'static,
L::Command: Send + 'static,
{
pub fn spawn(sim: L, on_fault: impl FnOnce(Fault) + Send + 'static) -> Self {
let (cmd_tx, cmd_rx) = mpsc::channel();
let handle = std::thread::spawn(move || {
if let Err(fault) = catching(STEPPING, || run(sim, &cmd_rx)) {
log::error!("{fault}");
on_fault(fault);
}
});
Self {
cmd_tx,
handle: Some(handle),
}
}
pub fn send(&mut self, cmd: L::Command) {
drop(self.cmd_tx.send(cmd));
}
pub fn update(&mut self, _dt: f64) {}
pub fn shutdown(&mut self, cmd: L::Command) {
drop(self.cmd_tx.send(cmd));
if let Some(handle) = self.handle.take() {
drop(handle.join());
}
}
}
fn run<L: SimLoop + Send>(mut sim: L, cmd_rx: &mpsc::Receiver<L::Command>) {
sim.start();
loop {
match rayon::scope(|_| sim.pump()) {
Pace::Idle => {
let Ok(cmd) = cmd_rx.recv() else { return };
if sim.handle_command(cmd) {
return;
}
}
Pace::After(wait) => match cmd_rx.recv_timeout(wait) {
Ok(cmd) => {
if sim.handle_command(cmd) {
return;
}
}
Err(RecvTimeoutError::Timeout) => {}
Err(RecvTimeoutError::Disconnected) => return,
},
Pace::Now => {}
}
while let Ok(cmd) = cmd_rx.try_recv() {
if sim.handle_command(cmd) {
return;
}
}
}
}