use std::cell::RefCell;
use std::rc::Weak;
pub trait Subscriber {
fn mark_dirty(&self);
}
thread_local! {
static OBSERVER_STACK: RefCell<Vec<Weak<dyn Subscriber>>> = RefCell::new(Vec::new());
}
struct StackGuard;
impl Drop for StackGuard {
fn drop(&mut self) {
OBSERVER_STACK.with(|stack| {
stack.borrow_mut().pop();
});
}
}
pub fn with_observer<R>(observer: Weak<dyn Subscriber>, f: impl FnOnce() -> R) -> R {
OBSERVER_STACK.with(|stack| stack.borrow_mut().push(observer));
let _guard = StackGuard;
f()
}
pub fn current_observer() -> Option<Weak<dyn Subscriber>> {
OBSERVER_STACK.with(|stack| stack.borrow().last().cloned())
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
use std::rc::Rc;
struct MockSub(Cell<u32>);
impl Subscriber for MockSub {
fn mark_dirty(&self) {
self.0.set(self.0.get() + 1);
}
}
#[test]
fn no_observer_outside_scope() {
assert!(current_observer().is_none());
}
#[test]
fn observer_present_inside_with_observer() {
let sub = Rc::new(MockSub(Cell::new(0)));
with_observer(Rc::downgrade(&sub) as Weak<dyn Subscriber>, || {
assert!(current_observer().is_some());
});
assert!(current_observer().is_none());
}
#[test]
fn with_observer_returns_closure_value() {
let sub = Rc::new(MockSub(Cell::new(0)));
let result = with_observer(Rc::downgrade(&sub) as Weak<dyn Subscriber>, || 42);
assert_eq!(result, 42);
}
}