use alloc::sync::Arc;
use core::future::Future;
use core::sync::atomic::{AtomicBool, Ordering};
use crate::pipe::SendPipe;
pub trait DemandGate: Send + Sync + 'static {
fn is_armed(&self) -> bool;
fn on_close(&self) {}
}
pub struct AlwaysArmed;
impl DemandGate for AlwaysArmed {
fn is_armed(&self) -> bool {
true
}
}
pub struct AtomicGate(Arc<AtomicBool>);
impl AtomicGate {
#[must_use]
pub fn pair(initial_armed: bool) -> (Self, AtomicGateController) {
let shared = Arc::new(AtomicBool::new(initial_armed));
(Self(Arc::clone(&shared)), AtomicGateController(shared))
}
}
impl DemandGate for AtomicGate {
fn is_armed(&self) -> bool {
self.0.load(Ordering::Acquire)
}
}
pub struct AtomicGateController(Arc<AtomicBool>);
impl AtomicGateController {
pub fn arm(&self) {
self.0.store(true, Ordering::Release);
}
pub fn disarm(&self) {
self.0.store(false, Ordering::Release);
}
#[must_use]
pub fn is_armed(&self) -> bool {
self.0.load(Ordering::Acquire)
}
}
pub struct Demand<S, G = AlwaysArmed> {
inner: S,
gate: G,
}
impl<S, G> Demand<S, G> {
#[must_use]
pub fn new(inner: S, gate: G) -> Self {
Self { inner, gate }
}
#[must_use]
pub fn is_armed(&self) -> bool
where
G: DemandGate,
{
self.gate.is_armed()
}
}
impl<S, G> SendPipe for Demand<S, G>
where
S: SendPipe,
S::In: Send,
G: DemandGate,
{
type In = S::In;
type Out = ();
type Err = S::Err;
fn call(&self, item: S::In) -> impl Future<Output = Result<(), S::Err>> + Send {
let armed = self.gate.is_armed();
async move {
if armed {
self.inner.call(item).await.map(|_| ())
} else {
Ok(())
}
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
use alloc::sync::Arc;
use core::sync::atomic::AtomicUsize;
use futures::executor::block_on;
#[derive(Debug)]
struct NoErr;
struct CountingSink(Arc<AtomicUsize>);
impl SendPipe for CountingSink {
type In = u32;
type Out = ();
type Err = NoErr;
fn call(&self, _input: u32) -> impl Future<Output = Result<(), NoErr>> + Send {
let calls = Arc::clone(&self.0);
async move {
calls.fetch_add(1, Ordering::Relaxed);
Ok(())
}
}
}
#[test]
fn always_armed_always_dispatches() {
let calls = Arc::new(AtomicUsize::new(0));
let gated = Demand::new(CountingSink(Arc::clone(&calls)), AlwaysArmed);
block_on(gated.call(1)).unwrap();
assert_eq!(calls.load(Ordering::Relaxed), 1);
}
#[test]
fn closed_gate_is_dormant_no_dispatch() {
let calls = Arc::new(AtomicUsize::new(0));
let (gate, _controller) = AtomicGate::pair(false);
let gated = Demand::new(CountingSink(Arc::clone(&calls)), gate);
for _ in 0..100 {
block_on(gated.call(1)).expect("dormant returns Ok");
}
assert_eq!(
calls.load(Ordering::Relaxed),
0,
"no dispatch while dormant"
);
}
#[test]
fn controller_arms_after_gate_moved_into_pipe() {
let calls = Arc::new(AtomicUsize::new(0));
let (gate, controller) = AtomicGate::pair(false);
let gated = Demand::new(CountingSink(Arc::clone(&calls)), gate);
block_on(gated.call(1)).unwrap();
assert_eq!(calls.load(Ordering::Relaxed), 0, "dormant before arm");
controller.arm();
block_on(gated.call(2)).unwrap();
assert_eq!(calls.load(Ordering::Relaxed), 1, "dispatches once armed");
controller.disarm();
block_on(gated.call(3)).unwrap();
assert_eq!(
calls.load(Ordering::Relaxed),
1,
"dormant again after disarm"
);
}
}