use std::any::TypeId;
use std::cell::Cell;
use std::fmt::{self, Debug};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
use crate::app::{App, Handle, HandleId};
#[derive(Clone)]
pub struct Listener {
callback: Rc<dyn Fn(&mut App)>,
identity: ListenerIdentity,
}
#[derive(Clone, Copy)]
enum ListenerIdentity {
Closure,
HandleMethod(HandleId, TypeId),
}
impl Listener {
pub fn new(callback: impl Fn(&mut App) + 'static) -> Listener {
Listener {
callback: Rc::new(callback),
identity: ListenerIdentity::Closure,
}
}
pub fn handle_method<T: 'static, F>(this: Handle<T>, f: F) -> Listener
where
F: Fn(Handle<T>, &mut App) + 'static,
{
const {
assert!(
std::mem::size_of::<F>() == 0,
"pass the function by name: a fn pointer or capturing closure \
has no canonical identity to match on removal"
)
};
Listener {
callback: Rc::new(move |app: &mut App| f(this, app)),
identity: ListenerIdentity::HandleMethod(this.id(), TypeId::of::<F>()),
}
}
pub fn call(&self, app: &mut App) {
(self.callback)(app);
}
}
impl PartialEq for Listener {
fn eq(&self, other: &Listener) -> bool {
match (self.identity, other.identity) {
(ListenerIdentity::Closure, ListenerIdentity::Closure) => {
Rc::ptr_eq(&self.callback, &other.callback)
}
(
ListenerIdentity::HandleMethod(handle, function),
ListenerIdentity::HandleMethod(other_handle, other_function),
) => handle == other_handle && function == other_function,
_ => false,
}
}
}
impl Eq for Listener {}
impl Hash for Listener {
fn hash<H: Hasher>(&self, state: &mut H) {
match self.identity {
ListenerIdentity::Closure => (Rc::as_ptr(&self.callback) as *const ()).hash(state),
ListenerIdentity::HandleMethod(handle, function) => {
handle.hash(state);
function.hash(state);
}
}
}
}
impl Debug for Listener {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Listener")
}
}
pub trait Listenable {
fn add_listener(&self, app: &mut App, listener: Listener);
fn remove_listener(&self, app: &mut App, listener: &Listener);
}
pub struct MergingListenable {
children: Vec<Option<Rc<dyn Listenable>>>,
}
impl MergingListenable {
pub fn new(children: Vec<Option<Rc<dyn Listenable>>>) -> MergingListenable {
MergingListenable { children }
}
}
impl Listenable for MergingListenable {
fn add_listener(&self, app: &mut App, listener: Listener) {
for child in self.children.iter().flatten() {
child.add_listener(app, listener.clone());
}
}
fn remove_listener(&self, app: &mut App, listener: &Listener) {
for child in self.children.iter().flatten() {
child.remove_listener(app, listener);
}
}
}
impl Debug for MergingListenable {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Listenable::merge([{} children])", self.children.len())
}
}
pub trait ValueListenable<T>: Listenable {
fn value<'a>(&'a self, app: &'a App) -> &'a T;
}
#[derive(Debug, Default)]
pub struct ChangeNotifierData {
count: usize,
listeners: Vec<Option<Listener>>,
notification_call_stack_depth: Rc<Cell<usize>>,
reentrantly_removed_listeners: usize,
#[cfg(debug_assertions)]
debug_disposed: bool,
}
impl ChangeNotifierData {
pub fn new() -> ChangeNotifierData {
ChangeNotifierData::default()
}
pub fn debug_assert_not_disposed(notifier: &ChangeNotifierData) -> bool {
#[cfg(debug_assertions)]
assert!(
!notifier.debug_disposed,
"A ChangeNotifier was used after being disposed.\n\
Once you have called dispose() on a ChangeNotifier, it can no longer be used."
);
let _ = notifier;
true
}
pub fn has_listeners(&self) -> bool {
self.count > 0
}
pub fn add_listener(&mut self, listener: Listener) {
debug_assert!(ChangeNotifierData::debug_assert_not_disposed(self));
if self.count == self.listeners.len() {
if self.count == 0 {
self.listeners = vec![None; 1];
} else {
let mut new_listeners = vec![None; self.listeners.len() * 2];
new_listeners[..self.count].clone_from_slice(&self.listeners[..self.count]);
self.listeners = new_listeners;
}
}
self.listeners[self.count] = Some(listener);
self.count += 1;
}
fn remove_at(&mut self, index: usize) {
self.count -= 1;
if self.count * 2 <= self.listeners.len() {
let mut new_listeners = vec![None; self.count];
new_listeners[..index].clone_from_slice(&self.listeners[..index]);
new_listeners[index..self.count]
.clone_from_slice(&self.listeners[index + 1..self.count + 1]);
self.listeners = new_listeners;
} else {
for i in index..self.count {
let moved = self.listeners[i + 1].clone();
self.listeners[i] = moved;
}
self.listeners[self.count] = None;
}
}
pub fn remove_listener(&mut self, listener: &Listener) {
for i in 0..self.count {
if self.listeners[i].as_ref() == Some(listener) {
if self.notification_call_stack_depth.get() > 0 {
self.listeners[i] = None;
self.reentrantly_removed_listeners += 1;
} else {
self.remove_at(i);
}
break;
}
}
}
pub fn dispose(&mut self) {
debug_assert!(ChangeNotifierData::debug_assert_not_disposed(self));
debug_assert!(
self.notification_call_stack_depth.get() == 0,
"The \"dispose()\" method on {self:?} was called during the call to \
\"notify_listeners()\". This is likely to cause errors since it modifies \
the list of listeners while the list is being used."
);
#[cfg(debug_assertions)]
{
self.debug_disposed = true;
}
self.listeners = Vec::new();
self.count = 0;
self.reentrantly_removed_listeners = 0;
}
pub(crate) fn notify_listeners<T: 'static>(
app: &mut App,
owner: Handle<T>,
field: fn(&mut T) -> &mut ChangeNotifierData,
) {
let (notification, end) = {
let notifier = field(app.get_mut(owner));
debug_assert!(ChangeNotifierData::debug_assert_not_disposed(notifier));
if notifier.count == 0 {
return;
}
let notification =
Notification::begin(Rc::clone(¬ifier.notification_call_stack_depth));
(notification, notifier.count)
};
for i in 0..end {
let listener = field(app.get_mut(owner)).listeners[i].clone();
if let Some(listener) = listener {
listener.call(app);
}
}
drop(notification);
let notifier = field(app.get_mut(owner));
if notifier.notification_call_stack_depth.get() == 0
&& notifier.reentrantly_removed_listeners > 0
{
notifier.compact_reentrantly_removed_listeners();
}
}
fn compact_reentrantly_removed_listeners(&mut self) {
let new_length = self.count - self.reentrantly_removed_listeners;
if new_length * 2 <= self.listeners.len() {
let mut new_listeners = vec![None; new_length];
let mut new_index = 0;
for listener in &self.listeners[..self.count] {
if listener.is_some() {
new_listeners[new_index] = listener.clone();
new_index += 1;
}
}
self.listeners = new_listeners;
} else {
for i in 0..new_length {
if self.listeners[i].is_none() {
let mut swap_index = i + 1;
while self.listeners[swap_index].is_none() {
swap_index += 1;
}
self.listeners.swap(i, swap_index);
}
}
}
self.reentrantly_removed_listeners = 0;
self.count = new_length;
}
}
struct Notification {
depth: Rc<Cell<usize>>,
}
impl Notification {
fn begin(depth: Rc<Cell<usize>>) -> Notification {
depth.set(depth.get() + 1);
Notification { depth }
}
}
impl Drop for Notification {
fn drop(&mut self) {
self.depth.set(self.depth.get() - 1);
}
}
pub trait ListenableObject: Sized + 'static {
fn add_listener(self: Handle<Self>, app: &mut App, listener: Listener);
fn remove_listener(self: Handle<Self>, app: &mut App, listener: &Listener);
}
impl<T: ChangeNotifier> ListenableObject for T {
fn add_listener(self: Handle<Self>, app: &mut App, listener: Listener) {
ChangeNotifier::will_add_listener(self, app);
app.get_mut(self)
.change_notifier_data_mut()
.add_listener(listener);
}
fn remove_listener(self: Handle<Self>, app: &mut App, listener: &Listener) {
if !app.contains(self) {
return;
}
app.get_mut(self)
.change_notifier_data_mut()
.remove_listener(listener);
ChangeNotifier::did_remove_listener(self, app);
}
}
impl<T: ListenableObject> Listenable for Handle<T> {
fn add_listener(&self, app: &mut App, listener: Listener) {
T::add_listener(*self, app, listener);
}
fn remove_listener(&self, app: &mut App, listener: &Listener) {
T::remove_listener(*self, app, listener);
}
}
pub trait ChangeNotifier: Sized + 'static {
fn change_notifier_data(&self) -> &ChangeNotifierData;
fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData;
fn will_add_listener(self: Handle<Self>, app: &mut App) {
let _ = app;
}
fn did_remove_listener(self: Handle<Self>, app: &mut App) {
let _ = app;
}
}
impl ChangeNotifier for ChangeNotifierData {
fn change_notifier_data(&self) -> &ChangeNotifierData {
self
}
fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
self
}
}
impl<T: ChangeNotifier> Handle<T> {
pub fn notify_listeners(self, app: &mut App) {
ChangeNotifierData::notify_listeners(app, self, T::change_notifier_data_mut);
}
}
#[derive(Debug, Default)]
pub struct ValueNotifier<T> {
change_notifier: ChangeNotifierData,
value: T,
}
impl<T> ValueNotifier<T> {
pub fn new(value: T) -> ValueNotifier<T> {
ValueNotifier {
change_notifier: ChangeNotifierData::new(),
value,
}
}
pub fn value(&self) -> &T {
&self.value
}
pub fn dispose(&mut self) {
self.change_notifier.dispose();
}
}
impl<T: 'static> ChangeNotifier for ValueNotifier<T> {
fn change_notifier_data(&self) -> &ChangeNotifierData {
&self.change_notifier
}
fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
&mut self.change_notifier
}
}
impl<T: PartialEq + 'static> Handle<ValueNotifier<T>> {
pub fn set_value(self, app: &mut App, new_value: T) {
if app.get(self).value == new_value {
return;
}
app.get_mut(self).value = new_value;
self.notify_listeners(app);
}
}
impl<T: 'static> ValueListenable<T> for Handle<ValueNotifier<T>> {
fn value<'a>(&'a self, app: &'a App) -> &'a T {
app.get(*self).value()
}
}
#[cfg(test)]
mod tests {
use std::cell::RefCell;
use std::panic::{AssertUnwindSafe, catch_unwind};
use super::*;
use crate::app_cell::AppCell;
type Log = Rc<RefCell<Vec<String>>>;
fn fired(log: &Log) -> Vec<String> {
log.borrow().clone()
}
#[derive(Default)]
struct TestNotifier {
change_notifier: ChangeNotifierData,
}
impl ChangeNotifier for TestNotifier {
fn change_notifier_data(&self) -> &ChangeNotifierData {
&self.change_notifier
}
fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
&mut self.change_notifier
}
}
fn recording(log: &Log, name: &'static str) -> Listener {
let log = Rc::clone(log);
Listener::new(move |_app| log.borrow_mut().push(name.to_string()))
}
fn add(app: &mut App, host: Handle<TestNotifier>, listener: Listener) {
host.add_listener(app, listener);
}
fn remove(app: &mut App, host: Handle<TestNotifier>, listener: &Listener) {
host.remove_listener(app, listener);
}
fn notify(app: &mut App, host: Handle<TestNotifier>) {
host.notify_listeners(app);
}
fn silence_panic_hook() -> Box<dyn Fn(&std::panic::PanicHookInfo<'_>) + Send + Sync + 'static> {
let hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
hook
}
#[test]
fn listeners_are_called_in_the_order_they_were_added() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let listener = recording(&log, "listener");
add(&mut app, host, listener.clone());
add(&mut app, host, listener.clone());
notify(&mut app, host);
assert_eq!(fired(&log), ["listener", "listener"]);
log.borrow_mut().clear();
remove(&mut app, host, &listener);
notify(&mut app, host);
assert_eq!(fired(&log), ["listener"]);
log.borrow_mut().clear();
remove(&mut app, host, &listener);
notify(&mut app, host);
assert_eq!(fired(&log), Vec::<String>::new());
log.borrow_mut().clear();
remove(&mut app, host, &listener);
notify(&mut app, host);
assert_eq!(fired(&log), Vec::<String>::new());
log.borrow_mut().clear();
add(&mut app, host, listener.clone());
notify(&mut app, host);
assert_eq!(fired(&log), ["listener"]);
log.borrow_mut().clear();
let listener1 = recording(&log, "listener1");
add(&mut app, host, listener1.clone());
notify(&mut app, host);
assert_eq!(fired(&log), ["listener", "listener1"]);
log.borrow_mut().clear();
let listener2 = recording(&log, "listener2");
add(&mut app, host, listener2.clone());
notify(&mut app, host);
assert_eq!(fired(&log), ["listener", "listener1", "listener2"]);
log.borrow_mut().clear();
remove(&mut app, host, &listener1);
notify(&mut app, host);
assert_eq!(fired(&log), ["listener", "listener2"]);
log.borrow_mut().clear();
add(&mut app, host, listener1.clone());
notify(&mut app, host);
assert_eq!(fired(&log), ["listener", "listener2", "listener1"]);
}
#[test]
fn a_listener_mutates_the_notifier_dispatching_to_it() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let listener1 = recording(&log, "listener1");
let listener3 = recording(&log, "listener3");
let listener4 = recording(&log, "listener4");
let listener2 = {
let log = Rc::clone(&log);
let (one, three, four) = (listener1.clone(), listener3.clone(), listener4.clone());
Listener::new(move |app: &mut App| {
log.borrow_mut().push("listener2".to_string());
let notifier = &mut app.get_mut(host).change_notifier;
notifier.remove_listener(&one);
notifier.remove_listener(&three);
notifier.add_listener(four.clone());
})
};
add(&mut app, host, listener1);
add(&mut app, host, listener2);
add(&mut app, host, listener3);
notify(&mut app, host);
assert_eq!(fired(&log), ["listener1", "listener2"]);
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), ["listener2", "listener4"]);
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), ["listener2", "listener4", "listener4"]);
}
#[test]
fn a_listener_added_and_removed_immediately_during_notify_is_not_called() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let listener2 = recording(&log, "listener2");
let listener3 = recording(&log, "listener3");
let listener1 = {
let log = Rc::clone(&log);
let two = listener2.clone();
let three = listener3.clone();
Listener::new(move |app: &mut App| {
log.borrow_mut().push("listener1".to_string());
let notifier = &mut app.get_mut(host).change_notifier;
notifier.add_listener(two.clone());
notifier.remove_listener(&two);
notifier.add_listener(three.clone());
})
};
add(&mut app, host, listener1);
notify(&mut app, host);
assert_eq!(fired(&log), ["listener1"]);
}
#[test]
fn a_self_removing_listener_in_the_middle_still_notifies_all() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let listener1 = recording(&log, "listener1");
let self_removing_slot: Rc<RefCell<Option<Listener>>> = Rc::new(RefCell::new(None));
let self_removing = {
let log = Rc::clone(&log);
let slot = Rc::clone(&self_removing_slot);
Listener::new(move |app: &mut App| {
log.borrow_mut().push("selfRemovingListener".to_string());
let me = slot.borrow().clone().unwrap();
app.get_mut(host).change_notifier.remove_listener(&me);
})
};
*self_removing_slot.borrow_mut() = Some(self_removing.clone());
add(&mut app, host, listener1.clone());
add(&mut app, host, self_removing);
add(&mut app, host, listener1);
notify(&mut app, host);
assert_eq!(
fired(&log),
["listener1", "selfRemovingListener", "listener1"]
);
}
#[test]
fn the_first_listener_removing_itself_still_notifies_the_rest() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let self_removing_slot: Rc<RefCell<Option<Listener>>> = Rc::new(RefCell::new(None));
let self_removing = {
let log = Rc::clone(&log);
let slot = Rc::clone(&self_removing_slot);
Listener::new(move |app: &mut App| {
log.borrow_mut().push("selfRemovingListener".to_string());
let me = slot.borrow().clone().unwrap();
app.get_mut(host).change_notifier.remove_listener(&me);
})
};
*self_removing_slot.borrow_mut() = Some(self_removing.clone());
let listener1 = recording(&log, "listener1");
add(&mut app, host, self_removing);
add(&mut app, host, listener1);
notify(&mut app, host);
assert_eq!(fired(&log), ["selfRemovingListener", "listener1"]);
}
#[test]
fn only_a_handle_to_the_same_closure_compares_equal() {
let log = Log::default();
let listener = recording(&log, "a");
assert_eq!(listener, listener.clone());
assert_ne!(listener, recording(&log, "a"));
}
#[test]
fn a_rebuilt_handle_method_listener_matches_like_a_dart_tear_off() {
struct Host;
fn hook(_this: Handle<Host>, _app: &mut App) {}
fn other_hook(_this: Handle<Host>, _app: &mut App) {}
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let a = app.create(Host);
let b = app.create(Host);
assert_eq!(
Listener::handle_method(a, hook),
Listener::handle_method(a, hook)
);
assert_ne!(
Listener::handle_method(a, hook),
Listener::handle_method(b, hook)
);
assert_ne!(
Listener::handle_method(a, hook),
Listener::handle_method(a, other_hook)
);
assert_ne!(Listener::handle_method(a, hook), Listener::new(|_app| {}));
}
#[test]
fn a_handle_method_listener_calls_the_function_with_its_handle() {
struct Host;
thread_local! {
static CALLED_WITH: Cell<Option<HandleId>> = const { Cell::new(None) };
}
fn hook(this: Handle<Host>, _app: &mut App) {
CALLED_WITH.with(|called| called.set(Some(this.id())));
}
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let host = app.create(Host);
Listener::handle_method(host, hook).call(&mut app);
assert_eq!(CALLED_WITH.with(Cell::get), Some(host.id()));
}
#[test]
fn removing_keeps_order_across_both_shrink_branches() {
for first in 0..8 {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let names = ["0", "1", "2", "3", "4", "5", "6", "7"];
let listeners: Vec<Listener> = names.iter().map(|name| recording(&log, name)).collect();
for listener in &listeners {
add(&mut app, host, listener.clone());
}
let mut survivors: Vec<String> = names.iter().map(|n| n.to_string()).collect();
let mut saw_realloc = false;
for step in 0..8 {
let index = (first + step) % 8;
remove(&mut app, host, &listeners[index]);
survivors.retain(|s| s != names[index]);
saw_realloc |= app.get(host).change_notifier.listeners.len() < 8;
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), survivors, "after removing {index}");
assert_eq!(app.get(host).change_notifier.count, survivors.len());
}
assert!(saw_realloc, "the shrink branch never ran");
}
}
#[test]
fn dispose_drops_every_listener_but_leaves_removal_callable() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let listener = recording(&log, "a");
add(&mut app, host, listener.clone());
app.get_mut(host).change_notifier.dispose();
assert!(!app.get(host).change_notifier.has_listeners());
app.get_mut(host).change_notifier.remove_listener(&listener);
assert_eq!(fired(&log), Vec::<String>::new());
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "was used after being disposed")]
fn adding_a_listener_after_dispose_panics_in_debug() {
let mut notifier = ChangeNotifierData::new();
notifier.dispose();
notifier.add_listener(recording(&Log::default(), "a"));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "was used after being disposed")]
fn notifying_after_dispose_panics_in_debug() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let host = app.create(TestNotifier::default());
app.get_mut(host).change_notifier.dispose();
notify(&mut app, host);
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "was used after being disposed")]
fn disposing_twice_panics_in_debug() {
let mut notifier = ChangeNotifierData::new();
notifier.dispose();
notifier.dispose();
}
#[test]
fn has_listeners_is_false_after_dispose() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let counter = app.create(ValueNotifier::new(0i32));
counter.add_listener(&mut app, recording(&Log::default(), "a"));
assert!(app.get(counter).change_notifier.has_listeners());
app.get_mut(counter).dispose();
assert!(!app.get(counter).change_notifier.has_listeners());
}
#[test]
fn value_notifier_notifies_only_when_the_value_differs() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let notifier = app.create(ValueNotifier::new(2.0));
notifier.add_listener(&mut app, {
let log = Rc::clone(&log);
Listener::new(move |app: &mut App| {
log.borrow_mut().push(app.get(notifier).value().to_string());
})
});
notifier.set_value(&mut app, 3.0);
assert_eq!(fired(&log), ["3"]);
log.borrow_mut().clear();
notifier.set_value(&mut app, 3.0);
assert_eq!(fired(&log), Vec::<String>::new());
}
#[test]
fn has_listeners() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let notifier = app.create(ValueNotifier::new(true));
assert!(!app.get(notifier).change_notifier.has_listeners());
let test1 = recording(&Log::default(), "test1");
let test2 = recording(&Log::default(), "test2");
notifier.add_listener(&mut app, test1.clone());
assert!(app.get(notifier).change_notifier.has_listeners());
notifier.add_listener(&mut app, test1.clone());
assert!(app.get(notifier).change_notifier.has_listeners());
notifier.remove_listener(&mut app, &test1);
assert!(app.get(notifier).change_notifier.has_listeners());
notifier.remove_listener(&mut app, &test1);
assert!(!app.get(notifier).change_notifier.has_listeners());
notifier.add_listener(&mut app, test1.clone());
assert!(app.get(notifier).change_notifier.has_listeners());
notifier.add_listener(&mut app, test2.clone());
assert!(app.get(notifier).change_notifier.has_listeners());
notifier.remove_listener(&mut app, &test1);
assert!(app.get(notifier).change_notifier.has_listeners());
notifier.remove_listener(&mut app, &test2);
assert!(!app.get(notifier).change_notifier.has_listeners());
}
#[test]
fn debug_assert_not_disposed_returns_true_while_live() {
let notifier = ChangeNotifierData::new();
assert!(ChangeNotifierData::debug_assert_not_disposed(¬ifier));
}
#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "was used after being disposed")]
fn debug_assert_not_disposed_panics_after_dispose() {
let mut notifier = ChangeNotifierData::new();
notifier.dispose();
ChangeNotifierData::debug_assert_not_disposed(¬ifier);
}
#[test]
fn a_listener_assigns_through_its_owners_setter_and_the_dispatch_recurses() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let counter = app.create(ValueNotifier::new(0i32));
let listener1 = {
let log = Rc::clone(&log);
Listener::new(move |app: &mut App| {
log.borrow_mut().push("listener1".to_string());
if *app.get(counter).value() < 0 {
counter.set_value(app, 0);
}
})
};
counter.add_listener(&mut app, listener1);
counter.notify_listeners(&mut app);
assert_eq!(fired(&log), ["listener1"]);
log.borrow_mut().clear();
counter.set_value(&mut app, 3);
assert_eq!(fired(&log), ["listener1"]);
log.borrow_mut().clear();
counter.set_value(&mut app, -2);
assert_eq!(fired(&log), ["listener1", "listener1"]);
assert_eq!(*app.get(counter).value(), 0);
}
#[test]
fn removing_listeners_while_notifying_on_a_list_which_will_not_resize() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let names = [
"listener0",
"listener1",
"listener2",
"listener3",
"listener4",
"listener5",
"listener6",
"listener7",
"listener8",
"listener9",
"listener10",
"listener11",
];
let listeners: Vec<Listener> = names.iter().map(|name| recording(&log, name)).collect();
let auto_remove = {
let to_remove = [
listeners[1].clone(),
listeners[3].clone(),
listeners[4].clone(),
];
let auto_remove_slot: Rc<RefCell<Option<Listener>>> = Rc::new(RefCell::new(None));
let slot = Rc::clone(&auto_remove_slot);
let listener = Listener::new(move |app: &mut App| {
let notifier = &mut app.get_mut(host).change_notifier;
notifier.remove_listener(&to_remove[0]);
notifier.remove_listener(&to_remove[1]);
notifier.remove_listener(&to_remove[2]);
let me = slot.borrow().clone().unwrap();
notifier.remove_listener(&me);
});
*auto_remove_slot.borrow_mut() = Some(listener.clone());
listener
};
add(&mut app, host, auto_remove);
for listener in &listeners {
add(&mut app, host, listener.clone());
}
let remaining = [
"listener0",
"listener2",
"listener5",
"listener6",
"listener7",
"listener8",
"listener9",
"listener10",
"listener11",
];
notify(&mut app, host);
assert_eq!(fired(&log), remaining);
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), remaining);
for i in [0, 2, 5, 6, 7, 8, 9, 10, 11] {
remove(&mut app, host, &listeners[i]);
}
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), Vec::<String>::new());
}
#[test]
#[cfg(debug_assertions)]
fn dispose_during_notify_panics_and_does_not_finish_the_callback() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let host = app.create(TestNotifier::default());
let finished = Rc::new(Cell::new(false));
let finished_flag = Rc::clone(&finished);
add(
&mut app,
host,
Listener::new(move |app: &mut App| {
app.get_mut(host).change_notifier.dispose();
finished_flag.set(true);
}),
);
let hook = silence_panic_hook();
let panicked = catch_unwind(AssertUnwindSafe(|| {
notify(&mut app, host);
}))
.is_err();
std::panic::set_hook(hook);
assert!(panicked);
assert!(!finished.get());
app.get_mut(host).change_notifier.dispose();
}
#[test]
fn a_bare_change_notifier_handle_can_notify() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let on_undo = app.create(ChangeNotifierData::new());
on_undo.add_listener(&mut app, recording(&log, "undo"));
on_undo.notify_listeners(&mut app);
assert_eq!(fired(&log), ["undo"]);
}
#[test]
fn a_panicking_listener_leaves_the_notifier_usable() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let l0 = recording(&log, "l0");
let l2 = recording(&log, "l2");
let l3 = Listener::new(|_app: &mut App| panic!("bad listener"));
let l1 = {
let log = Rc::clone(&log);
let (zero, two) = (l0.clone(), l2.clone());
Listener::new(move |app: &mut App| {
log.borrow_mut().push("l1".to_string());
let notifier = &mut app.get_mut(host).change_notifier;
notifier.remove_listener(&zero);
notifier.remove_listener(&two);
})
};
add(&mut app, host, l0);
add(&mut app, host, l1);
add(&mut app, host, l2);
add(&mut app, host, l3.clone());
let hook = silence_panic_hook();
let panicked = catch_unwind(AssertUnwindSafe(|| {
notify(&mut app, host);
}))
.is_err();
std::panic::set_hook(hook);
assert!(panicked);
assert_eq!(fired(&log), ["l0", "l1"]);
assert_eq!(
app.get(host)
.change_notifier
.notification_call_stack_depth
.get(),
0
);
assert_eq!(app.get(host).change_notifier.count, 4);
app.get_mut(host).change_notifier.remove_listener(&l3);
assert_eq!(app.get(host).change_notifier.count, 3);
assert_eq!(
app.get(host).change_notifier.reentrantly_removed_listeners,
2
);
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), ["l1"]);
assert_eq!(app.get(host).change_notifier.count, 1);
assert_eq!(
app.get(host).change_notifier.reentrantly_removed_listeners,
0
);
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), ["l1"]);
}
#[test]
fn a_panic_inside_a_nested_dispatch_still_pairs_the_depth_guards() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let inner_host = app.create(TestNotifier::default());
let doomed = recording(&log, "doomed");
add(
&mut app,
inner_host,
Listener::new(|_app: &mut App| panic!("inner listener")),
);
let outer = {
let log = Rc::clone(&log);
let doomed = doomed.clone();
Listener::new(move |app: &mut App| {
log.borrow_mut().push("outer".to_string());
app.get_mut(host).change_notifier.remove_listener(&doomed);
notify(app, inner_host);
})
};
add(&mut app, host, outer);
add(&mut app, host, doomed);
let hook = silence_panic_hook();
let panicked = catch_unwind(AssertUnwindSafe(|| {
notify(&mut app, host);
}))
.is_err();
std::panic::set_hook(hook);
assert!(panicked);
assert_eq!(
app.get(host)
.change_notifier
.notification_call_stack_depth
.get(),
0
);
assert_eq!(
app.get(inner_host)
.change_notifier
.notification_call_stack_depth
.get(),
0
);
let survivor = app.get(host).change_notifier.listeners[0].clone().unwrap();
remove(&mut app, host, &survivor);
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(
app.get(host).change_notifier.reentrantly_removed_listeners,
0
);
assert!(!app.get(host).change_notifier.has_listeners());
}
#[test]
fn dispose_clears_the_compaction_owed_by_a_panicked_dispatch() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let doomed = recording(&log, "doomed");
let saboteur = {
let doomed = doomed.clone();
Listener::new(move |app: &mut App| {
app.get_mut(host).change_notifier.remove_listener(&doomed);
panic!("bad listener");
})
};
add(&mut app, host, saboteur);
add(&mut app, host, doomed);
let hook = silence_panic_hook();
let _ = catch_unwind(AssertUnwindSafe(|| {
notify(&mut app, host);
}));
std::panic::set_hook(hook);
assert_eq!(
app.get(host).change_notifier.reentrantly_removed_listeners,
1
);
app.get_mut(host).change_notifier.dispose();
assert_eq!(
app.get(host).change_notifier.reentrantly_removed_listeners,
0
);
assert_eq!(app.get(host).change_notifier.count, 0);
}
#[test]
#[cfg(not(debug_assertions))]
fn a_disposed_notifier_reused_in_release_does_not_underflow() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let log = Log::default();
let host = app.create(TestNotifier::default());
let doomed = recording(&log, "doomed");
let saboteur = {
let doomed = doomed.clone();
Listener::new(move |app: &mut App| {
app.get_mut(host).change_notifier.remove_listener(&doomed);
panic!("bad listener");
})
};
add(&mut app, host, saboteur);
add(&mut app, host, doomed);
let hook = silence_panic_hook();
let _ = catch_unwind(AssertUnwindSafe(|| {
notify(&mut app, host);
}));
std::panic::set_hook(hook);
app.get_mut(host).change_notifier.dispose();
add(&mut app, host, recording(&log, "after"));
log.borrow_mut().clear();
notify(&mut app, host);
assert_eq!(fired(&log), ["after"]);
assert_eq!(app.get(host).change_notifier.count, 1);
}
}