#![allow(dead_code)]
use super::keyed_disposable::KeyedDisposable;
use super::shared_delivery_queue::SharedDeliveryQueue;
use super::synchronize_safe::synchronize_safe_shared;
use crate::cache::ChangeSet;
use ferroui_reactive::disposables::SingleAssignmentDisposable;
use ferroui_reactive::disposables::SingleAssignmentDisposableValue;
use ferroui_reactive::ObservableExt;
use ferroui_system::{IDisposable, IObservable, IObserver, ObservableError};
use std::cell::Cell;
use std::hash::Hash;
use std::rc::{Rc, Weak};
pub(crate) trait ParentSubscriptionHandler<P, K, C, O> {
fn parent_on_next(&self, owner: &CacheParentSubscription<P, K, C, O>, changes: ChangeSet<P, K>);
fn child_on_next(&self, child: C, parent_key: &K);
fn emit_changes(&self, observer: &Rc<dyn IObserver<O>>);
}
pub(crate) struct CacheParentSubscription<P, K, C, O> {
this: Weak<CacheParentSubscription<P, K, C, O>>,
child_subscriptions: KeyedDisposable<K>,
parent_subscription: SingleAssignmentDisposableValue,
queue: Rc<SharedDeliveryQueue>,
observer: Rc<dyn IObserver<O>>,
subscription_counter: Cell<isize>,
frame_depth: Cell<usize>,
is_completed: Cell<bool>,
has_terminated: Cell<bool>,
disposed: Cell<bool>,
handler: Box<dyn ParentSubscriptionHandler<P, K, C, O>>,
}
impl<P: Clone + 'static, K: Eq + Hash + Clone + 'static, C: 'static, O: 'static> CacheParentSubscription<P, K, C, O> {
pub(crate) fn new(observer: Rc<dyn IObserver<O>>, handler: Box<dyn ParentSubscriptionHandler<P, K, C, O>>) -> Rc<Self> {
Rc::new_cyclic(|this| Self {
this: this.clone(),
child_subscriptions: KeyedDisposable::new(),
parent_subscription: SingleAssignmentDisposableValue::new(),
queue: SharedDeliveryQueue::new(),
observer,
subscription_counter: Cell::new(1),
frame_depth: Cell::new(0),
is_completed: Cell::new(false),
has_terminated: Cell::new(false),
disposed: Cell::new(false),
handler,
})
}
pub(crate) fn handler(&self) -> &dyn ParentSubscriptionHandler<P, K, C, O> {
&*self.handler
}
pub(crate) fn queue(&self) -> &Rc<SharedDeliveryQueue> {
&self.queue
}
fn strong(&self) -> Rc<Self> {
self.this.upgrade().expect("the subscription is alive while it is used")
}
pub(crate) fn add_child_subscription(&self, observable: Rc<dyn IObservable<C>>, parent_key: K) {
self.subscription_counter.set(self.subscription_counter.get() + 1);
let container = SingleAssignmentDisposable::new();
self.child_subscriptions.add(parent_key.clone(), Some(container.clone()));
let (finally_owner, next_owner, error_owner, completed_owner) = (self.strong(), self.strong(), self.strong(), self.strong());
let (next_key, completed_key) = (parent_key.clone(), parent_key);
let subscription = observable.finally(move || finally_owner.check_completed()).subscribe_all(
move |value| next_owner.deliver_child(value, &next_key),
move |error| error_owner.terminal_error(error),
move || completed_owner.complete_child(&completed_key),
);
container.set_disposable(Some(subscription));
}
pub(crate) fn remove_child_subscription(&self, parent_key: &K) {
self.child_subscriptions.remove(parent_key);
}
pub(crate) fn create_parent_subscription(&self, source: Rc<dyn IObservable<ChangeSet<P, K>>>) {
let (next_owner, error_owner, completed_owner) = (self.strong(), self.strong(), self.strong());
let subscription = synchronize_safe_shared(source, &self.queue).subscribe_all(
move |changes| next_owner.deliver_parent(changes),
move |error| error_owner.terminal_error(error),
move || completed_owner.complete_parent(),
);
self.parent_subscription.set_disposable(Some(subscription));
}
pub(crate) fn make_child_observable<T: 'static>(&self, observable: Rc<dyn IObservable<T>>) -> Rc<dyn IObservable<T>> {
synchronize_safe_shared(observable, &self.queue)
}
fn deliver_parent(&self, changes: ChangeSet<P, K>) {
self.begin_frame();
self.handler.parent_on_next(self, changes);
self.end_frame();
}
fn deliver_child(&self, child: C, parent_key: &K) {
self.begin_frame();
self.handler.child_on_next(child, parent_key);
self.end_frame();
}
fn complete_parent(&self) {
self.begin_frame();
self.check_completed();
self.end_frame();
}
fn complete_child(&self, parent_key: &K) {
self.begin_frame();
self.remove_child_subscription(parent_key);
self.end_frame();
}
fn begin_frame(&self) {
self.frame_depth.set(self.frame_depth.get() + 1);
}
fn end_frame(&self) {
let depth = self.frame_depth.get() - 1;
self.frame_depth.set(depth);
if depth != 0 {
return;
}
self.handler.emit_changes(&self.observer);
if self.is_completed.get() && !self.has_terminated.get() {
self.has_terminated.set(true);
self.observer.on_completed();
}
}
fn terminal_error(&self, error: ObservableError) {
self.has_terminated.set(true);
self.observer.on_error(error);
}
fn check_completed(&self) {
let counter = self.subscription_counter.get() - 1;
self.subscription_counter.set(counter);
if counter == 0 {
self.is_completed.set(true);
}
debug_assert!(counter >= 0, "Should never be negative");
}
}
impl<P, K: Eq + Hash + Clone, C, O> IDisposable for CacheParentSubscription<P, K, C, O> {
fn dispose(&self) {
if self.disposed.replace(true) {
return;
}
self.queue.dispose();
self.parent_subscription.dispose();
self.child_subscriptions.dispose();
}
}