use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::mpsc::Sender;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use crate::error::{Error, Result};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SessionExit {
Stopped,
ProducerEnded,
Disconnected,
IncompatibleDevice,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ControlMsg {
Arm,
Disarm,
Stop,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct DesiredState(u64);
impl DesiredState {
const ARMED: u64 = 1;
pub(crate) const fn initial() -> Self {
Self(0)
}
pub(crate) fn is_armed(self) -> bool {
self.0 & Self::ARMED != 0
}
}
#[derive(Clone, Copy)]
struct RateState {
pps: u32,
min: u32,
max: u32,
}
#[derive(Clone)]
pub struct SessionControl {
inner: Arc<SessionControlInner>,
}
struct SessionControlInner {
desired_state: AtomicU64,
stop_requested: AtomicBool,
control_tx: Mutex<Sender<ControlMsg>>,
color_delay: Mutex<Duration>,
rate: Mutex<RateState>,
}
impl SessionControl {
#[cfg(test)]
pub(crate) fn new(control_tx: Sender<ControlMsg>, color_delay: Duration, pps: u32) -> Self {
Self::new_with_pps_bounds(control_tx, color_delay, pps, 0, u32::MAX)
}
pub(crate) fn new_with_pps_bounds(
control_tx: Sender<ControlMsg>,
color_delay: Duration,
pps: u32,
pps_min: u32,
pps_max: u32,
) -> Self {
Self {
inner: Arc::new(SessionControlInner {
desired_state: AtomicU64::new(0),
stop_requested: AtomicBool::new(false),
control_tx: Mutex::new(control_tx),
color_delay: Mutex::new(color_delay),
rate: Mutex::new(RateState {
pps,
min: pps_min,
max: pps_max,
}),
}),
}
}
fn set_desired_armed(&self, armed: bool, msg: ControlMsg) -> Result<()> {
let armed_bit = u64::from(armed);
let mut current = self.inner.desired_state.load(Ordering::SeqCst);
loop {
if current & DesiredState::ARMED == armed_bit {
break;
}
let next = (current.wrapping_add(2) & !DesiredState::ARMED) | armed_bit;
match self.inner.desired_state.compare_exchange(
current,
next,
Ordering::SeqCst,
Ordering::SeqCst,
) {
Ok(_) => break,
Err(observed) => current = observed,
}
}
let tx = self
.inner
.control_tx
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tx.send(msg).map_err(|_| {
Error::disconnected("desired session state recorded, but scheduler notification failed")
})
}
pub fn arm(&self) -> Result<()> {
self.set_desired_armed(true, ControlMsg::Arm)
}
pub fn disarm(&self) -> Result<()> {
self.set_desired_armed(false, ControlMsg::Disarm)
}
pub(crate) fn desired_state(&self) -> DesiredState {
DesiredState(self.inner.desired_state.load(Ordering::SeqCst))
}
pub fn is_armed(&self) -> bool {
self.desired_state().is_armed()
}
pub fn set_color_delay(&self, delay: Duration) {
*self
.inner
.color_delay
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()) = delay;
}
pub fn color_delay(&self) -> Duration {
*self
.inner
.color_delay
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub fn set_pps(&self, pps: u32) -> Result<()> {
let mut rate = self
.inner
.rate
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if pps < rate.min || pps > rate.max {
return Err(Error::invalid_config(format!(
"PPS {pps} outside active backend range [{}, {}]",
rate.min, rate.max
)));
}
rate.pps = pps;
Ok(())
}
pub(crate) fn update_pps_bounds(&self, min: u32, max: u32) -> Result<()> {
let mut rate = self
.inner
.rate
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
if rate.pps < min || rate.pps > max {
return Err(Error::invalid_config(format!(
"current PPS {} outside replacement backend range [{min}, {max}]",
rate.pps
)));
}
rate.min = min;
rate.max = max;
Ok(())
}
pub fn pps(&self) -> u32 {
self.inner
.rate
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.pps
}
pub fn stop(&self) -> Result<()> {
self.inner.stop_requested.store(true, Ordering::SeqCst);
let tx = self
.inner
.control_tx
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tx.send(ControlMsg::Stop).map_err(|_| {
Error::disconnected("session stop recorded, but scheduler notification failed")
})
}
pub fn is_stop_requested(&self) -> bool {
self.inner.stop_requested.load(Ordering::SeqCst)
}
}