use std::mem;
use std::sync::{Arc, Condvar, Mutex};
#[cfg(test)]
#[path = "trigger_test.rs"]
mod test;
const POISON_PANIC: &str = "invariant error: trigger mutex should never become poisoned";
pub struct Sender<T>(Arc<Inner<T>>);
pub struct Receiver<T>(Arc<Inner<T>>);
pub fn trigger<T>() -> (Sender<T>, Receiver<T>) {
let inner =
Arc::new(Inner { state_mutex: Mutex::new(State::Pending), condvar: Condvar::new() });
(Sender(Arc::clone(&inner)), Receiver(inner))
}
impl<T> Sender<T> {
pub fn activate(&self, value: T) {
self.0.activate(value);
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
self.0.disconnect();
}
}
impl<T> Receiver<T> {
pub fn wait(&self) -> Option<T> {
self.0.wait()
}
}
impl<T> Drop for Receiver<T> {
fn drop(&mut self) {
self.0.disconnect();
}
}
enum State<T> {
Pending,
Activated(T),
Disconnected(Option<T>),
}
struct Inner<T> {
state_mutex: Mutex<State<T>>,
condvar: Condvar,
}
impl<T> Inner<T> {
fn disconnect(&self) {
let &Inner { state_mutex, condvar } = &self;
let mut state_guard = state_mutex.lock().expect(POISON_PANIC);
if let State::Disconnected(_) = *state_guard {
return;
}
let state = mem::replace(&mut *state_guard, State::Pending);
let disconnect_value = if let State::Activated(value) = state { Some(value) } else { None };
*state_guard = State::Disconnected(disconnect_value);
condvar.notify_one();
}
fn activate(&self, value: T) {
let &Inner { state_mutex, condvar } = &self;
let mut state_guard = state_mutex.lock().expect(POISON_PANIC);
if let State::Disconnected(_) = *state_guard {
return;
}
*state_guard = State::Activated(value);
condvar.notify_one();
}
fn wait(&self) -> Option<T> {
let &Inner { state_mutex, condvar } = &self;
let mut state_guard = state_mutex.lock().expect(POISON_PANIC);
loop {
let idle = match &*state_guard {
State::Pending => State::Pending,
State::Activated(_) => State::Pending,
State::Disconnected(_) => State::Disconnected(None),
};
match mem::replace(&mut *state_guard, idle) {
State::Pending => {
state_guard = condvar.wait(state_guard).expect(POISON_PANIC);
}
State::Activated(value) => return Some(value),
State::Disconnected(value) => return value,
}
}
}
}