use std::sync::{Mutex, MutexGuard, PoisonError, TryLockError};
use std::time::{Duration, Instant};
use crate::error::Result;
use crate::protocol::{Frame, frame_brake, frame_mode, frame_velocity};
use crate::transport::Transport;
use crate::types::Mode;
use super::timing::BusTiming;
pub(super) fn lock<T>(m: &Mutex<T>) -> MutexGuard<'_, T> {
m.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(super) struct Port<T> {
pub(super) transport: T,
pub(super) last_tx: Option<Instant>,
pub(super) timing: BusTiming,
}
impl<T: Transport> Port<T> {
fn gap_remaining(&self) -> Duration {
match self.last_tx {
Some(at) => self.transport.pace(
(crate::protocol::frame_time() + self.timing.min_gap).saturating_sub(at.elapsed()),
),
None => Duration::ZERO,
}
}
}
pub(super) fn with_gap<T: Transport, R>(
port: &Mutex<Port<T>>,
mut op: impl FnMut(&mut T) -> Result<R>,
) -> Result<R> {
loop {
let mut guard = lock(port);
let wait = guard.gap_remaining();
if wait.is_zero() {
let result = op(&mut guard.transport);
guard.last_tx = Some(Instant::now());
return result;
}
drop(guard);
std::thread::sleep(wait);
}
}
fn send_round<T: Transport>(
port: &Mutex<Port<T>>,
ids: &[u8],
frame_for: impl Fn(u8) -> Frame,
deadline: Instant,
) -> Result<()> {
let mut result = Ok(());
for &id in ids {
let frame = frame_for(id);
if let Err(e) = with_gap(port, |t| t.send(&frame))
&& result.is_ok()
{
result = Err(e);
}
}
let remaining = deadline.saturating_duration_since(Instant::now());
let sleep = lock(port).transport.pace(remaining);
if !sleep.is_zero() {
std::thread::sleep(sleep);
}
result
}
pub(super) fn stop_all<T: Transport>(port: &Mutex<Port<T>>, ids: &[u8]) {
if ids.is_empty() {
return;
}
let BusTiming {
stop_accel,
stop_gap,
..
} = lock(port).timing;
let mut deadline = Instant::now();
for step in 0..15u8 {
deadline += stop_gap;
let _ = send_round(
port,
ids,
|id| match step {
0..=4 => frame_mode(id, Mode::Velocity),
5..=9 => frame_velocity(id, 0, stop_accel),
_ => frame_brake(id),
},
deadline,
);
}
}
pub(super) fn mode_all<T: Transport>(port: &Mutex<Port<T>>, ids: &[u8], mode: Mode) -> Result<()> {
if ids.is_empty() {
return Ok(());
}
let mode_repeat_gap = lock(port).timing.mode_repeat_gap;
let mut deadline = Instant::now();
for _ in 0..5 {
deadline += mode_repeat_gap;
send_round(port, ids, |id| frame_mode(id, mode), deadline)?;
}
Ok(())
}
pub(super) fn peek_min_gap<T: Transport>(port: &Mutex<Port<T>>) -> Option<Duration> {
match port.try_lock() {
Ok(p) => Some(p.timing.min_gap),
Err(TryLockError::Poisoned(p)) => Some(p.into_inner().timing.min_gap),
Err(TryLockError::WouldBlock) => None,
}
}