Skip to main content

ferroui_dynamic_data/cache/
observable_cache.rs

1//! `ObservableCache<TObject, TKey>` (`Cache/ObservableCache.cs`), with `IConnectableCache`,
2//! `IObservableCache` (`Cache/IConnectableCache.cs`, `Cache/IObservableCache.cs`).
3
4use super::internal::{KeySelector, ReaderWriter};
5use super::{Change, ChangeReason, ChangeSet, ICacheUpdater, IChangeSet, ISourceUpdater, ObservableCacheEx};
6use crate::internal::DeliveryQueue;
7use ferroui_reactive::disposables::Disposable;
8use ferroui_reactive::subjects::{BehaviorSubject, Subject, SubjectBase};
9use ferroui_reactive::{Observable, ObservableExt};
10use ferroui_system::collections::generic::Dictionary;
11use ferroui_system::{IDisposable, IObservable, IObserver, ObservableError};
12use std::cell::{Cell, RefCell};
13use std::hash::Hash;
14use std::rc::{Rc, Weak};
15
16/// A cache that can be observed (`IConnectableCache<TObject, TKey>`).
17pub trait IConnectableCache<T, K> {
18    /// The number of items, now and whenever it changes (`CountChanged`).
19    fn count_changed(&self) -> Rc<dyn IObservable<usize>>;
20    /// The items as additions, then the changes, of the items that `predicate` accepts; empty
21    /// change sets are left out unless `suppress_empty_change_sets` is false (`Connect`).
22    fn connect_with(&self, predicate: Option<Rc<dyn Fn(&T) -> bool>>, suppress_empty_change_sets: bool) -> Rc<dyn IObservable<ChangeSet<T, K>>>;
23    /// The changes before they are applied, while the items are as they were (`Preview`).
24    fn preview_with(&self, predicate: Option<Rc<dyn Fn(&T) -> bool>>) -> Rc<dyn IObservable<ChangeSet<T, K>>>;
25    /// The changes of the item of `key` (`Watch`).
26    fn watch(&self, key: K) -> Rc<dyn IObservable<Change<T, K>>>;
27
28    /// The items and their changes (`Connect()`).
29    fn connect(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
30        self.connect_with(None, true)
31    }
32
33    /// The items that `predicate` accepts and their changes (`Connect(Func<TObject, bool>)`).
34    fn connect_filtered(&self, predicate: impl Fn(&T) -> bool + 'static) -> Rc<dyn IObservable<ChangeSet<T, K>>>
35    where
36        Self: Sized,
37    {
38        self.connect_with(Some(Rc::new(predicate)), true)
39    }
40
41    /// The changes before they are applied (`Preview()`).
42    fn preview(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
43        self.preview_with(None)
44    }
45}
46
47/// A cache that can be observed and read (`IObservableCache<TObject, TKey>`).
48pub trait IObservableCache<T, K>: IConnectableCache<T, K> + IDisposable {
49    /// The number of items (`Count`).
50    fn count(&self) -> usize;
51    /// The items (`Items`).
52    fn items(&self) -> Vec<T>;
53    /// The keys (`Keys`).
54    fn keys(&self) -> Vec<K>;
55    /// The keys and items (`KeyValues`).
56    fn key_values(&self) -> Dictionary<K, T>;
57    /// The item of `key` (`Lookup`).
58    fn lookup(&self, key: &K) -> Option<T>;
59}
60
61/// A notification of the queue of the cache (`CacheUpdate`).
62#[derive(Clone)]
63struct CacheUpdate<T, K> {
64    changes: Option<ChangeSet<T, K>>,
65    count: usize,
66    version: u64,
67}
68
69/// The notifications that a suspension holds back (`SuspensionTracker`).
70struct SuspensionTracker<T, K> {
71    are_notifications_suspended: Rc<BehaviorSubject<bool>>,
72    pending_changes: Vec<Change<T, K>>,
73    count_suspend_count: usize,
74    notify_suspend_count: usize,
75    disposed: bool,
76}
77
78impl<T: Clone + 'static, K: Clone + 'static> SuspensionTracker<T, K> {
79    fn new() -> Self {
80        Self { are_notifications_suspended: BehaviorSubject::new(false), pending_changes: Vec::new(), count_suspend_count: 0, notify_suspend_count: 0, disposed: false }
81    }
82
83    fn is_count_suspended(&self) -> bool {
84        self.count_suspend_count > 0
85    }
86
87    fn are_notifications_suspended(&self) -> bool {
88        self.notify_suspend_count > 0
89    }
90}
91
92struct Inner<T, K> {
93    this: Weak<Inner<T, K>>,
94    changes: Rc<Subject<ChangeSet<T, K>>>,
95    changes_preview: Rc<Subject<ChangeSet<T, K>>>,
96    count_changed: RefCell<Option<Rc<Subject<usize>>>>,
97    suspension: RefCell<Option<SuspensionTracker<T, K>>>,
98    reader_writer: ReaderWriter<T, K>,
99    notifications: Rc<DeliveryQueue<CacheUpdate<T, K>>>,
100    is_edit_in_progress: RefCell<Option<Rc<BehaviorSubject<bool>>>>,
101    edit_level: Cell<usize>,
102    current_version: Cell<u64>,
103    current_delivery_version: Cell<u64>,
104    loader: RefCell<Option<Rc<dyn IDisposable>>>,
105    disposed: Cell<bool>,
106}
107
108/// A cache whose edits and contents can be observed (`ObservableCache<TObject, TKey>`): the core
109/// of `SourceCache`, and the cache of `AsObservableCache`.
110pub struct ObservableCache<T, K> {
111    inner: Rc<Inner<T, K>>,
112}
113
114impl<T, K> Clone for ObservableCache<T, K> {
115    fn clone(&self) -> Self {
116        Self { inner: self.inner.clone() }
117    }
118}
119
120impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> ObservableCache<T, K> {
121    fn create(key_selector: Option<KeySelector<T, K>>) -> Self {
122        let inner = Rc::new_cyclic(|this: &Weak<Inner<T, K>>| Inner {
123            this: this.clone(),
124            changes: Subject::new(),
125            changes_preview: Subject::new(),
126            count_changed: RefCell::new(None),
127            suspension: RefCell::new(None),
128            reader_writer: ReaderWriter::new(key_selector),
129            notifications: DeliveryQueue::new(Rc::new(CacheUpdateObserver { cache: this.clone() })),
130            is_edit_in_progress: RefCell::new(None),
131            edit_level: Cell::new(0),
132            current_version: Cell::new(0),
133            current_delivery_version: Cell::new(0),
134            loader: RefCell::new(None),
135            disposed: Cell::new(false),
136        });
137        Self { inner }
138    }
139
140    /// An empty cache whose items are keyed by `key_selector`
141    /// (`ObservableCache(Func<TObject, TKey>)`).
142    pub fn new(key_selector: impl Fn(&T) -> K + 'static) -> Self {
143        Self::create(Some(Rc::new(key_selector)))
144    }
145
146    /// A cache without a key selector, for edits that give the keys
147    /// (`ObservableCache(Func<TObject, TKey>? = null)`).
148    pub fn without_key_selector() -> Self {
149        Self::create(None)
150    }
151
152    /// A cache that applies the change sets of `source` (`ObservableCache(IObservable<IChangeSet>)`).
153    pub fn from_source<C: IChangeSet<T, K>>(source: Rc<dyn IObservable<C>>) -> Self {
154        let cache = Self::create(None);
155        let inner = Rc::downgrade(&cache.inner);
156        let (error_target, completed_target) = (inner.clone(), inner.clone());
157        let loader = source.change_sets().subscribe_all(
158            move |change_set| {
159                if let Some(inner) = inner.upgrade() {
160                    let notifications = inner.notifications.acquire();
161                    let preview_handler = inner.preview_handler();
162                    let changes = inner.reader_writer.write_changes(&change_set, preview_handler.as_ref().map(|h| h as &dyn Fn(&ChangeSet<T, K>)), inner.changes.has_observers());
163                    let version = inner.current_version.get() + 1;
164                    inner.current_version.set(version);
165                    notifications.enqueue_next(CacheUpdate { changes: Some(changes), count: inner.reader_writer.count(), version });
166                }
167            },
168            move |error| {
169                if let Some(inner) = error_target.upgrade() {
170                    inner.notify_error(error);
171                }
172            },
173            move || {
174                if let Some(inner) = completed_target.upgrade() {
175                    inner.notify_completed();
176                }
177            },
178        );
179        *cache.inner.loader.borrow_mut() = Some(loader);
180        cache
181    }
182
183    /// The key selector of the cache.
184    pub fn key_selector(&self) -> Option<&KeySelector<T, K>> {
185        self.inner.reader_writer.key_selector()
186    }
187
188    /// Runs an edit with the cache's key selector (`UpdateFromSource`); the changes are delivered
189    /// once the outermost edit ends.
190    pub fn update_from_source(&self, update: impl FnOnce(&mut dyn ISourceUpdater<T, K>)) {
191        let mut update = Some(update);
192        self.inner.update(&mut |updater| (update.take().expect("an edit runs once"))(updater));
193    }
194
195    /// Runs an edit that gives the keys (`UpdateFromIntermediate`).
196    pub fn update_from_intermediate(&self, update: impl FnOnce(&mut dyn ICacheUpdater<T, K>)) {
197        let mut update = Some(update);
198        self.inner.update(&mut |updater| (update.take().expect("an edit runs once"))(updater));
199    }
200
201    /// The items as additions, those that `filter` accepts (`GetInitialUpdates`).
202    pub(crate) fn get_initial_updates(&self, filter: Option<&dyn Fn(&T) -> bool>) -> ChangeSet<T, K> {
203        self.inner.reader_writer.get_initial_updates(filter)
204    }
205
206    /// Holds back the notifications of count changes until the result is disposed
207    /// (`SuspendCount`).
208    pub fn suspend_count(&self) -> Rc<dyn IDisposable> {
209        self.inner.suspension_tracker(|tracker| tracker.count_suspend_count += 1);
210        let inner = self.inner.clone();
211        Disposable::create(move || inner.resume_count())
212    }
213
214    /// Holds back the notifications of changes until the result is disposed; the changes made
215    /// meanwhile are delivered as one change set (`SuspendNotifications`).
216    pub fn suspend_notifications(&self) -> Rc<dyn IDisposable> {
217        let suspended = self.inner.suspension_tracker(|tracker| {
218            tracker.notify_suspend_count += 1;
219            (tracker.notify_suspend_count == 1 && !tracker.disposed).then(|| tracker.are_notifications_suspended.clone())
220        });
221        if let Some(subject) = suspended {
222            subject.on_next(true);
223        }
224        let inner = self.inner.clone();
225        Disposable::create(move || inner.resume_notifications())
226    }
227}
228
229impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> Inner<T, K> {
230    fn preview_handler(&self) -> Option<impl Fn(&ChangeSet<T, K>)> {
231        if !self.changes_preview.has_observers() {
232            return None;
233        }
234        let this = self.this.clone();
235        Some(move |changes: &ChangeSet<T, K>| {
236            if let Some(inner) = this.upgrade() {
237                if changes.count() != 0 && !inner.notifications.is_terminated() {
238                    inner.changes_preview.on_next(changes.clone());
239                }
240            }
241        })
242    }
243
244    fn edit_in_progress_subject(&self) -> Option<Rc<BehaviorSubject<bool>>> {
245        self.is_edit_in_progress.borrow().clone()
246    }
247
248    /// `UpdateFromSource` and `UpdateFromIntermediate`.
249    fn update(&self, update: &mut dyn FnMut(&mut dyn ISourceUpdater<T, K>)) {
250        let notifications = self.notifications.acquire();
251        let level = self.edit_level.get() + 1;
252        self.edit_level.set(level);
253        if level == 1 {
254            if let Some(subject) = self.edit_in_progress_subject() {
255                subject.on_next(true);
256            }
257        }
258        // An edit that panics still runs upstream's `finally`: the level comes down and, at the
259        // outermost edit, the edit is no longer in progress (no changes are enqueued: none were
260        // captured). The normal path below runs the same steps in upstream's order.
261        struct Unwind<'a, T: Clone + 'static, K: Eq + Hash + Clone + 'static> {
262            inner: &'a Inner<T, K>,
263            armed: bool,
264        }
265        impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> Drop for Unwind<'_, T, K> {
266            fn drop(&mut self) {
267                if self.armed {
268                    let level = self.inner.edit_level.get() - 1;
269                    self.inner.edit_level.set(level);
270                    if level == 0 {
271                        if let Some(subject) = self.inner.edit_in_progress_subject() {
272                            subject.on_next(false);
273                        }
274                    }
275                }
276            }
277        }
278        let mut unwind = Unwind { inner: self, armed: true };
279        let changes = if level == 1 {
280            let preview_handler = self.preview_handler();
281            Some(self.reader_writer.write(update, preview_handler.as_ref().map(|h| h as &dyn Fn(&ChangeSet<T, K>)), self.changes.has_observers()))
282        } else {
283            self.reader_writer.write_nested(update);
284            None
285        };
286        unwind.armed = false;
287        drop(unwind);
288        // `finally`: the level comes down, the changes are enqueued, then the edit is no longer
289        // in progress; the delivery follows when the scope of the queue ends.
290        let level = self.edit_level.get() - 1;
291        self.edit_level.set(level);
292        if let Some(changes) = changes {
293            if level == 0 {
294                let version = self.current_version.get() + 1;
295                self.current_version.set(version);
296                notifications.enqueue_next(CacheUpdate { changes: Some(changes), count: self.reader_writer.count(), version });
297            }
298        }
299        if level == 0 {
300            if let Some(subject) = self.edit_in_progress_subject() {
301                subject.on_next(false);
302            }
303        }
304        drop(notifications);
305    }
306
307    fn suspension_tracker<R>(&self, action: impl FnOnce(&mut SuspensionTracker<T, K>) -> R) -> R {
308        let mut tracker = self.suspension.borrow_mut();
309        action(tracker.get_or_insert_with(SuspensionTracker::new))
310    }
311
312    fn notify_completed(&self) {
313        self.notifications.acquire().enqueue_completed();
314    }
315
316    fn notify_error(&self, error: ObservableError) {
317        self.notifications.acquire().enqueue_error(error);
318    }
319
320    fn resume_count(&self) {
321        let notifications = self.notifications.acquire();
322        let resumed = self.suspension_tracker(|tracker| {
323            tracker.count_suspend_count -= 1;
324            tracker.count_suspend_count == 0
325        });
326        if resumed && self.count_changed.borrow().is_some() {
327            notifications.enqueue_next(CacheUpdate { changes: None, count: self.reader_writer.count(), version: 0 });
328        }
329    }
330
331    fn resume_notifications(&self) {
332        let notifications = self.notifications.acquire();
333        let (changes, emit_resume) = self.suspension_tracker(|tracker| {
334            tracker.notify_suspend_count -= 1;
335            if tracker.notify_suspend_count == 0 && !tracker.disposed {
336                let changes = (!tracker.pending_changes.is_empty()).then(|| ChangeSet::from(std::mem::take(&mut tracker.pending_changes)));
337                (changes, true)
338            } else {
339                (None, false)
340            }
341        });
342        if let Some(changes) = changes {
343            let version = self.current_version.get() + 1;
344            self.current_version.set(version);
345            notifications.enqueue_next(CacheUpdate { changes: Some(changes), count: self.reader_writer.count(), version });
346        }
347        if emit_resume {
348            let subject = self.suspension_tracker(|tracker| (!tracker.disposed).then(|| tracker.are_notifications_suspended.clone()));
349            if let Some(subject) = subject {
350                subject.on_next(false);
351            }
352        }
353    }
354
355    /// The changes of the subject, without those that a subscriber that connects now has in its
356    /// snapshot already: when notifications are pending, the ones up to the current version are
357    /// skipped (the version guard of `CreateConnectObservable` and `CreateWatchObservable`).
358    fn changes_after_snapshot(&self) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
359        let changes = self.changes.as_observable();
360        if !self.notifications.has_pending() {
361            return changes;
362        }
363        let snapshot_version = self.current_version.get();
364        let this = self.this.clone();
365        changes.skip_while(move |_| this.upgrade().is_some_and(|inner| inner.current_delivery_version.get() <= snapshot_version))
366    }
367
368    /// `CreateConnectObservable`.
369    fn create_connect_observable(&self, predicate: Option<Rc<dyn Fn(&T) -> bool>>, suppress_empty_change_sets: bool) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
370        let this = self.this.clone();
371        Observable::create(move |observer: Rc<dyn IObserver<ChangeSet<T, K>>>| {
372            let Some(inner) = this.upgrade() else { return Disposable::empty() };
373            let (snapshot_inner, snapshot_predicate) = (this.clone(), predicate.clone());
374            let initial = Observable::defer(move || {
375                let initial = snapshot_inner.upgrade().map(|inner| inner.reader_writer.get_initial_updates(snapshot_predicate.as_deref())).unwrap_or_default();
376                Observable::return_(initial)
377            });
378            let mut changes = initial.concat(inner.changes_after_snapshot());
379            if let Some(predicate) = &predicate {
380                let predicate = predicate.clone();
381                changes = changes.filter_with(move |item: &T| predicate(item), suppress_empty_change_sets);
382            } else if suppress_empty_change_sets {
383                changes = changes.not_empty();
384            }
385            changes.subscribe(observer)
386        })
387    }
388
389    /// `CreateWatchObservable`.
390    fn create_watch_observable(&self, key: K) -> Rc<dyn IObservable<Change<T, K>>> {
391        let this = self.this.clone();
392        Observable::create(move |observer: Rc<dyn IObserver<Change<T, K>>>| {
393            let Some(inner) = this.upgrade() else { return Disposable::empty() };
394            if let Some(initial) = inner.reader_writer.lookup(&key) {
395                observer.on_next(Change::new(ChangeReason::Add, key.clone(), initial));
396            }
397            let (completed, key) = (observer.clone(), key.clone());
398            inner.changes_after_snapshot().finally(move || completed.on_completed()).subscribe_next(move |changes: ChangeSet<T, K>| {
399                for change in &changes {
400                    if change.key() == &key {
401                        observer.on_next(change.clone());
402                    }
403                }
404            })
405        })
406    }
407
408    /// `CreateDeferredNotificationObservable`: a connection made while an edit or a suspension is
409    /// in progress waits for both to end.
410    fn create_deferred_notification_observable<V: Clone + 'static>(&self, factory: Rc<dyn Fn() -> Rc<dyn IObservable<V>>>) -> Rc<dyn IObservable<V>> {
411        let this = self.this.clone();
412        Observable::create(move |observer: Rc<dyn IObserver<V>>| {
413            let Some(inner) = this.upgrade() else { return Disposable::empty() };
414            let suspension_active = inner.suspension.borrow().is_some();
415            let edit_active = inner.is_edit_in_progress.borrow().is_some() || inner.edit_level.get() != 0;
416            let observable: Rc<dyn IObservable<V>> = match (suspension_active, edit_active) {
417                (false, false) => factory(),
418                (false, true) => {
419                    let (inner2, factory2, this2) = (inner.clone(), factory.clone(), this.clone());
420                    inner
421                        .edit_in_progress()
422                        .where_(|in_progress| !*in_progress)
423                        .take(1)
424                        .select_many(move |_| {
425                            let suspended = inner2.suspension.borrow().as_ref().is_some_and(SuspensionTracker::are_notifications_suspended);
426                            match suspended {
427                                true => fully_deferred(&this2, &factory2),
428                                false => factory2(),
429                            }
430                        })
431                }
432                (true, false) => {
433                    let (inner2, factory2, this2) = (inner.clone(), factory.clone(), this.clone());
434                    inner
435                        .notifications_suspended()
436                        .where_(|suspended| !*suspended)
437                        .take(1)
438                        .select_many(move |_| {
439                            let editing = inner2.is_edit_in_progress.borrow().as_ref().is_some_and(|subject| subject.value().unwrap_or(false));
440                            match editing {
441                                true => fully_deferred(&this2, &factory2),
442                                false => factory2(),
443                            }
444                        })
445                }
446                (true, true) => fully_deferred(&this, &factory),
447            };
448            observable.subscribe(observer)
449        })
450    }
451
452    /// The subject of edits in progress, made on first use (`_isEditInProgress`).
453    fn edit_in_progress(&self) -> Rc<dyn IObservable<bool>> {
454        let mut subject = self.is_edit_in_progress.borrow_mut();
455        subject.get_or_insert_with(|| BehaviorSubject::new(self.edit_level.get() != 0)).as_observable()
456    }
457
458    /// The subject of suspensions (`NotificationsSuspendedObservable`).
459    fn notifications_suspended(&self) -> Rc<dyn IObservable<bool>> {
460        self.suspension_tracker(|tracker| tracker.are_notifications_suspended.as_observable())
461    }
462}
463
464/// `CreateFullyDeferredConnection`: waits until no suspension and no edit is in progress.
465fn fully_deferred<T: Clone + 'static, K: Eq + Hash + Clone + 'static, V: Clone + 'static>(
466    this: &Weak<Inner<T, K>>,
467    factory: &Rc<dyn Fn() -> Rc<dyn IObservable<V>>>,
468) -> Rc<dyn IObservable<V>> {
469    let Some(inner) = this.upgrade() else { return Observable::empty() };
470    let factory = factory.clone();
471    inner
472        .notifications_suspended()
473        .combine_latest(inner.edit_in_progress(), |suspended, editing| suspended || editing)
474        .where_(|deferred| !*deferred)
475        .take(1)
476        .select(move |_| factory())
477        .switch()
478}
479
480/// The observer of the queue of the cache (`CacheUpdateObserver`): it delivers the changes and
481/// the count.
482struct CacheUpdateObserver<T, K> {
483    cache: Weak<Inner<T, K>>,
484}
485
486impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> IObserver<CacheUpdate<T, K>> for CacheUpdateObserver<T, K> {
487    fn on_next(&self, value: CacheUpdate<T, K>) {
488        let Some(cache) = self.cache.upgrade() else { return };
489        if let Some(changes) = value.changes {
490            cache.current_delivery_version.set(value.version);
491            let held_back = cache.suspension.borrow_mut().as_mut().is_some_and(|tracker| {
492                if tracker.are_notifications_suspended() {
493                    tracker.pending_changes.extend(changes.iter().cloned());
494                    true
495                } else {
496                    false
497                }
498            });
499            if !held_back {
500                cache.changes.on_next(changes);
501            }
502        }
503        let count_suspended = cache.suspension.borrow().as_ref().is_some_and(SuspensionTracker::is_count_suspended);
504        if !count_suspended {
505            let subject = cache.count_changed.borrow().clone();
506            if let Some(subject) = subject {
507                subject.on_next(value.count);
508            }
509        }
510    }
511
512    fn on_error(&self, error: ObservableError) {
513        let Some(cache) = self.cache.upgrade() else { return };
514        cache.changes_preview.on_error(error.clone());
515        cache.changes.on_error(error.clone());
516        if let Some(subject) = cache.edit_in_progress_subject() {
517            subject.on_error(error.clone());
518        }
519        let count = cache.count_changed.borrow().clone();
520        if let Some(subject) = count {
521            subject.on_error(error.clone());
522        }
523        let suspended = cache.suspension.borrow_mut().as_mut().map(|tracker| {
524            tracker.disposed = true;
525            tracker.are_notifications_suspended.clone()
526        });
527        if let Some(subject) = suspended {
528            subject.on_error(error);
529        }
530    }
531
532    fn on_completed(&self) {
533        let Some(cache) = self.cache.upgrade() else { return };
534        cache.changes.on_completed();
535        cache.changes_preview.on_completed();
536        if let Some(subject) = cache.edit_in_progress_subject() {
537            subject.on_completed();
538        }
539        let count = cache.count_changed.borrow().clone();
540        if let Some(subject) = count {
541            subject.on_completed();
542        }
543        let suspended = cache.suspension.borrow_mut().as_mut().map(|tracker| {
544            tracker.disposed = true;
545            tracker.are_notifications_suspended.clone()
546        });
547        if let Some(subject) = suspended {
548            subject.on_completed();
549        }
550    }
551}
552
553impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> IConnectableCache<T, K> for ObservableCache<T, K> {
554    fn count_changed(&self) -> Rc<dyn IObservable<usize>> {
555        let this = Rc::downgrade(&self.inner);
556        Observable::create(move |observer: Rc<dyn IObserver<usize>>| {
557            let Some(inner) = this.upgrade() else { return Disposable::empty() };
558            let subject = inner.count_changed.borrow_mut().get_or_insert_with(Subject::new).clone();
559            let mut count_changed = subject.as_observable();
560            if inner.notifications.has_pending() {
561                let snapshot_version = inner.current_version.get();
562                let weak = this.clone();
563                count_changed = count_changed.skip_while(move |_| weak.upgrade().is_some_and(|inner| inner.current_delivery_version.get() <= snapshot_version));
564            }
565            count_changed.start_with([inner.reader_writer.count()]).distinct_until_changed().subscribe(observer)
566        })
567    }
568
569    fn connect_with(&self, predicate: Option<Rc<dyn Fn(&T) -> bool>>, suppress_empty_change_sets: bool) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
570        let this = Rc::downgrade(&self.inner);
571        let factory: Rc<dyn Fn() -> Rc<dyn IObservable<ChangeSet<T, K>>>> = Rc::new(move || match this.upgrade() {
572            Some(inner) => inner.create_connect_observable(predicate.clone(), suppress_empty_change_sets),
573            None => Observable::empty(),
574        });
575        self.inner.create_deferred_notification_observable(factory)
576    }
577
578    fn preview_with(&self, predicate: Option<Rc<dyn Fn(&T) -> bool>>) -> Rc<dyn IObservable<ChangeSet<T, K>>> {
579        let preview = self.inner.changes_preview.as_observable();
580        match predicate {
581            None => preview,
582            Some(predicate) => preview.filter(move |item: &T| predicate(item)),
583        }
584    }
585
586    fn watch(&self, key: K) -> Rc<dyn IObservable<Change<T, K>>> {
587        let this = Rc::downgrade(&self.inner);
588        let factory: Rc<dyn Fn() -> Rc<dyn IObservable<Change<T, K>>>> = Rc::new(move || match this.upgrade() {
589            Some(inner) => inner.create_watch_observable(key.clone()),
590            None => Observable::empty(),
591        });
592        self.inner.create_deferred_notification_observable(factory)
593    }
594}
595
596impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> IObservableCache<T, K> for ObservableCache<T, K> {
597    fn count(&self) -> usize {
598        self.inner.reader_writer.count()
599    }
600
601    fn items(&self) -> Vec<T> {
602        self.inner.reader_writer.items()
603    }
604
605    fn keys(&self) -> Vec<K> {
606        self.inner.reader_writer.keys()
607    }
608
609    fn key_values(&self) -> Dictionary<K, T> {
610        self.inner.reader_writer.key_values()
611    }
612
613    fn lookup(&self, key: &K) -> Option<T> {
614        self.inner.reader_writer.lookup(key)
615    }
616}
617
618impl<T: Clone + 'static, K: Eq + Hash + Clone + 'static> IDisposable for ObservableCache<T, K> {
619    /// Ends the subscription to the source and completes the observers (`Dispose`).
620    fn dispose(&self) {
621        if self.inner.disposed.replace(true) {
622            return;
623        }
624        let loader = self.inner.loader.borrow_mut().take();
625        if let Some(loader) = loader {
626            loader.dispose();
627        }
628        self.inner.notify_completed();
629    }
630}