use std::cell::{Cell, RefCell};
use std::rc::Rc;
use rustdv_sim::sync::Event;
struct ObjInner {
count: Cell<usize>,
drained: Event,
raised_ever: Cell<bool>,
active: RefCell<Vec<String>>,
}
#[derive(Clone)]
pub struct ObjectionRegistry {
inner: Rc<ObjInner>,
}
impl ObjectionRegistry {
#[allow(clippy::new_without_default)]
pub fn new() -> ObjectionRegistry {
ObjectionRegistry {
inner: Rc::new(ObjInner {
count: Cell::new(0),
drained: Event::new(),
raised_ever: Cell::new(false),
active: RefCell::new(Vec::new()),
}),
}
}
pub fn raise(&self, description: &str) -> ObjectionGuard {
let inner = self.inner.clone();
inner.count.set(inner.count.get() + 1);
inner.raised_ever.set(true);
inner.drained.clear();
inner.active.borrow_mut().push(description.to_string());
ObjectionGuard { inner, description: description.to_string() }
}
pub fn count(&self) -> usize {
self.inner.count.get()
}
pub fn ever_raised(&self) -> bool {
self.inner.raised_ever.get()
}
pub async fn wait_drained_event(&self) {
self.inner.drained.wait().await;
}
pub fn active(&self) -> Vec<String> {
self.inner.active.borrow().clone()
}
pub async fn wait_all_dropped(&self) {
if !self.inner.raised_ever.get() {
rustdv_sim::log::warning(
"all_objections_dropped awaited but no objection was ever raised",
);
return;
}
if self.inner.count.get() == 0 {
return;
}
self.inner.drained.wait().await;
}
}
pub struct ObjectionGuard {
inner: Rc<ObjInner>,
description: String,
}
impl Drop for ObjectionGuard {
fn drop(&mut self) {
let mut active = self.inner.active.borrow_mut();
if let Some(pos) = active.iter().position(|d| *d == self.description) {
active.remove(pos);
}
drop(active);
let n = self.inner.count.get().saturating_sub(1);
self.inner.count.set(n);
if n == 0 {
self.inner.drained.set();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rustdv_sim::testing::{assert_pending, block_on};
#[test]
fn a_guard_raises_and_dropping_it_drops() {
let reg = ObjectionRegistry::new();
assert_eq!(reg.count(), 0);
{
let _g = reg.raise("stimulus");
assert_eq!(reg.count(), 1);
}
assert_eq!(reg.count(), 0, "the guard dropped it");
}
#[test]
fn nested_objections_end_only_at_the_last_drop() {
let reg = ObjectionRegistry::new();
let a = reg.raise("a");
let b = reg.raise("b");
assert_eq!(reg.count(), 2);
drop(a);
assert_eq!(reg.count(), 1, "one left");
drop(b);
assert_eq!(reg.count(), 0);
}
#[test]
fn drained_fires_when_a_raised_count_returns_to_zero() {
block_on(async {
let reg = ObjectionRegistry::new();
let g = reg.raise("work");
let waiter = reg.clone();
rustdv_sim::executor::spawn(async move {
waiter.wait_drained_event().await;
});
drop(g);
reg.wait_drained_event().await;
});
}
#[test]
fn never_objecting_never_fires_the_drained_event() {
let reg = ObjectionRegistry::new();
assert_eq!(reg.count(), 0);
assert_pending(async move { reg.wait_drained_event().await });
}
}