openlogi_device/
device_io.rs1use tokio::sync::watch;
8
9#[derive(Clone)]
11pub struct DeviceIoSignal {
12 sender: watch::Sender<DeviceIoState>,
13}
14
15#[derive(Clone)]
17pub struct DeviceIoGate {
18 receiver: watch::Receiver<DeviceIoState>,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22enum DeviceIoState {
23 Allowed,
24 Suspended,
25}
26
27#[must_use]
29pub fn device_io_channel() -> (DeviceIoSignal, DeviceIoGate) {
30 let (sender, receiver) = watch::channel(DeviceIoState::Allowed);
31 (DeviceIoSignal { sender }, DeviceIoGate { receiver })
32}
33
34impl DeviceIoSignal {
35 #[must_use]
39 pub fn suspend(&self) -> bool {
40 self.sender.send_if_modified(|state| {
41 if *state == DeviceIoState::Suspended {
42 return false;
43 }
44 *state = DeviceIoState::Suspended;
45 true
46 })
47 }
48
49 #[must_use]
53 pub fn resume(&self) -> bool {
54 self.sender.send_if_modified(|state| {
55 if *state == DeviceIoState::Allowed {
56 return false;
57 }
58 *state = DeviceIoState::Allowed;
59 true
60 })
61 }
62}
63
64impl DeviceIoGate {
65 #[must_use]
67 pub fn allows_io(&self) -> bool {
68 *self.receiver.borrow() == DeviceIoState::Allowed
69 }
70
71 pub async fn changed(&mut self) -> Option<bool> {
76 self.receiver.changed().await.ok()?;
77 Some(self.allows_io())
78 }
79
80 pub async fn wait_until_allowed(&mut self) -> bool {
83 while !self.allows_io() {
84 if self.changed().await.is_none() {
85 return false;
86 }
87 }
88 true
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use std::time::Duration;
95
96 use super::device_io_channel;
97
98 #[tokio::test]
99 async fn latest_transition_is_retained_and_duplicates_coalesce() {
100 let (signal, mut gate) = device_io_channel();
101 assert!(gate.allows_io());
102
103 assert!(signal.suspend());
104 assert!(!signal.suspend());
105 gate.changed()
106 .await
107 .expect("the suspend transition should remain available");
108 assert!(!gate.allows_io());
109 tokio::time::timeout(Duration::from_millis(10), gate.changed())
110 .await
111 .expect_err("duplicate suspend publications must coalesce");
112
113 assert!(signal.resume());
114 assert!(gate.wait_until_allowed().await);
115 assert!(gate.allows_io());
116 }
117}