use std::cell::{Cell, RefCell};
use crate::shared::{Shared, SharedMut, WeakMut, shared, shared_mut};
thread_local! {
static DEPTH: Cell<usize> = const { Cell::new(0) };
static DRAINING: Cell<bool> = const { Cell::new(false) };
static PENDING: RefCell<Vec<WeakMut<dyn Observer>>> = const { RefCell::new(Vec::new()) };
}
struct ObserverEntry {
observer: WeakMut<dyn Observer>,
defer_reentrant: bool,
}
impl ObserverEntry {
fn new(observer: &SharedMut<dyn Observer>) -> Self {
let defer_reentrant = observer
.try_borrow()
.map_or(true, |observer| observer.defer_reentrant_update());
ObserverEntry {
observer: SharedMut::downgrade(observer),
defer_reentrant,
}
}
}
struct DepthGuard;
impl DepthGuard {
fn enter() -> Self {
DEPTH.with(|depth| depth.set(depth.get() + 1));
DepthGuard
}
}
impl Drop for DepthGuard {
fn drop(&mut self) {
DEPTH.with(|depth| depth.set(depth.get() - 1));
}
}
struct DrainGuard;
impl DrainGuard {
fn try_enter() -> Option<Self> {
if DRAINING.with(Cell::get) {
return None;
}
DRAINING.with(|flag| flag.set(true));
Some(DrainGuard)
}
}
impl Drop for DrainGuard {
fn drop(&mut self) {
DRAINING.with(|flag| flag.set(false));
}
}
pub(crate) fn deliver(observer: &SharedMut<dyn Observer>) {
match observer.try_borrow_mut() {
Ok(mut delivered) => delivered.update(),
Err(_) => defer(observer),
}
}
fn defer(observer: &SharedMut<dyn Observer>) {
let weak = SharedMut::downgrade(observer);
PENDING.with(|pending| {
let mut pending = pending.borrow_mut();
if !pending.iter().any(|queued| queued.ptr_eq(&weak)) {
pending.push(weak);
}
});
}
fn drain_pending() {
let Some(_draining) = DrainGuard::try_enter() else {
return;
};
let mut stalled: Vec<WeakMut<dyn Observer>> = Vec::new();
loop {
let batch: Vec<WeakMut<dyn Observer>> =
PENDING.with(|pending| pending.borrow_mut().drain(..).collect());
if batch.is_empty() {
break;
}
for weak in batch {
let Some(observer) = weak.upgrade() else {
continue;
};
match observer.try_borrow_mut() {
Ok(mut observer) => observer.update(),
Err(_) => {
if !stalled.iter().any(|queued| queued.ptr_eq(&weak)) {
stalled.push(weak);
}
}
}
}
}
if !stalled.is_empty() {
PENDING.with(|pending| pending.borrow_mut().append(&mut stalled));
}
}
pub trait Observer {
fn update(&mut self);
fn defer_reentrant_update(&self) -> bool {
true
}
}
pub trait AsObservable {
fn observable(&self) -> &Observable;
}
enum Target {
Broadcast(Shared<Observable>),
Deliver(SharedMut<dyn Observer>),
}
pub(crate) struct ResetThenNotify {
reset: Box<dyn Fn()>,
target: Target,
}
impl ResetThenNotify {
pub(crate) fn broadcasting(
observable: Shared<Observable>,
reset: impl Fn() + 'static,
) -> SharedMut<ResetThenNotify> {
shared_mut(ResetThenNotify {
reset: Box::new(reset),
target: Target::Broadcast(observable),
})
}
pub(crate) fn delivering(
observer: SharedMut<dyn Observer>,
reset: impl Fn() + 'static,
) -> SharedMut<ResetThenNotify> {
shared_mut(ResetThenNotify {
reset: Box::new(reset),
target: Target::Deliver(observer),
})
}
pub(crate) fn forwarding(observable: Shared<Observable>) -> SharedMut<ResetThenNotify> {
Self::broadcasting(observable, || {})
}
pub(crate) fn forwarder() -> (Shared<Observable>, SharedMut<ResetThenNotify>) {
let observable = shared(Observable::new());
let forwarder = Self::forwarding(Shared::clone(&observable));
(observable, forwarder)
}
}
impl Observer for ResetThenNotify {
fn update(&mut self) {
(self.reset)();
match &self.target {
Target::Broadcast(observable) => observable.notify_observers(),
Target::Deliver(observer) => deliver(observer),
}
}
}
#[derive(Default)]
pub struct Observable {
observers: RefCell<Vec<ObserverEntry>>,
}
impl Observable {
pub fn new() -> Self {
Observable::default()
}
pub fn register_observer(&self, observer: &SharedMut<dyn Observer>) -> bool {
let weak = SharedMut::downgrade(observer);
let mut observers = self.observers.borrow_mut();
observers.retain(|entry| entry.observer.strong_count() > 0);
if observers.iter().any(|entry| entry.observer.ptr_eq(&weak)) {
return false;
}
observers.push(ObserverEntry::new(observer));
true
}
pub fn unregister_observer(&self, observer: &SharedMut<dyn Observer>) -> bool {
let target = SharedMut::downgrade(observer);
let mut observers = self.observers.borrow_mut();
let mut removed = false;
observers.retain(|entry| {
if entry.observer.ptr_eq(&target) {
removed = true;
return false;
}
entry.observer.strong_count() > 0
});
removed
}
pub fn notify_observers(&self) {
let snapshot: Vec<(SharedMut<dyn Observer>, bool)> = {
let mut observers = self.observers.borrow_mut();
observers.retain(|entry| entry.observer.strong_count() > 0);
observers
.iter()
.filter_map(|entry| {
entry
.observer
.upgrade()
.map(|observer| (observer, entry.defer_reentrant))
})
.collect()
};
{
let _depth = DepthGuard::enter();
for (observer, defer_reentrant) in snapshot {
match observer.try_borrow_mut() {
Ok(mut observer) => observer.update(),
Err(_) => {
if defer_reentrant {
defer(&observer);
}
}
}
}
}
if DEPTH.with(Cell::get) == 0 {
drain_pending();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shared::{Shared, SharedMut, shared_mut};
struct UpdateCounter {
counter: usize,
}
impl UpdateCounter {
fn new() -> SharedMut<UpdateCounter> {
shared_mut(UpdateCounter { counter: 0 })
}
}
impl Observer for UpdateCounter {
fn update(&mut self) {
self.counter += 1;
}
}
fn as_observer(obs: &SharedMut<UpdateCounter>) -> SharedMut<dyn Observer> {
obs.clone()
}
#[test]
fn notify_increments_registered_observers() {
let observable = Observable::new();
let counter = UpdateCounter::new();
assert!(observable.register_observer(&as_observer(&counter)));
assert_eq!(counter.borrow().counter, 0);
observable.notify_observers();
assert_eq!(counter.borrow().counter, 1);
observable.notify_observers();
assert_eq!(counter.borrow().counter, 2);
}
#[test]
fn registration_is_idempotent() {
let observable = Observable::new();
let counter = UpdateCounter::new();
assert!(observable.register_observer(&as_observer(&counter)));
assert!(!observable.register_observer(&as_observer(&counter)));
observable.notify_observers();
assert_eq!(counter.borrow().counter, 1);
}
#[test]
fn unregister_stops_notifications() {
let observable = Observable::new();
let counter = UpdateCounter::new();
observable.register_observer(&as_observer(&counter));
assert!(observable.unregister_observer(&as_observer(&counter)));
observable.notify_observers();
assert_eq!(counter.borrow().counter, 0);
assert!(!observable.unregister_observer(&as_observer(&counter)));
}
#[test]
fn unregister_on_empty_is_harmless() {
let observable = Observable::new();
let counter = UpdateCounter::new();
assert!(!observable.unregister_observer(&as_observer(&counter)));
}
#[test]
fn unregister_unknown_observer_is_not_confused_by_dead_weaks() {
let observable = Observable::new();
let registered = UpdateCounter::new();
observable.register_observer(&as_observer(®istered));
{
let transient = UpdateCounter::new();
observable.register_observer(&as_observer(&transient));
}
let never_registered = UpdateCounter::new();
assert!(!observable.unregister_observer(&as_observer(&never_registered)));
observable.notify_observers();
assert_eq!(registered.borrow().counter, 1);
}
#[test]
fn dropped_observers_are_pruned() {
let observable = Observable::new();
let survivor = UpdateCounter::new();
observable.register_observer(&as_observer(&survivor));
{
let transient = UpdateCounter::new();
observable.register_observer(&as_observer(&transient));
}
observable.notify_observers();
assert_eq!(survivor.borrow().counter, 1);
}
struct ReentrantObserver {
updates: usize,
observable: Shared<Observable>,
spawned: SharedMut<Vec<SharedMut<UpdateCounter>>>,
spawn_count: usize,
}
impl Observer for ReentrantObserver {
fn update(&mut self) {
self.updates += 1;
for _ in 0..self.spawn_count {
let extra = UpdateCounter::new();
self.observable
.register_observer(&(extra.clone() as SharedMut<dyn Observer>));
self.spawned.borrow_mut().push(extra);
}
}
}
#[test]
fn add_observers_during_notify_does_not_miss_initial_observers() {
let observable = Shared::new(Observable::new());
let spawned: SharedMut<Vec<SharedMut<UpdateCounter>>> = shared_mut(Vec::new());
let plain = UpdateCounter::new();
observable.register_observer(&as_observer(&plain));
let reentrant = shared_mut(ReentrantObserver {
updates: 0,
observable: observable.clone(),
spawned: spawned.clone(),
spawn_count: 10,
});
observable.register_observer(&(reentrant.clone() as SharedMut<dyn Observer>));
observable.notify_observers();
assert_eq!(plain.borrow().counter, 1);
assert_eq!(reentrant.borrow().updates, 1);
assert_eq!(spawned.borrow().len(), 10);
assert!(spawned.borrow().iter().all(|o| o.borrow().counter == 0));
}
struct Renotifier {
updates: usize,
observable: Shared<Observable>,
}
impl Observer for Renotifier {
fn update(&mut self) {
self.updates += 1;
if self.updates == 1 {
self.observable.notify_observers();
}
}
}
struct CrossNotifier {
updates: usize,
other: Shared<Observable>,
fired: bool,
}
impl Observer for CrossNotifier {
fn update(&mut self) {
self.updates += 1;
if !self.fired {
self.fired = true;
self.other.notify_observers();
}
}
}
#[test]
fn cross_observable_reentrant_notification_is_redelivered() {
let a = Shared::new(Observable::new());
let b = Shared::new(Observable::new());
let listener = shared_mut(CrossNotifier {
updates: 0,
other: b.clone(),
fired: false,
});
a.register_observer(&(listener.clone() as SharedMut<dyn Observer>));
b.register_observer(&(listener.clone() as SharedMut<dyn Observer>));
let bystander = UpdateCounter::new();
b.register_observer(&as_observer(&bystander));
a.notify_observers();
assert_eq!(listener.borrow().updates, 2);
assert_eq!(bystander.borrow().counter, 1);
}
#[test]
fn notification_blocked_by_an_outside_borrow_is_delivered_later() {
let observable = Observable::new();
let counter = UpdateCounter::new();
observable.register_observer(&as_observer(&counter));
{
let held = counter.borrow();
observable.notify_observers();
assert_eq!(held.counter, 0);
}
observable.notify_observers();
assert_eq!(counter.borrow().counter, 2);
}
#[test]
fn reentrant_notification_defers_the_in_flight_observer() {
let observable = Shared::new(Observable::new());
let plain = UpdateCounter::new();
observable.register_observer(&as_observer(&plain));
let renotifier = shared_mut(Renotifier {
updates: 0,
observable: observable.clone(),
});
observable.register_observer(&(renotifier.clone() as SharedMut<dyn Observer>));
observable.notify_observers();
assert_eq!(renotifier.borrow().updates, 2);
assert_eq!(plain.borrow().counter, 2);
}
}