Skip to main content

inset_foundation/
change_notifier.rs

1//! Flutter counterpart: `foundation/change_notifier.dart`.
2
3use std::any::TypeId;
4use std::cell::Cell;
5use std::fmt::{self, Debug};
6use std::hash::{Hash, Hasher};
7use std::rc::Rc;
8
9use crate::app::{App, Handle, HandleId};
10
11/// A closure registered with a [`Listenable`].
12///
13/// Flutter's counterpart is `VoidCallback`. Receives [`App`] because a Rust
14/// listener cannot own what it mutates.
15///
16/// [`Listenable::remove_listener`] matches identity: [`Listener::new`] equals
17/// only its clones; [`Listener::handle_method`] equals any other built from the
18/// same handle and function.
19#[derive(Clone)]
20pub struct Listener {
21    callback: Rc<dyn Fn(&mut App)>,
22    identity: ListenerIdentity,
23}
24
25/// Equality key. Closures compare by allocation; handle-methods by
26/// `(handle, TypeId of F)`. TypeId, not address: release codegen can merge
27/// identical bodies.
28#[derive(Clone, Copy)]
29enum ListenerIdentity {
30    Closure,
31    HandleMethod(HandleId, TypeId),
32}
33
34impl Listener {
35    /// Wraps a closure as a listener.
36    pub fn new(callback: impl Fn(&mut App) + 'static) -> Listener {
37        Listener {
38            callback: Rc::new(callback),
39            identity: ListenerIdentity::Closure,
40        }
41    }
42
43    /// A handle's associated function, comparable like a Dart method tear-off.
44    ///
45    /// Pass the function by name. Rebuild at the removal site with the same
46    /// handle and function. A fn pointer or capturing closure is rejected: it
47    /// has no canonical identity. Two captureless closure literals also never
48    /// match — each has a unique type, as in Dart where only tear-offs
49    /// canonicalize.
50    pub fn handle_method<T: 'static, F>(this: Handle<T>, f: F) -> Listener
51    where
52        F: Fn(Handle<T>, &mut App) + 'static,
53    {
54        const {
55            assert!(
56                std::mem::size_of::<F>() == 0,
57                "pass the function by name: a fn pointer or capturing closure \
58                 has no canonical identity to match on removal"
59            )
60        };
61        Listener {
62            callback: Rc::new(move |app: &mut App| f(this, app)),
63            identity: ListenerIdentity::HandleMethod(this.id(), TypeId::of::<F>()),
64        }
65    }
66
67    /// Calls the closure.
68    pub fn call(&self, app: &mut App) {
69        (self.callback)(app);
70    }
71}
72
73impl PartialEq for Listener {
74    fn eq(&self, other: &Listener) -> bool {
75        match (self.identity, other.identity) {
76            (ListenerIdentity::Closure, ListenerIdentity::Closure) => {
77                Rc::ptr_eq(&self.callback, &other.callback)
78            }
79            (
80                ListenerIdentity::HandleMethod(handle, function),
81                ListenerIdentity::HandleMethod(other_handle, other_function),
82            ) => handle == other_handle && function == other_function,
83            _ => false,
84        }
85    }
86}
87
88impl Eq for Listener {}
89
90impl Hash for Listener {
91    fn hash<H: Hasher>(&self, state: &mut H) {
92        match self.identity {
93            ListenerIdentity::Closure => (Rc::as_ptr(&self.callback) as *const ()).hash(state),
94            ListenerIdentity::HandleMethod(handle, function) => {
95                handle.hash(state);
96                function.hash(state);
97            }
98        }
99    }
100}
101
102impl Debug for Listener {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        f.write_str("Listener")
105    }
106}
107
108/// An object that maintains a list of listeners.
109///
110/// The listeners are typically used to notify clients that the object has been
111/// updated.
112///
113/// There are two variants of this interface:
114///
115///  * [`ValueListenable`], an interface that augments the [`Listenable`]
116///    interface with the concept of a _current value_.
117///
118///  * `Animation`, an interface that augments the [`ValueListenable`] interface
119///    to add the concept of direction (forward or reverse).
120///
121/// Many classes in the Flutter API use or implement these interfaces. The
122/// following subclasses are especially relevant:
123///
124///  * [`ChangeNotifier`], which can be mixed in to create objects that
125///    implement the [`Listenable`] interface.
126///
127///  * [`ValueNotifier`], which implements the [`ValueListenable`] interface
128///    with a mutable value that triggers the notifications when modified.
129///
130/// The terms "notify clients", "send notifications", "trigger notifications",
131/// and "fire notifications" are used interchangeably.
132pub trait Listenable {
133    /// Register a closure to be called when the object notifies its listeners.
134    fn add_listener(&self, app: &mut App, listener: Listener);
135
136    /// Remove a previously registered closure from the list of closures that the
137    /// object notifies.
138    fn remove_listener(&self, app: &mut App, listener: &Listener);
139}
140
141/// A [`Listenable`] that triggers when any of the given [`Listenable`]s
142/// themselves trigger.
143///
144/// Once it is created, items must not be added to or removed from the list.
145/// Doing so will lead to memory leaks or exceptions.
146///
147/// The list may contain `None`s; they are ignored.
148///
149/// Dart's `Listenable.merge` factory.
150pub struct MergingListenable {
151    children: Vec<Option<Rc<dyn Listenable>>>,
152}
153
154impl MergingListenable {
155    /// Dart `Listenable.merge(listenables)`.
156    pub fn new(children: Vec<Option<Rc<dyn Listenable>>>) -> MergingListenable {
157        MergingListenable { children }
158    }
159}
160
161impl Listenable for MergingListenable {
162    fn add_listener(&self, app: &mut App, listener: Listener) {
163        for child in self.children.iter().flatten() {
164            child.add_listener(app, listener.clone());
165        }
166    }
167
168    fn remove_listener(&self, app: &mut App, listener: &Listener) {
169        for child in self.children.iter().flatten() {
170            child.remove_listener(app, listener);
171        }
172    }
173}
174
175impl Debug for MergingListenable {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(f, "Listenable::merge([{} children])", self.children.len())
178    }
179}
180
181/// An interface for implementors of [`Listenable`] that expose a [`value`].
182///
183/// This interface is implemented by [`ValueNotifier<T>`] and `Animation<T>`, and
184/// allows other APIs to accept either of those implementations interchangeably.
185///
186/// [`value`]: ValueListenable::value
187pub trait ValueListenable<T>: Listenable {
188    /// The current value of the object.
189    ///
190    /// When the value changes, the callbacks registered with
191    /// [`Listenable::add_listener`] will be invoked.
192    fn value<'a>(&'a self, app: &'a App) -> &'a T;
193}
194
195/// The fields of Dart's `ChangeNotifier` mixin.
196#[derive(Debug, Default)]
197pub struct ChangeNotifierData {
198    count: usize,
199    // Dart keeps `_listeners` as a fixed-length list. `count` is the live
200    // length; slots from `count` onward, and slots vacated during a dispatch,
201    // are `None`.
202    listeners: Vec<Option<Listener>>,
203    /// Shared with [`Notification`] so a panic still decrements.
204    notification_call_stack_depth: Rc<Cell<usize>>,
205    reentrantly_removed_listeners: usize,
206    #[cfg(debug_assertions)]
207    debug_disposed: bool,
208}
209
210impl ChangeNotifierData {
211    /// Creates a notifier with no listeners.
212    pub fn new() -> ChangeNotifierData {
213        ChangeNotifierData::default()
214    }
215
216    /// Used by owners to assert that the [`ChangeNotifier`] has not yet been
217    /// disposed.
218    ///
219    /// Call it inside a `debug_assert!`, as Dart calls it inside an `assert`,
220    /// so that it is stripped in release:
221    ///
222    /// ```
223    /// # use inset_foundation::ChangeNotifierData;
224    /// # struct MyNotifier { change_notifier: ChangeNotifierData }
225    /// impl MyNotifier {
226    ///     fn do_update(&mut self) {
227    ///         debug_assert!(ChangeNotifierData::debug_assert_not_disposed(&self.change_notifier));
228    ///         // ...
229    ///     }
230    /// }
231    /// ```
232    pub fn debug_assert_not_disposed(notifier: &ChangeNotifierData) -> bool {
233        #[cfg(debug_assertions)]
234        assert!(
235            !notifier.debug_disposed,
236            "A ChangeNotifier was used after being disposed.\n\
237             Once you have called dispose() on a ChangeNotifier, it can no longer be used."
238        );
239        let _ = notifier;
240        true
241    }
242
243    /// Whether any listeners are currently registered.
244    ///
245    /// Clients should not depend on this value for their behavior, because having
246    /// one listener's logic change when another listener happens to start or stop
247    /// listening will lead to extremely hard-to-track bugs. Owners might use
248    /// this information to determine whether to do any work when there are no
249    /// listeners, however; for example, resuming a stream when a listener is
250    /// added and pausing it when a listener is removed.
251    ///
252    /// Typically this is used by overriding [`Listenable::add_listener`], checking
253    /// if [`has_listeners`] is false before calling `self.change_notifier.add_listener()`,
254    /// and if so, starting whatever work is needed to determine when to call
255    /// [`notify_listeners`]; and similarly, by overriding
256    /// [`Listenable::remove_listener`], checking if [`has_listeners`] is false
257    /// after calling `self.change_notifier.remove_listener()`, and if so, stopping that
258    /// same work.
259    ///
260    /// This method returns false if [`dispose`] has been called.
261    ///
262    /// [`has_listeners`]: ChangeNotifierData::has_listeners
263    /// [`notify_listeners`]: Handle::notify_listeners
264    /// [`dispose`]: ChangeNotifierData::dispose
265    pub fn has_listeners(&self) -> bool {
266        self.count > 0
267    }
268
269    /// Register a closure to be called when the object changes.
270    ///
271    /// If the given closure is already registered, an additional instance is
272    /// added, and must be removed the same number of times it is added before it
273    /// will stop being called.
274    ///
275    /// This method must not be called after [`dispose`] has been called.
276    ///
277    /// If a listener is added twice, and is removed once during an iteration
278    /// (e.g. in response to a notification), it will still be called again. If,
279    /// on the other hand, it is removed as many times as it was registered, then
280    /// it will no longer be called. This odd behavior is the result of the
281    /// [`ChangeNotifier`] not being able to determine which listener is being
282    /// removed, since they are identical, therefore it will conservatively still
283    /// call all the listeners when it knows that any are still registered.
284    ///
285    /// This surprising behavior can be unexpectedly observed when registering a
286    /// listener on two separate objects which are both forwarding all
287    /// registrations to a common upstream object.
288    ///
289    /// See also:
290    ///
291    ///  * [`remove_listener`], which removes a previously registered closure from
292    ///    the list of closures that are notified when the object changes.
293    ///
294    /// [`dispose`]: ChangeNotifierData::dispose
295    /// [`remove_listener`]: ChangeNotifierData::remove_listener
296    pub fn add_listener(&mut self, listener: Listener) {
297        debug_assert!(ChangeNotifierData::debug_assert_not_disposed(self));
298
299        if self.count == self.listeners.len() {
300            if self.count == 0 {
301                self.listeners = vec![None; 1];
302            } else {
303                let mut new_listeners = vec![None; self.listeners.len() * 2];
304                new_listeners[..self.count].clone_from_slice(&self.listeners[..self.count]);
305                self.listeners = new_listeners;
306            }
307        }
308        self.listeners[self.count] = Some(listener);
309        self.count += 1;
310    }
311
312    fn remove_at(&mut self, index: usize) {
313        // The list holding the listeners is not growable for performances reasons.
314        // We still want to shrink this list if a lot of listeners have been added
315        // and then removed outside a notify_listeners iteration.
316        // We do this only when the real number of listeners is half the length
317        // of our list.
318        self.count -= 1;
319        if self.count * 2 <= self.listeners.len() {
320            let mut new_listeners = vec![None; self.count];
321
322            // Listeners before the index are at the same place.
323            new_listeners[..index].clone_from_slice(&self.listeners[..index]);
324
325            // Listeners after the index move towards the start of the list.
326            new_listeners[index..self.count]
327                .clone_from_slice(&self.listeners[index + 1..self.count + 1]);
328
329            self.listeners = new_listeners;
330        } else {
331            // When there are more listeners than half the length of the list, we only
332            // shift our listeners, so that we avoid to reallocate memory for the
333            // whole list.
334            for i in index..self.count {
335                let moved = self.listeners[i + 1].clone();
336                self.listeners[i] = moved;
337            }
338            self.listeners[self.count] = None;
339        }
340    }
341
342    /// Remove a previously registered closure from the list of closures that are
343    /// notified when the object changes.
344    ///
345    /// If the given listener is not registered, the call is ignored.
346    ///
347    /// This method returns immediately if [`dispose`] has been called.
348    ///
349    /// If a listener is added twice, and is removed once during an iteration
350    /// (e.g. in response to a notification), it will still be called again. If,
351    /// on the other hand, it is removed as many times as it was registered, then
352    /// it will no longer be called. This odd behavior is the result of the
353    /// [`ChangeNotifier`] not being able to determine which listener is being
354    /// removed, since they are identical, therefore it will conservatively still
355    /// call all the listeners when it knows that any are still registered.
356    ///
357    /// See also:
358    ///
359    ///  * [`add_listener`], which registers a closure to be called when the object
360    ///    changes.
361    ///
362    /// [`dispose`]: ChangeNotifierData::dispose
363    /// [`add_listener`]: ChangeNotifierData::add_listener
364    pub fn remove_listener(&mut self, listener: &Listener) {
365        // This method is allowed to be called on disposed instances for usability
366        // reasons. Due to how our frame scheduling logic between render objects and
367        // overlays, it is common that the owner of this instance would be disposed a
368        // frame earlier than the listeners. Allowing calls to this method after it
369        // is disposed makes it easier for listeners to properly clean up.
370        for i in 0..self.count {
371            if self.listeners[i].as_ref() == Some(listener) {
372                if self.notification_call_stack_depth.get() > 0 {
373                    // We don't resize the list during notify_listeners iterations
374                    // but we set to null, the listeners we want to remove. We will
375                    // effectively resize the list at the end of all notify_listeners
376                    // iterations.
377                    self.listeners[i] = None;
378                    self.reentrantly_removed_listeners += 1;
379                } else {
380                    // When we are outside the notify_listeners iterations we can
381                    // effectively shrink the list.
382                    self.remove_at(i);
383                }
384                break;
385            }
386        }
387    }
388
389    /// Discards any resources used by the object.
390    ///
391    /// After this is called, the object is not in a usable state and should be
392    /// discarded (calls to [`add_listener`] will panic in debug builds after the
393    /// object is disposed).
394    ///
395    /// This method should only be called by the object's owner.
396    ///
397    /// This method does not notify listeners, and clears the listener list once
398    /// it is called. Owners of this type must decide on whether to notify
399    /// listeners or not immediately before disposal.
400    ///
401    /// [`add_listener`]: ChangeNotifierData::add_listener
402    pub fn dispose(&mut self) {
403        debug_assert!(ChangeNotifierData::debug_assert_not_disposed(self));
404        debug_assert!(
405            self.notification_call_stack_depth.get() == 0,
406            "The \"dispose()\" method on {self:?} was called during the call to \
407             \"notify_listeners()\". This is likely to cause errors since it modifies \
408             the list of listeners while the list is being used."
409        );
410        #[cfg(debug_assertions)]
411        {
412            self.debug_disposed = true;
413        }
414        self.listeners = Vec::new();
415        self.count = 0;
416        // A panicking listener skips Dart's per-listener `catch`, which is
417        // what zeros this. Leaving it set underflows
418        // `count - reentrantly_removed_listeners` on the next dispatch.
419        //
420        // The depth is not reset: it is shared with any live Notification,
421        // which decrements on drop.
422        self.reentrantly_removed_listeners = 0;
423    }
424
425    /// Dispatch through [`Handle::notify_listeners`]. The owner is a handle, not
426    /// `&mut self`, so a listener can re-enter it.
427    pub(crate) fn notify_listeners<T: 'static>(
428        app: &mut App,
429        owner: Handle<T>,
430        field: fn(&mut T) -> &mut ChangeNotifierData,
431    ) {
432        let (notification, end) = {
433            let notifier = field(app.get_mut(owner));
434            debug_assert!(ChangeNotifierData::debug_assert_not_disposed(notifier));
435            if notifier.count == 0 {
436                return;
437            }
438
439            // To make sure that listeners removed during this iteration are not called,
440            // we set them to null, but we don't shrink the list right away.
441            // By doing this, we can continue to iterate on our list until it reaches
442            // the last listener added before the call to this method.
443
444            // To allow potential listeners to recursively call notify_listeners, we track
445            // the number of times this method is called in notification_call_stack_depth.
446            // Once every recursive iteration is finished (i.e. when
447            // notification_call_stack_depth == 0), we can safely shrink our list so that
448            // it will only contain not null listeners.
449            let notification =
450                Notification::begin(Rc::clone(&notifier.notification_call_stack_depth));
451            (notification, notifier.count)
452        };
453
454        for i in 0..end {
455            let listener = field(app.get_mut(owner)).listeners[i].clone();
456            if let Some(listener) = listener {
457                listener.call(app);
458            }
459        }
460
461        drop(notification);
462
463        let notifier = field(app.get_mut(owner));
464        if notifier.notification_call_stack_depth.get() == 0
465            && notifier.reentrantly_removed_listeners > 0
466        {
467            notifier.compact_reentrantly_removed_listeners();
468        }
469    }
470
471    fn compact_reentrantly_removed_listeners(&mut self) {
472        // We really remove the listeners when all notifications are done.
473        let new_length = self.count - self.reentrantly_removed_listeners;
474        if new_length * 2 <= self.listeners.len() {
475            // As in remove_at, we only shrink the list when the real number of
476            // listeners is half the length of our list.
477            let mut new_listeners = vec![None; new_length];
478
479            let mut new_index = 0;
480            for listener in &self.listeners[..self.count] {
481                if listener.is_some() {
482                    new_listeners[new_index] = listener.clone();
483                    new_index += 1;
484                }
485            }
486
487            self.listeners = new_listeners;
488        } else {
489            // Otherwise we put all the null references at the end.
490            for i in 0..new_length {
491                if self.listeners[i].is_none() {
492                    // We swap this item with the next not null item.
493                    let mut swap_index = i + 1;
494                    while self.listeners[swap_index].is_none() {
495                        swap_index += 1;
496                    }
497                    self.listeners.swap(i, swap_index);
498                }
499            }
500        }
501
502        self.reentrantly_removed_listeners = 0;
503        self.count = new_length;
504    }
505}
506
507/// Dart's `try`/`finally` around each listener. Holds the depth by `Rc`, never
508/// a borrow of the notifier, so a panic still decrements.
509struct Notification {
510    depth: Rc<Cell<usize>>,
511}
512
513impl Notification {
514    fn begin(depth: Rc<Cell<usize>>) -> Notification {
515        depth.set(depth.get() + 1);
516        Notification { depth }
517    }
518}
519
520impl Drop for Notification {
521    fn drop(&mut self) {
522        self.depth.set(self.depth.get() - 1);
523    }
524}
525
526/// The object side of [`Listenable`]: an arena object that keeps listeners. Implementing it
527/// makes `Handle<Self>` a [`Listenable`]. Every [`ChangeNotifier`] is one; an object with its
528/// own listener bookkeeping (Flutter's `AnimationLocalListenersMixin`) implements it directly.
529///
530/// [`Listenable`] itself takes `&self` so that an erased handle type can implement it; a crate
531/// cannot implement that foreign trait for `Handle<ItsType>`, so it implements this one on
532/// the type instead.
533pub trait ListenableObject: Sized + 'static {
534    /// Register a closure to be called when the object notifies its listeners.
535    fn add_listener(self: Handle<Self>, app: &mut App, listener: Listener);
536
537    /// Remove a previously registered closure from the list of closures that the
538    /// object notifies.
539    fn remove_listener(self: Handle<Self>, app: &mut App, listener: &Listener);
540}
541
542impl<T: ChangeNotifier> ListenableObject for T {
543    fn add_listener(self: Handle<Self>, app: &mut App, listener: Listener) {
544        ChangeNotifier::will_add_listener(self, app);
545        app.get_mut(self)
546            .change_notifier_data_mut()
547            .add_listener(listener);
548    }
549
550    fn remove_listener(self: Handle<Self>, app: &mut App, listener: &Listener) {
551        // Allowed on a disposed instance (see `ChangeNotifierData::remove_listener`); here a
552        // disposed notifier may already have left the arena, so a stale handle is a no-op.
553        if !app.contains(self) {
554            return;
555        }
556        app.get_mut(self)
557            .change_notifier_data_mut()
558            .remove_listener(listener);
559        ChangeNotifier::did_remove_listener(self, app);
560    }
561}
562
563impl<T: ListenableObject> Listenable for Handle<T> {
564    fn add_listener(&self, app: &mut App, listener: Listener) {
565        T::add_listener(*self, app, listener);
566    }
567
568    fn remove_listener(&self, app: &mut App, listener: &Listener) {
569        T::remove_listener(*self, app, listener);
570    }
571}
572
573/// A class that can be mixed in that provides a change notification API using
574/// [`Listener`] for notifications.
575///
576/// It is O(1) for adding listeners and O(N) for removing listeners and
577/// dispatching notifications (where N is the number of listeners).
578///
579/// Hold [`ChangeNotifierData`] as a field and implement this trait. Then
580/// [`Handle::notify_listeners`] is inherent — no extra import at the call site.
581///
582/// See also:
583///
584///  * [`ValueNotifier`], which is a [`ChangeNotifier`] that wraps a single
585///    value.
586pub trait ChangeNotifier: Sized + 'static {
587    fn change_notifier_data(&self) -> &ChangeNotifierData;
588
589    fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData;
590
591    /// Runs before the listener is stored. Dart overrides of `addListener` that
592    /// work before `super.addListener` go here.
593    fn will_add_listener(self: Handle<Self>, app: &mut App) {
594        let _ = app;
595    }
596
597    /// Runs after the listener is removed. Dart overrides of `removeListener` that
598    /// work after `super.removeListener` go here.
599    fn did_remove_listener(self: Handle<Self>, app: &mut App) {
600        let _ = app;
601    }
602}
603
604impl ChangeNotifier for ChangeNotifierData {
605    fn change_notifier_data(&self) -> &ChangeNotifierData {
606        self
607    }
608
609    fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
610        self
611    }
612}
613
614impl<T: ChangeNotifier> Handle<T> {
615    /// Call all the registered listeners.
616    ///
617    /// Call this method whenever the object changes, to notify any clients the
618    /// object may have changed. Listeners that are added during this iteration
619    /// will not be visited. Listeners that are removed during this iteration will
620    /// not be visited after they are removed.
621    ///
622    /// This method must not be called after [`ChangeNotifierData::dispose`] has been
623    /// called.
624    ///
625    /// Surprising behavior can result when reentrantly removing a listener (e.g.
626    /// in response to a notification) that has been registered multiple times.
627    /// See the discussion at [`ChangeNotifierData::remove_listener`].
628    pub fn notify_listeners(self, app: &mut App) {
629        ChangeNotifierData::notify_listeners(app, self, T::change_notifier_data_mut);
630    }
631}
632
633/// A [`ChangeNotifier`] that holds a single value.
634///
635/// When [`value`] is replaced with a new value that is **not equal** to the old
636/// value as evaluated by `PartialEq`, this type notifies its listeners.
637///
638/// ## Limitations
639///
640/// Notifications are triggered based on **equality**, not on mutations within
641/// the value itself. As a result, changes to mutable values that do not affect
642/// their equality will not cause listeners to be notified.
643///
644/// Because of this behavior, [`ValueNotifier`] is best used with immutable data
645/// types. For mutable data types, consider holding a [`ChangeNotifierData`]
646/// directly and calling [`Handle::notify_listeners`] when changes occur.
647///
648/// [`value`]: ValueListenable::value
649#[derive(Debug, Default)]
650pub struct ValueNotifier<T> {
651    change_notifier: ChangeNotifierData,
652    value: T,
653}
654
655impl<T> ValueNotifier<T> {
656    /// Creates a [`ChangeNotifier`] that wraps this value.
657    pub fn new(value: T) -> ValueNotifier<T> {
658        ValueNotifier {
659            change_notifier: ChangeNotifierData::new(),
660            value,
661        }
662    }
663
664    /// The wrapped value. Prefer [`ValueListenable::value`] on the handle when
665    /// you do not already have a borrow of the slot.
666    pub fn value(&self) -> &T {
667        &self.value
668    }
669
670    /// Discards any resources used by the object.
671    pub fn dispose(&mut self) {
672        self.change_notifier.dispose();
673    }
674}
675
676impl<T: 'static> ChangeNotifier for ValueNotifier<T> {
677    fn change_notifier_data(&self) -> &ChangeNotifierData {
678        &self.change_notifier
679    }
680
681    fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
682        &mut self.change_notifier
683    }
684}
685
686impl<T: PartialEq + 'static> Handle<ValueNotifier<T>> {
687    /// Replaces the current value, notifying listeners when it differs.
688    ///
689    /// Dart's setter takes only the value. This takes [`App`] because notifying
690    /// does: a listener may assign back through this setter.
691    pub fn set_value(self, app: &mut App, new_value: T) {
692        if app.get(self).value == new_value {
693            return;
694        }
695        app.get_mut(self).value = new_value;
696        self.notify_listeners(app);
697    }
698}
699
700impl<T: 'static> ValueListenable<T> for Handle<ValueNotifier<T>> {
701    fn value<'a>(&'a self, app: &'a App) -> &'a T {
702        app.get(*self).value()
703    }
704}
705
706#[cfg(test)]
707mod tests {
708    use std::cell::RefCell;
709    use std::panic::{AssertUnwindSafe, catch_unwind};
710
711    use super::*;
712    use crate::app_cell::AppCell;
713
714    type Log = Rc<RefCell<Vec<String>>>;
715
716    fn fired(log: &Log) -> Vec<String> {
717        log.borrow().clone()
718    }
719
720    #[derive(Default)]
721    struct TestNotifier {
722        change_notifier: ChangeNotifierData,
723    }
724
725    impl ChangeNotifier for TestNotifier {
726        fn change_notifier_data(&self) -> &ChangeNotifierData {
727            &self.change_notifier
728        }
729
730        fn change_notifier_data_mut(&mut self) -> &mut ChangeNotifierData {
731            &mut self.change_notifier
732        }
733    }
734
735    fn recording(log: &Log, name: &'static str) -> Listener {
736        let log = Rc::clone(log);
737        Listener::new(move |_app| log.borrow_mut().push(name.to_string()))
738    }
739
740    fn add(app: &mut App, host: Handle<TestNotifier>, listener: Listener) {
741        host.add_listener(app, listener);
742    }
743
744    fn remove(app: &mut App, host: Handle<TestNotifier>, listener: &Listener) {
745        host.remove_listener(app, listener);
746    }
747
748    fn notify(app: &mut App, host: Handle<TestNotifier>) {
749        host.notify_listeners(app);
750    }
751
752    fn silence_panic_hook() -> Box<dyn Fn(&std::panic::PanicHookInfo<'_>) + Send + Sync + 'static> {
753        let hook = std::panic::take_hook();
754        std::panic::set_hook(Box::new(|_| {}));
755        hook
756    }
757
758    /// `change_notifier_test.dart`: "ChangeNotifier" — order, duplicates, extra remove.
759    #[test]
760    fn listeners_are_called_in_the_order_they_were_added() {
761        let cell = AppCell::new();
762        let mut app = cell.borrow_mut();
763        let log = Log::default();
764        let host = app.create(TestNotifier::default());
765
766        let listener = recording(&log, "listener");
767        add(&mut app, host, listener.clone());
768        add(&mut app, host, listener.clone());
769        notify(&mut app, host);
770        assert_eq!(fired(&log), ["listener", "listener"]);
771        log.borrow_mut().clear();
772
773        remove(&mut app, host, &listener);
774        notify(&mut app, host);
775        assert_eq!(fired(&log), ["listener"]);
776        log.borrow_mut().clear();
777
778        remove(&mut app, host, &listener);
779        notify(&mut app, host);
780        assert_eq!(fired(&log), Vec::<String>::new());
781        log.borrow_mut().clear();
782
783        remove(&mut app, host, &listener);
784        notify(&mut app, host);
785        assert_eq!(fired(&log), Vec::<String>::new());
786        log.borrow_mut().clear();
787
788        add(&mut app, host, listener.clone());
789        notify(&mut app, host);
790        assert_eq!(fired(&log), ["listener"]);
791        log.borrow_mut().clear();
792
793        let listener1 = recording(&log, "listener1");
794        add(&mut app, host, listener1.clone());
795        notify(&mut app, host);
796        assert_eq!(fired(&log), ["listener", "listener1"]);
797        log.borrow_mut().clear();
798
799        let listener2 = recording(&log, "listener2");
800        add(&mut app, host, listener2.clone());
801        notify(&mut app, host);
802        assert_eq!(fired(&log), ["listener", "listener1", "listener2"]);
803        log.borrow_mut().clear();
804
805        remove(&mut app, host, &listener1);
806        notify(&mut app, host);
807        assert_eq!(fired(&log), ["listener", "listener2"]);
808        log.borrow_mut().clear();
809
810        add(&mut app, host, listener1.clone());
811        notify(&mut app, host);
812        assert_eq!(fired(&log), ["listener", "listener2", "listener1"]);
813    }
814
815    /// `change_notifier_test.dart`: "ChangeNotifier with mutating listener".
816    #[test]
817    fn a_listener_mutates_the_notifier_dispatching_to_it() {
818        let cell = AppCell::new();
819        let mut app = cell.borrow_mut();
820        let log = Log::default();
821        let host = app.create(TestNotifier::default());
822
823        let listener1 = recording(&log, "listener1");
824        let listener3 = recording(&log, "listener3");
825        let listener4 = recording(&log, "listener4");
826        let listener2 = {
827            let log = Rc::clone(&log);
828            let (one, three, four) = (listener1.clone(), listener3.clone(), listener4.clone());
829            Listener::new(move |app: &mut App| {
830                log.borrow_mut().push("listener2".to_string());
831                let notifier = &mut app.get_mut(host).change_notifier;
832                notifier.remove_listener(&one);
833                notifier.remove_listener(&three);
834                notifier.add_listener(four.clone());
835            })
836        };
837
838        add(&mut app, host, listener1);
839        add(&mut app, host, listener2);
840        add(&mut app, host, listener3);
841
842        notify(&mut app, host);
843        assert_eq!(fired(&log), ["listener1", "listener2"]);
844
845        log.borrow_mut().clear();
846        notify(&mut app, host);
847        assert_eq!(fired(&log), ["listener2", "listener4"]);
848
849        log.borrow_mut().clear();
850        notify(&mut app, host);
851        assert_eq!(fired(&log), ["listener2", "listener4", "listener4"]);
852    }
853
854    /// `change_notifier_test.dart`: "During notifyListeners, a listener was added and removed immediately".
855    #[test]
856    fn a_listener_added_and_removed_immediately_during_notify_is_not_called() {
857        let cell = AppCell::new();
858        let mut app = cell.borrow_mut();
859        let log = Log::default();
860        let host = app.create(TestNotifier::default());
861
862        let listener2 = recording(&log, "listener2");
863        let listener3 = recording(&log, "listener3");
864        let listener1 = {
865            let log = Rc::clone(&log);
866            let two = listener2.clone();
867            let three = listener3.clone();
868            Listener::new(move |app: &mut App| {
869                log.borrow_mut().push("listener1".to_string());
870                let notifier = &mut app.get_mut(host).change_notifier;
871                notifier.add_listener(two.clone());
872                notifier.remove_listener(&two);
873                notifier.add_listener(three.clone());
874            })
875        };
876
877        add(&mut app, host, listener1);
878        notify(&mut app, host);
879        assert_eq!(fired(&log), ["listener1"]);
880    }
881
882    /// `change_notifier_test.dart`: self-removing listener in the middle still notifies all.
883    #[test]
884    fn a_self_removing_listener_in_the_middle_still_notifies_all() {
885        let cell = AppCell::new();
886        let mut app = cell.borrow_mut();
887        let log = Log::default();
888        let host = app.create(TestNotifier::default());
889
890        let listener1 = recording(&log, "listener1");
891        let self_removing_slot: Rc<RefCell<Option<Listener>>> = Rc::new(RefCell::new(None));
892        let self_removing = {
893            let log = Rc::clone(&log);
894            let slot = Rc::clone(&self_removing_slot);
895            Listener::new(move |app: &mut App| {
896                log.borrow_mut().push("selfRemovingListener".to_string());
897                let me = slot.borrow().clone().unwrap();
898                app.get_mut(host).change_notifier.remove_listener(&me);
899            })
900        };
901        *self_removing_slot.borrow_mut() = Some(self_removing.clone());
902
903        add(&mut app, host, listener1.clone());
904        add(&mut app, host, self_removing);
905        add(&mut app, host, listener1);
906
907        notify(&mut app, host);
908        assert_eq!(
909            fired(&log),
910            ["listener1", "selfRemovingListener", "listener1"]
911        );
912    }
913
914    /// `change_notifier_test.dart`: first listener removes itself, still notifies the rest.
915    #[test]
916    fn the_first_listener_removing_itself_still_notifies_the_rest() {
917        let cell = AppCell::new();
918        let mut app = cell.borrow_mut();
919        let log = Log::default();
920        let host = app.create(TestNotifier::default());
921
922        let self_removing_slot: Rc<RefCell<Option<Listener>>> = Rc::new(RefCell::new(None));
923        let self_removing = {
924            let log = Rc::clone(&log);
925            let slot = Rc::clone(&self_removing_slot);
926            Listener::new(move |app: &mut App| {
927                log.borrow_mut().push("selfRemovingListener".to_string());
928                let me = slot.borrow().clone().unwrap();
929                app.get_mut(host).change_notifier.remove_listener(&me);
930            })
931        };
932        *self_removing_slot.borrow_mut() = Some(self_removing.clone());
933        let listener1 = recording(&log, "listener1");
934
935        add(&mut app, host, self_removing);
936        add(&mut app, host, listener1);
937
938        notify(&mut app, host);
939        assert_eq!(fired(&log), ["selfRemovingListener", "listener1"]);
940    }
941
942    #[test]
943    fn only_a_handle_to_the_same_closure_compares_equal() {
944        let log = Log::default();
945        let listener = recording(&log, "a");
946
947        assert_eq!(listener, listener.clone());
948        assert_ne!(listener, recording(&log, "a"));
949    }
950
951    #[test]
952    fn a_rebuilt_handle_method_listener_matches_like_a_dart_tear_off() {
953        struct Host;
954        fn hook(_this: Handle<Host>, _app: &mut App) {}
955        fn other_hook(_this: Handle<Host>, _app: &mut App) {}
956
957        let cell = AppCell::new();
958        let mut app = cell.borrow_mut();
959        let a = app.create(Host);
960        let b = app.create(Host);
961
962        assert_eq!(
963            Listener::handle_method(a, hook),
964            Listener::handle_method(a, hook)
965        );
966        assert_ne!(
967            Listener::handle_method(a, hook),
968            Listener::handle_method(b, hook)
969        );
970        assert_ne!(
971            Listener::handle_method(a, hook),
972            Listener::handle_method(a, other_hook)
973        );
974        assert_ne!(Listener::handle_method(a, hook), Listener::new(|_app| {}));
975    }
976
977    #[test]
978    fn a_handle_method_listener_calls_the_function_with_its_handle() {
979        struct Host;
980        thread_local! {
981            static CALLED_WITH: Cell<Option<HandleId>> = const { Cell::new(None) };
982        }
983        fn hook(this: Handle<Host>, _app: &mut App) {
984            CALLED_WITH.with(|called| called.set(Some(this.id())));
985        }
986
987        let cell = AppCell::new();
988        let mut app = cell.borrow_mut();
989        let host = app.create(Host);
990        Listener::handle_method(host, hook).call(&mut app);
991        assert_eq!(CALLED_WITH.with(Cell::get), Some(host.id()));
992    }
993
994    /// `remove_at` shifts in place while survivors fill more than half the
995    /// buffer, and reallocates once they fit in half of it.
996    #[test]
997    fn removing_keeps_order_across_both_shrink_branches() {
998        for first in 0..8 {
999            let cell = AppCell::new();
1000            let mut app = cell.borrow_mut();
1001            let log = Log::default();
1002            let host = app.create(TestNotifier::default());
1003
1004            let names = ["0", "1", "2", "3", "4", "5", "6", "7"];
1005            let listeners: Vec<Listener> = names.iter().map(|name| recording(&log, name)).collect();
1006            for listener in &listeners {
1007                add(&mut app, host, listener.clone());
1008            }
1009
1010            let mut survivors: Vec<String> = names.iter().map(|n| n.to_string()).collect();
1011            let mut saw_realloc = false;
1012
1013            for step in 0..8 {
1014                let index = (first + step) % 8;
1015                remove(&mut app, host, &listeners[index]);
1016                survivors.retain(|s| s != names[index]);
1017                saw_realloc |= app.get(host).change_notifier.listeners.len() < 8;
1018
1019                log.borrow_mut().clear();
1020                notify(&mut app, host);
1021                assert_eq!(fired(&log), survivors, "after removing {index}");
1022                assert_eq!(app.get(host).change_notifier.count, survivors.len());
1023            }
1024
1025            assert!(saw_realloc, "the shrink branch never ran");
1026        }
1027    }
1028
1029    /// `change_notifier_test.dart`: "Cannot use a disposed ChangeNotifier except for remove listener".
1030    #[test]
1031    fn dispose_drops_every_listener_but_leaves_removal_callable() {
1032        let cell = AppCell::new();
1033        let mut app = cell.borrow_mut();
1034        let log = Log::default();
1035        let host = app.create(TestNotifier::default());
1036
1037        let listener = recording(&log, "a");
1038        add(&mut app, host, listener.clone());
1039        app.get_mut(host).change_notifier.dispose();
1040
1041        assert!(!app.get(host).change_notifier.has_listeners());
1042        app.get_mut(host).change_notifier.remove_listener(&listener);
1043        assert_eq!(fired(&log), Vec::<String>::new());
1044    }
1045
1046    #[test]
1047    #[cfg(debug_assertions)]
1048    #[should_panic(expected = "was used after being disposed")]
1049    fn adding_a_listener_after_dispose_panics_in_debug() {
1050        let mut notifier = ChangeNotifierData::new();
1051        notifier.dispose();
1052        notifier.add_listener(recording(&Log::default(), "a"));
1053    }
1054
1055    #[test]
1056    #[cfg(debug_assertions)]
1057    #[should_panic(expected = "was used after being disposed")]
1058    fn notifying_after_dispose_panics_in_debug() {
1059        let cell = AppCell::new();
1060        let mut app = cell.borrow_mut();
1061        let host = app.create(TestNotifier::default());
1062        app.get_mut(host).change_notifier.dispose();
1063        notify(&mut app, host);
1064    }
1065
1066    #[test]
1067    #[cfg(debug_assertions)]
1068    #[should_panic(expected = "was used after being disposed")]
1069    fn disposing_twice_panics_in_debug() {
1070        let mut notifier = ChangeNotifierData::new();
1071        notifier.dispose();
1072        notifier.dispose();
1073    }
1074
1075    /// `change_notifier_test.dart`: "Can check hasListener on a disposed ChangeNotifier".
1076    #[test]
1077    fn has_listeners_is_false_after_dispose() {
1078        let cell = AppCell::new();
1079        let mut app = cell.borrow_mut();
1080        let counter = app.create(ValueNotifier::new(0i32));
1081        counter.add_listener(&mut app, recording(&Log::default(), "a"));
1082        assert!(app.get(counter).change_notifier.has_listeners());
1083        app.get_mut(counter).dispose();
1084        assert!(!app.get(counter).change_notifier.has_listeners());
1085    }
1086
1087    /// `change_notifier_test.dart`: "Value notifier".
1088    #[test]
1089    fn value_notifier_notifies_only_when_the_value_differs() {
1090        let cell = AppCell::new();
1091        let mut app = cell.borrow_mut();
1092        let log = Log::default();
1093        let notifier = app.create(ValueNotifier::new(2.0));
1094
1095        notifier.add_listener(&mut app, {
1096            let log = Rc::clone(&log);
1097            Listener::new(move |app: &mut App| {
1098                log.borrow_mut().push(app.get(notifier).value().to_string());
1099            })
1100        });
1101
1102        notifier.set_value(&mut app, 3.0);
1103        assert_eq!(fired(&log), ["3"]);
1104        log.borrow_mut().clear();
1105
1106        notifier.set_value(&mut app, 3.0);
1107        assert_eq!(fired(&log), Vec::<String>::new());
1108    }
1109
1110    /// `change_notifier_test.dart`: "hasListeners".
1111    #[test]
1112    fn has_listeners() {
1113        let cell = AppCell::new();
1114        let mut app = cell.borrow_mut();
1115        let notifier = app.create(ValueNotifier::new(true));
1116        assert!(!app.get(notifier).change_notifier.has_listeners());
1117
1118        let test1 = recording(&Log::default(), "test1");
1119        let test2 = recording(&Log::default(), "test2");
1120
1121        notifier.add_listener(&mut app, test1.clone());
1122        assert!(app.get(notifier).change_notifier.has_listeners());
1123        notifier.add_listener(&mut app, test1.clone());
1124        assert!(app.get(notifier).change_notifier.has_listeners());
1125        notifier.remove_listener(&mut app, &test1);
1126        assert!(app.get(notifier).change_notifier.has_listeners());
1127        notifier.remove_listener(&mut app, &test1);
1128        assert!(!app.get(notifier).change_notifier.has_listeners());
1129        notifier.add_listener(&mut app, test1.clone());
1130        assert!(app.get(notifier).change_notifier.has_listeners());
1131        notifier.add_listener(&mut app, test2.clone());
1132        assert!(app.get(notifier).change_notifier.has_listeners());
1133        notifier.remove_listener(&mut app, &test1);
1134        assert!(app.get(notifier).change_notifier.has_listeners());
1135        notifier.remove_listener(&mut app, &test2);
1136        assert!(!app.get(notifier).change_notifier.has_listeners());
1137    }
1138
1139    /// `change_notifier_test.dart`: "Calling debugAssertNotDisposed works as intended".
1140    #[test]
1141    fn debug_assert_not_disposed_returns_true_while_live() {
1142        let notifier = ChangeNotifierData::new();
1143        assert!(ChangeNotifierData::debug_assert_not_disposed(&notifier));
1144    }
1145
1146    #[test]
1147    #[cfg(debug_assertions)]
1148    #[should_panic(expected = "was used after being disposed")]
1149    fn debug_assert_not_disposed_panics_after_dispose() {
1150        let mut notifier = ChangeNotifierData::new();
1151        notifier.dispose();
1152        ChangeNotifierData::debug_assert_not_disposed(&notifier);
1153    }
1154
1155    /// `change_notifier_test.dart`: "notifyListener can be called recursively".
1156    #[test]
1157    fn a_listener_assigns_through_its_owners_setter_and_the_dispatch_recurses() {
1158        let cell = AppCell::new();
1159        let mut app = cell.borrow_mut();
1160        let log = Log::default();
1161        let counter = app.create(ValueNotifier::new(0i32));
1162
1163        let listener1 = {
1164            let log = Rc::clone(&log);
1165            Listener::new(move |app: &mut App| {
1166                log.borrow_mut().push("listener1".to_string());
1167                if *app.get(counter).value() < 0 {
1168                    counter.set_value(app, 0);
1169                }
1170            })
1171        };
1172        counter.add_listener(&mut app, listener1);
1173
1174        counter.notify_listeners(&mut app);
1175        assert_eq!(fired(&log), ["listener1"]);
1176
1177        log.borrow_mut().clear();
1178        counter.set_value(&mut app, 3);
1179        assert_eq!(fired(&log), ["listener1"]);
1180
1181        log.borrow_mut().clear();
1182        counter.set_value(&mut app, -2);
1183        assert_eq!(fired(&log), ["listener1", "listener1"]);
1184        assert_eq!(*app.get(counter).value(), 0);
1185    }
1186
1187    /// `change_notifier_test.dart`: "Remove Listeners while notifying on a list which will not resize".
1188    #[test]
1189    fn removing_listeners_while_notifying_on_a_list_which_will_not_resize() {
1190        let cell = AppCell::new();
1191        let mut app = cell.borrow_mut();
1192        let log = Log::default();
1193        let host = app.create(TestNotifier::default());
1194
1195        let names = [
1196            "listener0",
1197            "listener1",
1198            "listener2",
1199            "listener3",
1200            "listener4",
1201            "listener5",
1202            "listener6",
1203            "listener7",
1204            "listener8",
1205            "listener9",
1206            "listener10",
1207            "listener11",
1208        ];
1209        let listeners: Vec<Listener> = names.iter().map(|name| recording(&log, name)).collect();
1210
1211        let auto_remove = {
1212            let to_remove = [
1213                listeners[1].clone(),
1214                listeners[3].clone(),
1215                listeners[4].clone(),
1216            ];
1217            let auto_remove_slot: Rc<RefCell<Option<Listener>>> = Rc::new(RefCell::new(None));
1218            let slot = Rc::clone(&auto_remove_slot);
1219            let listener = Listener::new(move |app: &mut App| {
1220                let notifier = &mut app.get_mut(host).change_notifier;
1221                notifier.remove_listener(&to_remove[0]);
1222                notifier.remove_listener(&to_remove[1]);
1223                notifier.remove_listener(&to_remove[2]);
1224                let me = slot.borrow().clone().unwrap();
1225                notifier.remove_listener(&me);
1226            });
1227            *auto_remove_slot.borrow_mut() = Some(listener.clone());
1228            listener
1229        };
1230
1231        add(&mut app, host, auto_remove);
1232        for listener in &listeners {
1233            add(&mut app, host, listener.clone());
1234        }
1235
1236        let remaining = [
1237            "listener0",
1238            "listener2",
1239            "listener5",
1240            "listener6",
1241            "listener7",
1242            "listener8",
1243            "listener9",
1244            "listener10",
1245            "listener11",
1246        ];
1247
1248        notify(&mut app, host);
1249        assert_eq!(fired(&log), remaining);
1250
1251        log.borrow_mut().clear();
1252        notify(&mut app, host);
1253        assert_eq!(fired(&log), remaining);
1254
1255        for i in [0, 2, 5, 6, 7, 8, 9, 10, 11] {
1256            remove(&mut app, host, &listeners[i]);
1257        }
1258        log.borrow_mut().clear();
1259        notify(&mut app, host);
1260        assert_eq!(fired(&log), Vec::<String>::new());
1261    }
1262
1263    /// `change_notifier_test.dart`: "ChangeNotifier can not dispose in callback".
1264    #[test]
1265    #[cfg(debug_assertions)]
1266    fn dispose_during_notify_panics_and_does_not_finish_the_callback() {
1267        let cell = AppCell::new();
1268        let mut app = cell.borrow_mut();
1269        let host = app.create(TestNotifier::default());
1270        let finished = Rc::new(Cell::new(false));
1271        let finished_flag = Rc::clone(&finished);
1272        add(
1273            &mut app,
1274            host,
1275            Listener::new(move |app: &mut App| {
1276                app.get_mut(host).change_notifier.dispose();
1277                finished_flag.set(true);
1278            }),
1279        );
1280
1281        let hook = silence_panic_hook();
1282        let panicked = catch_unwind(AssertUnwindSafe(|| {
1283            notify(&mut app, host);
1284        }))
1285        .is_err();
1286        std::panic::set_hook(hook);
1287
1288        assert!(panicked);
1289        assert!(!finished.get());
1290        app.get_mut(host).change_notifier.dispose();
1291    }
1292
1293    #[test]
1294    fn a_bare_change_notifier_handle_can_notify() {
1295        let cell = AppCell::new();
1296        let mut app = cell.borrow_mut();
1297        let log = Log::default();
1298        let on_undo = app.create(ChangeNotifierData::new());
1299
1300        on_undo.add_listener(&mut app, recording(&log, "undo"));
1301        on_undo.notify_listeners(&mut app);
1302
1303        assert_eq!(fired(&log), ["undo"]);
1304    }
1305
1306    #[test]
1307    fn a_panicking_listener_leaves_the_notifier_usable() {
1308        let cell = AppCell::new();
1309        let mut app = cell.borrow_mut();
1310        let log = Log::default();
1311        let host = app.create(TestNotifier::default());
1312
1313        let l0 = recording(&log, "l0");
1314        let l2 = recording(&log, "l2");
1315        let l3 = Listener::new(|_app: &mut App| panic!("bad listener"));
1316        let l1 = {
1317            let log = Rc::clone(&log);
1318            let (zero, two) = (l0.clone(), l2.clone());
1319            Listener::new(move |app: &mut App| {
1320                log.borrow_mut().push("l1".to_string());
1321                let notifier = &mut app.get_mut(host).change_notifier;
1322                notifier.remove_listener(&zero);
1323                notifier.remove_listener(&two);
1324            })
1325        };
1326
1327        add(&mut app, host, l0);
1328        add(&mut app, host, l1);
1329        add(&mut app, host, l2);
1330        add(&mut app, host, l3.clone());
1331
1332        let hook = silence_panic_hook();
1333        let panicked = catch_unwind(AssertUnwindSafe(|| {
1334            notify(&mut app, host);
1335        }))
1336        .is_err();
1337        std::panic::set_hook(hook);
1338
1339        assert!(panicked);
1340        assert_eq!(fired(&log), ["l0", "l1"]);
1341        assert_eq!(
1342            app.get(host)
1343                .change_notifier
1344                .notification_call_stack_depth
1345                .get(),
1346            0
1347        );
1348        assert_eq!(app.get(host).change_notifier.count, 4);
1349
1350        app.get_mut(host).change_notifier.remove_listener(&l3);
1351        assert_eq!(app.get(host).change_notifier.count, 3);
1352        assert_eq!(
1353            app.get(host).change_notifier.reentrantly_removed_listeners,
1354            2
1355        );
1356
1357        log.borrow_mut().clear();
1358        notify(&mut app, host);
1359        assert_eq!(fired(&log), ["l1"]);
1360        assert_eq!(app.get(host).change_notifier.count, 1);
1361        assert_eq!(
1362            app.get(host).change_notifier.reentrantly_removed_listeners,
1363            0
1364        );
1365
1366        log.borrow_mut().clear();
1367        notify(&mut app, host);
1368        assert_eq!(fired(&log), ["l1"]);
1369    }
1370
1371    #[test]
1372    fn a_panic_inside_a_nested_dispatch_still_pairs_the_depth_guards() {
1373        let cell = AppCell::new();
1374        let mut app = cell.borrow_mut();
1375        let log = Log::default();
1376        let host = app.create(TestNotifier::default());
1377        let inner_host = app.create(TestNotifier::default());
1378
1379        let doomed = recording(&log, "doomed");
1380        add(
1381            &mut app,
1382            inner_host,
1383            Listener::new(|_app: &mut App| panic!("inner listener")),
1384        );
1385
1386        let outer = {
1387            let log = Rc::clone(&log);
1388            let doomed = doomed.clone();
1389            Listener::new(move |app: &mut App| {
1390                log.borrow_mut().push("outer".to_string());
1391                app.get_mut(host).change_notifier.remove_listener(&doomed);
1392                notify(app, inner_host);
1393            })
1394        };
1395
1396        add(&mut app, host, outer);
1397        add(&mut app, host, doomed);
1398
1399        let hook = silence_panic_hook();
1400        let panicked = catch_unwind(AssertUnwindSafe(|| {
1401            notify(&mut app, host);
1402        }))
1403        .is_err();
1404        std::panic::set_hook(hook);
1405
1406        assert!(panicked);
1407        assert_eq!(
1408            app.get(host)
1409                .change_notifier
1410                .notification_call_stack_depth
1411                .get(),
1412            0
1413        );
1414        assert_eq!(
1415            app.get(inner_host)
1416                .change_notifier
1417                .notification_call_stack_depth
1418                .get(),
1419            0
1420        );
1421
1422        let survivor = app.get(host).change_notifier.listeners[0].clone().unwrap();
1423        remove(&mut app, host, &survivor);
1424        log.borrow_mut().clear();
1425        notify(&mut app, host);
1426        assert_eq!(
1427            app.get(host).change_notifier.reentrantly_removed_listeners,
1428            0
1429        );
1430        assert!(!app.get(host).change_notifier.has_listeners());
1431    }
1432
1433    #[test]
1434    fn dispose_clears_the_compaction_owed_by_a_panicked_dispatch() {
1435        let cell = AppCell::new();
1436        let mut app = cell.borrow_mut();
1437        let log = Log::default();
1438        let host = app.create(TestNotifier::default());
1439
1440        let doomed = recording(&log, "doomed");
1441        let saboteur = {
1442            let doomed = doomed.clone();
1443            Listener::new(move |app: &mut App| {
1444                app.get_mut(host).change_notifier.remove_listener(&doomed);
1445                panic!("bad listener");
1446            })
1447        };
1448        add(&mut app, host, saboteur);
1449        add(&mut app, host, doomed);
1450
1451        let hook = silence_panic_hook();
1452        let _ = catch_unwind(AssertUnwindSafe(|| {
1453            notify(&mut app, host);
1454        }));
1455        std::panic::set_hook(hook);
1456
1457        assert_eq!(
1458            app.get(host).change_notifier.reentrantly_removed_listeners,
1459            1
1460        );
1461
1462        app.get_mut(host).change_notifier.dispose();
1463        assert_eq!(
1464            app.get(host).change_notifier.reentrantly_removed_listeners,
1465            0
1466        );
1467        assert_eq!(app.get(host).change_notifier.count, 0);
1468    }
1469
1470    #[test]
1471    #[cfg(not(debug_assertions))]
1472    fn a_disposed_notifier_reused_in_release_does_not_underflow() {
1473        let cell = AppCell::new();
1474        let mut app = cell.borrow_mut();
1475        let log = Log::default();
1476        let host = app.create(TestNotifier::default());
1477
1478        let doomed = recording(&log, "doomed");
1479        let saboteur = {
1480            let doomed = doomed.clone();
1481            Listener::new(move |app: &mut App| {
1482                app.get_mut(host).change_notifier.remove_listener(&doomed);
1483                panic!("bad listener");
1484            })
1485        };
1486        add(&mut app, host, saboteur);
1487        add(&mut app, host, doomed);
1488
1489        let hook = silence_panic_hook();
1490        let _ = catch_unwind(AssertUnwindSafe(|| {
1491            notify(&mut app, host);
1492        }));
1493        std::panic::set_hook(hook);
1494
1495        app.get_mut(host).change_notifier.dispose();
1496        add(&mut app, host, recording(&log, "after"));
1497
1498        log.borrow_mut().clear();
1499        notify(&mut app, host);
1500        assert_eq!(fired(&log), ["after"]);
1501        assert_eq!(app.get(host).change_notifier.count, 1);
1502    }
1503}