proxima_primitives/pipe/
demand.rs1use alloc::sync::Arc;
18use core::future::Future;
19use core::sync::atomic::{AtomicBool, Ordering};
20
21use crate::pipe::SendPipe;
22
23pub trait DemandGate: Send + Sync + 'static {
28 fn is_armed(&self) -> bool;
30
31 fn on_close(&self) {}
35}
36
37pub struct AlwaysArmed;
40
41impl DemandGate for AlwaysArmed {
42 fn is_armed(&self) -> bool {
43 true
44 }
45}
46
47pub struct AtomicGate(Arc<AtomicBool>);
51
52impl AtomicGate {
53 #[must_use]
56 pub fn pair(initial_armed: bool) -> (Self, AtomicGateController) {
57 let shared = Arc::new(AtomicBool::new(initial_armed));
58 (Self(Arc::clone(&shared)), AtomicGateController(shared))
59 }
60}
61
62impl DemandGate for AtomicGate {
63 fn is_armed(&self) -> bool {
64 self.0.load(Ordering::Acquire)
65 }
66}
67
68pub struct AtomicGateController(Arc<AtomicBool>);
72
73impl AtomicGateController {
74 pub fn arm(&self) {
76 self.0.store(true, Ordering::Release);
77 }
78
79 pub fn disarm(&self) {
81 self.0.store(false, Ordering::Release);
82 }
83
84 #[must_use]
85 pub fn is_armed(&self) -> bool {
86 self.0.load(Ordering::Acquire)
87 }
88}
89
90pub struct Demand<S, G = AlwaysArmed> {
93 inner: S,
94 gate: G,
95}
96
97impl<S, G> Demand<S, G> {
98 #[must_use]
100 pub fn new(inner: S, gate: G) -> Self {
101 Self { inner, gate }
102 }
103
104 #[must_use]
106 pub fn is_armed(&self) -> bool
107 where
108 G: DemandGate,
109 {
110 self.gate.is_armed()
111 }
112}
113
114impl<S, G> SendPipe for Demand<S, G>
115where
116 S: SendPipe,
117 S::In: Send,
118 G: DemandGate,
119{
120 type In = S::In;
121 type Out = ();
122 type Err = S::Err;
123
124 fn call(&self, item: S::In) -> impl Future<Output = Result<(), S::Err>> + Send {
125 let armed = self.gate.is_armed();
127 async move {
128 if armed {
129 self.inner.call(item).await.map(|_| ())
130 } else {
131 Ok(())
132 }
133 }
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 #![allow(clippy::unwrap_used, clippy::expect_used)]
140
141 use super::*;
142 use alloc::sync::Arc;
143 use core::sync::atomic::AtomicUsize;
144 use futures::executor::block_on;
145
146 #[derive(Debug)]
147 struct NoErr;
148
149 struct CountingSink(Arc<AtomicUsize>);
150
151 impl SendPipe for CountingSink {
152 type In = u32;
153 type Out = ();
154 type Err = NoErr;
155
156 fn call(&self, _input: u32) -> impl Future<Output = Result<(), NoErr>> + Send {
157 let calls = Arc::clone(&self.0);
158 async move {
159 calls.fetch_add(1, Ordering::Relaxed);
160 Ok(())
161 }
162 }
163 }
164
165 #[test]
166 fn always_armed_always_dispatches() {
167 let calls = Arc::new(AtomicUsize::new(0));
168 let gated = Demand::new(CountingSink(Arc::clone(&calls)), AlwaysArmed);
169 block_on(gated.call(1)).unwrap();
170 assert_eq!(calls.load(Ordering::Relaxed), 1);
171 }
172
173 #[test]
174 fn closed_gate_is_dormant_no_dispatch() {
175 let calls = Arc::new(AtomicUsize::new(0));
176 let (gate, _controller) = AtomicGate::pair(false);
177 let gated = Demand::new(CountingSink(Arc::clone(&calls)), gate);
178 for _ in 0..100 {
179 block_on(gated.call(1)).expect("dormant returns Ok");
180 }
181 assert_eq!(
182 calls.load(Ordering::Relaxed),
183 0,
184 "no dispatch while dormant"
185 );
186 }
187
188 #[test]
189 fn controller_arms_after_gate_moved_into_pipe() {
190 let calls = Arc::new(AtomicUsize::new(0));
191 let (gate, controller) = AtomicGate::pair(false);
192 let gated = Demand::new(CountingSink(Arc::clone(&calls)), gate);
193
194 block_on(gated.call(1)).unwrap();
195 assert_eq!(calls.load(Ordering::Relaxed), 0, "dormant before arm");
196
197 controller.arm();
198 block_on(gated.call(2)).unwrap();
199 assert_eq!(calls.load(Ordering::Relaxed), 1, "dispatches once armed");
200
201 controller.disarm();
202 block_on(gated.call(3)).unwrap();
203 assert_eq!(
204 calls.load(Ordering::Relaxed),
205 1,
206 "dormant again after disarm"
207 );
208 }
209}