use tokio::sync::watch;
#[derive(Clone)]
pub struct DeviceIoSignal {
sender: watch::Sender<DeviceIoState>,
}
#[derive(Clone)]
pub struct DeviceIoGate {
receiver: watch::Receiver<DeviceIoState>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum DeviceIoState {
Allowed,
Suspended,
}
#[must_use]
pub fn device_io_channel() -> (DeviceIoSignal, DeviceIoGate) {
let (sender, receiver) = watch::channel(DeviceIoState::Allowed);
(DeviceIoSignal { sender }, DeviceIoGate { receiver })
}
impl DeviceIoSignal {
#[must_use]
pub fn suspend(&self) -> bool {
self.sender.send_if_modified(|state| {
if *state == DeviceIoState::Suspended {
return false;
}
*state = DeviceIoState::Suspended;
true
})
}
#[must_use]
pub fn resume(&self) -> bool {
self.sender.send_if_modified(|state| {
if *state == DeviceIoState::Allowed {
return false;
}
*state = DeviceIoState::Allowed;
true
})
}
}
impl DeviceIoGate {
#[must_use]
pub fn allows_io(&self) -> bool {
*self.receiver.borrow() == DeviceIoState::Allowed
}
pub async fn changed(&mut self) -> Option<bool> {
self.receiver.changed().await.ok()?;
Some(self.allows_io())
}
pub async fn wait_until_allowed(&mut self) -> bool {
while !self.allows_io() {
if self.changed().await.is_none() {
return false;
}
}
true
}
}
#[cfg(test)]
mod tests {
use std::time::Duration;
use super::device_io_channel;
#[tokio::test]
async fn latest_transition_is_retained_and_duplicates_coalesce() {
let (signal, mut gate) = device_io_channel();
assert!(gate.allows_io());
assert!(signal.suspend());
assert!(!signal.suspend());
gate.changed()
.await
.expect("the suspend transition should remain available");
assert!(!gate.allows_io());
tokio::time::timeout(Duration::from_millis(10), gate.changed())
.await
.expect_err("duplicate suspend publications must coalesce");
assert!(signal.resume());
assert!(gate.wait_until_allowed().await);
assert!(gate.allows_io());
}
}