use std::cell::RefCell;
use std::rc::Rc;
use futures::channel::mpsc;
use futures::stream::Stream;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectivityState {
Idle,
Connecting,
Ready,
TransientFailure,
}
#[derive(Default)]
struct Inner {
state: Option<ConnectivityState>,
watchers: Vec<mpsc::UnboundedSender<ConnectivityState>>,
}
#[derive(Clone, Default)]
pub(crate) struct StateWatch {
inner: Rc<RefCell<Inner>>,
}
impl StateWatch {
pub(crate) fn get(&self) -> ConnectivityState {
self.inner.borrow().state.unwrap_or(ConnectivityState::Idle)
}
pub(crate) fn set(&self, state: ConnectivityState) {
let mut inner = self.inner.borrow_mut();
if inner.state == Some(state) {
return;
}
inner.state = Some(state);
inner.watchers.retain(|w| w.unbounded_send(state).is_ok());
}
pub(crate) fn watch(&self) -> impl Stream<Item = ConnectivityState> + 'static {
let (tx, rx) = mpsc::unbounded();
self.inner.borrow_mut().watchers.push(tx);
rx
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures::StreamExt;
#[test]
fn starts_idle() {
assert_eq!(StateWatch::default().get(), ConnectivityState::Idle);
}
#[test]
fn watchers_see_transitions_but_not_repeats() {
futures::executor::block_on(async {
let watch = StateWatch::default();
let mut seen = watch.watch();
watch.set(ConnectivityState::Connecting);
watch.set(ConnectivityState::Connecting); watch.set(ConnectivityState::Ready);
watch.set(ConnectivityState::Idle);
drop(watch);
let all: Vec<_> = seen.by_ref().collect().await;
assert_eq!(
all,
vec![
ConnectivityState::Connecting,
ConnectivityState::Ready,
ConnectivityState::Idle
]
);
});
}
#[test]
fn a_watcher_added_later_sees_only_what_follows() {
futures::executor::block_on(async {
let watch = StateWatch::default();
watch.set(ConnectivityState::Ready);
let mut late = watch.watch();
watch.set(ConnectivityState::Idle);
drop(watch);
let all: Vec<_> = late.by_ref().collect().await;
assert_eq!(all, vec![ConnectivityState::Idle]);
});
}
#[test]
fn a_dropped_watcher_does_not_accumulate() {
let watch = StateWatch::default();
drop(watch.watch());
watch.set(ConnectivityState::Connecting);
watch.set(ConnectivityState::Ready);
assert!(watch.inner.borrow().watchers.is_empty());
}
}