use std::sync::mpsc;
use std::time::{Duration, Instant};
use crate::backend::BackendKind;
use crate::buffer_estimate::BufferEstimator;
use crate::device::DacInfo;
use crate::error::Error;
use crate::session::{ControlMsg, SessionControl};
use super::content_source::ContentSourceKind;
use super::session::FrameSessionMetrics;
mod blocking_fifo;
mod network_fifo;
mod udp_timed;
mod usb_frame_swap;
pub(crate) enum StepOutcome {
Continue,
StateChanged,
Stopped,
Disconnected,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ControlAction {
None,
StateChanged,
Stop,
}
pub(crate) trait Clock: Send {
fn now(&self) -> Instant;
fn sleep(&self, dur: Duration);
}
pub(crate) struct SystemClock;
impl Clock for SystemClock {
#[inline]
fn now(&self) -> Instant {
Instant::now()
}
#[inline]
fn sleep(&self, dur: Duration) {
std::thread::sleep(dur);
}
}
#[cfg(test)]
pub(crate) struct FakeClock {
now: std::sync::Mutex<Instant>,
slept: std::sync::Mutex<Duration>,
}
#[cfg(test)]
impl FakeClock {
pub(crate) fn new() -> Self {
Self {
now: std::sync::Mutex::new(Instant::now()),
slept: std::sync::Mutex::new(Duration::ZERO),
}
}
pub(crate) fn total_slept(&self) -> Duration {
*self.slept.lock().unwrap()
}
}
#[cfg(test)]
impl Clock for FakeClock {
fn now(&self) -> Instant {
*self.now.lock().unwrap()
}
fn sleep(&self, dur: Duration) {
*self.now.lock().unwrap() += dur;
*self.slept.lock().unwrap() += dur;
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum ShutterState {
Open,
Closed,
Unknown,
}
pub(crate) struct LoopCtx<'a> {
pub backend: &'a mut BackendKind,
pub source: ContentSourceKind<'a>,
pub control: &'a SessionControl,
pub control_rx: &'a mpsc::Receiver<ControlMsg>,
pub metrics: &'a FrameSessionMetrics,
pub shutter_open: &'a mut ShutterState,
pub error_sink: &'a mut dyn FnMut(Error),
pub target_buffer: Duration,
pub pps: u32,
pub is_armed: bool,
pub clock: &'a dyn Clock,
}
impl<'a> LoopCtx<'a> {
pub fn sleep_with_control_check(&mut self, duration: Duration) -> Result<(), StepOutcome> {
const SLICE: Duration = Duration::from_millis(2);
let deadline = self.clock.now() + duration;
loop {
let remaining = deadline.saturating_duration_since(self.clock.now());
if remaining.is_zero() {
return Ok(());
}
self.clock.sleep(remaining.min(SLICE));
self.metrics.mark_loop_activity();
if self.control.is_stop_requested() {
return Err(StepOutcome::Stopped);
}
close_if_disarmed(self.control, self.shutter_open, self.backend);
match process_control_messages(self.control_rx) {
ControlAction::Stop => return Err(StepOutcome::Stopped),
ControlAction::StateChanged => return Err(StepOutcome::StateChanged),
ControlAction::None => {}
}
}
}
pub fn sleep_until_precise(&mut self, deadline: Instant) -> Result<(), StepOutcome> {
const BUSY_WAIT_THRESHOLD: Duration = Duration::from_micros(500);
const SLEEP_SLICE: Duration = Duration::from_millis(1);
loop {
let now = self.clock.now();
if now >= deadline {
return Ok(());
}
let remaining = deadline.duration_since(now);
if remaining > BUSY_WAIT_THRESHOLD {
let slice = remaining
.saturating_sub(BUSY_WAIT_THRESHOLD)
.min(SLEEP_SLICE);
self.clock.sleep(slice);
} else {
std::thread::yield_now();
}
self.metrics.mark_loop_activity();
if self.control.is_stop_requested() {
return Err(StepOutcome::Stopped);
}
close_if_disarmed(self.control, self.shutter_open, self.backend);
match process_control_messages(self.control_rx) {
ControlAction::Stop => return Err(StepOutcome::Stopped),
ControlAction::StateChanged => return Err(StepOutcome::StateChanged),
ControlAction::None => {}
}
}
}
pub fn sleep_and_mark_activity(&self, duration: Duration) {
self.clock.sleep(duration);
self.metrics.mark_loop_activity();
}
}
pub(crate) fn process_control_messages(control_rx: &mpsc::Receiver<ControlMsg>) -> ControlAction {
use std::sync::mpsc::TryRecvError;
let mut action = ControlAction::None;
loop {
match control_rx.try_recv() {
Ok(ControlMsg::Arm | ControlMsg::Disarm) => action = ControlAction::StateChanged,
Ok(ControlMsg::Stop) => return ControlAction::Stop,
Err(TryRecvError::Empty) | Err(TryRecvError::Disconnected) => return action,
}
}
}
pub(crate) fn close_if_disarmed(
control: &SessionControl,
shutter_state: &mut ShutterState,
backend: &mut BackendKind,
) {
if !control.is_armed() && *shutter_state != ShutterState::Closed {
*shutter_state = match backend.set_shutter(false) {
Ok(()) => ShutterState::Closed,
Err(_) => ShutterState::Unknown,
};
}
}
pub(crate) trait OutputModelAdapter: Send {
fn step(&mut self, ctx: &mut LoopCtx<'_>) -> StepOutcome;
fn on_reconnect(&mut self, info: &DacInfo, backend: &mut BackendKind);
fn on_state_change(&mut self, _armed: bool) {}
fn drain_and_blank(&mut self, ctx: &mut LoopCtx<'_>, _timeout: Duration) {
blank_and_close_shutter(ctx);
}
}
pub(crate) fn blank_and_close_shutter(ctx: &mut LoopCtx<'_>) {
use crate::point::LaserPoint;
*ctx.shutter_open = match ctx.backend.set_shutter(false) {
Ok(()) => ShutterState::Closed,
Err(_) => ShutterState::Unknown,
};
let blank = [LaserPoint::blanked(0.0, 0.0); 16];
let _ = ctx.backend.try_write(ctx.pps, &blank);
}
pub(crate) fn estimator_fullness(estimator: Option<&dyn BufferEstimator>, pps: u32) -> u64 {
estimator.map_or(0, |e| {
let now = if e.needs_clock() {
Instant::now()
} else {
sentinel_instant()
};
e.estimated_fullness(now, pps)
})
}
fn sentinel_instant() -> Instant {
thread_local! {
static SENTINEL: Instant = Instant::now();
}
SENTINEL.with(|t| *t)
}
pub(crate) fn drain_via_estimator(ctx: &mut LoopCtx<'_>, timeout: Duration) {
if timeout.is_zero() {
return;
}
let drain_pps = ctx.pps.min(ctx.backend.caps().pps_max);
let start = ctx.clock.now();
let deadline = start + timeout;
const POLL: Duration = Duration::from_millis(5);
let mut now = start;
while now < deadline {
let buffered = estimator_fullness(ctx.backend.estimator(), drain_pps);
if buffered == 0 {
break;
}
if ctx.control.is_stop_requested() {
break;
}
close_if_disarmed(ctx.control, ctx.shutter_open, ctx.backend);
if !matches!(
process_control_messages(ctx.control_rx),
ControlAction::None
) {
break;
}
ctx.clock.sleep(POLL);
ctx.metrics.mark_loop_activity();
now = ctx.clock.now();
}
}
pub(crate) fn source_mismatch(ctx: &mut LoopCtx<'_>, adapter: &str) -> StepOutcome {
let msg = format!(
"{adapter} requires a content source matching its output model \
(pairing violated; this is a driver wiring bug)"
);
log::error!("{msg}");
(ctx.error_sink)(Error::invalid_config(msg));
StepOutcome::Stopped
}
pub(crate) fn for_backend(
backend: &BackendKind,
source_is_frame: bool,
) -> std::result::Result<Box<dyn OutputModelAdapter>, Error> {
use crate::device::OutputModel;
backend.validate_output_model()?;
let model = &backend.caps().output_model;
let model_is_frame = matches!(model, OutputModel::UsbFrameSwap);
if model_is_frame != source_is_frame {
return Err(Error::invalid_config(format!(
"output model {:?} requires a {} content source, got {}",
model,
if model_is_frame { "frame" } else { "FIFO" },
if source_is_frame { "frame" } else { "FIFO" },
)));
}
Ok(match model {
OutputModel::UsbFrameSwap => Box::new(usb_frame_swap::UsbFrameSwapAdapter::new(backend)),
OutputModel::NetworkFifo => Box::new(network_fifo::NetworkFifoAdapter::new(backend)),
OutputModel::UdpTimed => Box::new(udp_timed::UdpTimedAdapter::new(backend)),
OutputModel::BlockingFifo => Box::new(blocking_fifo::BlockingFifoAdapter::new(backend)),
})
}