use std::cell::{Cell, RefCell};
use std::rc::{Rc, Weak};
use crate::component::request_rebuild;
type Listener = Rc<dyn Fn()>;
#[derive(Default)]
struct Listeners {
entries: RefCell<Vec<(u64, Listener)>>,
next: Cell<u64>,
}
pub trait Listenable {
fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription;
}
#[must_use = "a subscription stops listening as soon as it is dropped"]
pub struct Subscription {
listeners: Weak<Listeners>,
id: u64,
}
impl Subscription {
pub fn forget(self) {
std::mem::forget(self);
}
}
impl Drop for Subscription {
fn drop(&mut self) {
if let Some(listeners) = self.listeners.upgrade() {
let removed = {
let mut entries = listeners.entries.borrow_mut();
entries
.iter()
.position(|(id, _)| *id == self.id)
.map(|at| entries.remove(at))
};
drop(removed);
}
}
}
#[derive(Clone, Default)]
pub struct ChangeNotifier {
listeners: Rc<Listeners>,
}
impl ChangeNotifier {
pub fn new() -> Self {
Self::default()
}
pub fn notify_listeners(&self) {
let told: Vec<Listener> = self
.listeners
.entries
.borrow()
.iter()
.map(|(_, listener)| listener.clone())
.collect();
for listener in told {
listener();
}
request_rebuild();
}
pub fn listener_count(&self) -> usize {
self.listeners.entries.borrow().len()
}
}
impl Listenable for ChangeNotifier {
fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription {
let id = self.listeners.next.get();
self.listeners.next.set(id + 1);
self.listeners
.entries
.borrow_mut()
.push((id, Rc::new(listener)));
Subscription {
listeners: Rc::downgrade(&self.listeners),
id,
}
}
}
impl std::fmt::Debug for ChangeNotifier {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ChangeNotifier({} listeners)", self.listener_count())
}
}
pub struct ValueNotifier<T> {
value: Rc<RefCell<T>>,
notifier: ChangeNotifier,
}
impl<T> Clone for ValueNotifier<T> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
notifier: self.notifier.clone(),
}
}
}
impl<T: 'static> ValueNotifier<T> {
pub fn new(value: T) -> Self {
Self {
value: Rc::new(RefCell::new(value)),
notifier: ChangeNotifier::new(),
}
}
pub fn get(&self) -> T
where
T: Clone,
{
self.value.borrow().clone()
}
pub fn with<R>(&self, read: impl FnOnce(&T) -> R) -> R {
read(&self.value.borrow())
}
pub fn set(&self, value: T)
where
T: PartialEq,
{
if *self.value.borrow() == value {
return;
}
*self.value.borrow_mut() = value;
self.notifier.notify_listeners();
}
pub fn update(&self, change: impl FnOnce(&mut T)) {
change(&mut self.value.borrow_mut());
self.notifier.notify_listeners();
}
}
impl<T: Default + 'static> Default for ValueNotifier<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T> Listenable for ValueNotifier<T> {
fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription {
self.notifier.add_listener(listener)
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for ValueNotifier<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("ValueNotifier")
.field(&*self.value.borrow())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::take_rebuild_request;
#[test]
fn listeners_are_told_in_the_order_they_were_added() {
let notifier = ChangeNotifier::new();
let log = Rc::new(RefCell::new(Vec::new()));
let (a, b) = (log.clone(), log.clone());
let _a = notifier.add_listener(move || a.borrow_mut().push("a"));
let _b = notifier.add_listener(move || b.borrow_mut().push("b"));
notifier.notify_listeners();
assert_eq!(*log.borrow(), ["a", "b"]);
}
#[test]
fn a_dropped_subscription_stops_the_notifications() {
let notifier = ChangeNotifier::new();
let count = Rc::new(Cell::new(0));
let counted = count.clone();
let subscription = notifier.add_listener(move || counted.set(counted.get() + 1));
notifier.notify_listeners();
assert_eq!(notifier.listener_count(), 1);
drop(subscription);
assert_eq!(notifier.listener_count(), 0);
notifier.notify_listeners();
assert_eq!(count.get(), 1);
}
#[test]
fn a_forgotten_subscription_keeps_listening() {
let notifier = ChangeNotifier::new();
let count = Rc::new(Cell::new(0));
let counted = count.clone();
notifier
.add_listener(move || counted.set(counted.get() + 1))
.forget();
notifier.notify_listeners();
notifier.notify_listeners();
assert_eq!(count.get(), 2);
}
#[test]
fn a_listener_may_drop_another_during_the_notification() {
let notifier = ChangeNotifier::new();
let count = Rc::new(Cell::new(0));
let slot: Rc<RefCell<Option<Subscription>>> = Rc::default();
let counted = count.clone();
let victim = notifier.add_listener(move || counted.set(counted.get() + 1));
*slot.borrow_mut() = Some(victim);
let dropper = slot.clone();
let _first = notifier.add_listener(move || {
dropper.borrow_mut().take();
});
notifier.notify_listeners();
notifier.notify_listeners();
assert_eq!(count.get(), 1);
}
#[test]
fn a_value_notifier_tells_only_when_the_value_changes() {
let value = ValueNotifier::new(1);
let count = Rc::new(Cell::new(0));
let counted = count.clone();
let _listener = value.add_listener(move || counted.set(counted.get() + 1));
value.set(1);
assert_eq!(count.get(), 0, "the same value is not a change");
value.set(2);
value.update(|v| *v += 1);
assert_eq!((count.get(), value.get()), (2, 3));
}
#[test]
fn a_change_asks_for_a_rebuild() {
take_rebuild_request();
let value = ValueNotifier::new(String::new());
value.set("x".into());
assert!(take_rebuild_request());
value.set("x".into());
assert!(!take_rebuild_request(), "and no change asks for nothing");
}
}