use crate::utils::serialized_delivery::{DeliveryStopped, UpdateOutcome};
use crate::{
delegate_disposal,
disposable::{
Disposable, DisposableExt, boxed_disposal::BoxedDisposal, chain_disposal::ChainDisposal,
},
observable::Subscription,
observer::{Flow, Observer, Termination},
utils::{
pending_events::EventBatch,
serialized_delivery::{SerializedDelivery, WeakSerializedDelivery},
subscribe_with_auto_dispose_on_termination::is_auto_dispose_on_termination_observer,
types::MaybeSend,
},
};
use educe::Educe;
delegate_disposal!(
Disposal<'or_sub, D>,
ChainDisposal<BoxedDisposal<'or_sub>, D>,
where D: Disposable
);
pub type OwningDisposal<'or_sub> = BoxedDisposal<'or_sub>;
pub fn subscribe_with_context<'or_sub, T, E, OR, D, M, F>(
observer: OR,
model: M,
builder: F,
) -> Subscription<Disposal<'or_sub, D>>
where
T: MaybeSend + 'or_sub,
E: MaybeSend + 'or_sub,
OR: MaybeSend + 'or_sub,
D: Disposable,
M: MaybeSend + 'or_sub,
F: FnOnce(SubscriptionContext<T, E, OR, M>) -> Subscription<D>,
{
debug_assert_observer_compatibility::<OR>();
let context = SubscriptionContext::<T, E, OR, M, ()>::new(observer, model);
let disposal = context.disposal();
let subscription = builder(context);
subscription.preceded_by(disposal.into_boxed()).map_into()
}
pub fn subscribe_with_context_owning_source<'or_sub, T, E, OR, D, M, F>(
observer: OR,
model: M,
builder: F,
) -> Subscription<OwningDisposal<'or_sub>>
where
T: MaybeSend + 'or_sub,
E: MaybeSend + 'or_sub,
OR: Observer<T, E> + MaybeSend + 'or_sub,
D: Disposable + MaybeSend + 'or_sub,
M: MaybeSend + 'or_sub,
F: FnOnce(SubscriptionContext<T, E, OR, M, D>) -> Subscription<D>,
{
debug_assert_observer_compatibility::<OR>();
let context = SubscriptionContext::<T, E, OR, M, D>::new(observer, model);
let disposal = context.disposal();
let subscription = builder(context.clone());
let previous_subscription = context.install_source_subscription(subscription);
debug_assert!(
!matches!(previous_subscription, Ok(Some(_))),
"the source subscription is installed only once"
);
disposal.into_boxed().into_subscription()
}
#[derive(Educe)]
#[educe(Debug)]
struct ContextResources<M, D: Disposable> {
model: M,
source_subscription: Option<Subscription<D>>,
}
type ContextDelivery<T, E, OR, M, D> = SerializedDelivery<T, E, OR, ContextResources<M, D>>;
type WeakContextDelivery<T, E, OR, M, D> = WeakSerializedDelivery<T, E, OR, ContextResources<M, D>>;
#[derive(Educe)]
#[educe(Debug, Clone)]
pub struct SubscriptionContext<T, E, OR, M, D: Disposable = ()> {
delivery: ContextDelivery<T, E, OR, M, D>,
}
impl<T, E, OR, M, D: Disposable> SubscriptionContext<T, E, OR, M, D> {
fn new(observer: OR, model: M) -> Self {
Self {
delivery: SerializedDelivery::idle(
observer,
ContextResources {
model,
source_subscription: None,
},
),
}
}
fn disposal(&self) -> SubscriptionContextDisposal<T, E, OR, M, D> {
SubscriptionContextDisposal {
delivery: self.delivery.clone(),
}
}
pub fn downgrade(&self) -> WeakSubscriptionContext<T, E, OR, M, D> {
WeakSubscriptionContext {
delivery: self.delivery.downgrade(),
}
}
}
impl<T, E, OR, M, D> SubscriptionContext<T, E, OR, M, D>
where
OR: Observer<T, E>,
D: Disposable,
{
pub fn update<R, DO, const EVENTS_DECIDED: bool>(
&self,
callback: impl FnOnce(&mut M) -> UpdateOutcome<T, E, R, DO, EVENTS_DECIDED>,
) -> Result<R, DeliveryStopped> {
self.delivery
.update(|resources| callback(&mut resources.model))
}
pub fn update_flow<DO, const EVENTS_DECIDED: bool>(
&self,
callback: impl FnOnce(&mut M) -> UpdateOutcome<T, E, (), DO, EVENTS_DECIDED>,
) -> Flow {
match self
.delivery
.update_with_flow(|resources| callback(&mut resources.model))
{
Ok(((), flow)) => flow,
Err(DeliveryStopped) => Flow::Stop,
}
}
fn install_source_subscription(
&self,
subscription: Subscription<D>,
) -> Result<Option<Subscription<D>>, DeliveryStopped> {
self.delivery.update(|resources| {
UpdateOutcome::new(resources.source_subscription.replace(subscription))
})
}
pub fn send_next(&self, value: T) -> Flow {
self.send(EventBatch::Next(value))
}
pub fn send_termination(&self, termination: Termination<E>) {
let _ = self.send(EventBatch::Termination(termination));
}
pub fn send(&self, events: EventBatch<T, E>) -> Flow {
self.delivery.send(events)
}
}
struct SubscriptionContextDisposal<T, E, OR, M, D: Disposable> {
delivery: ContextDelivery<T, E, OR, M, D>,
}
impl<T, E, OR, M, D: Disposable> Disposable for SubscriptionContextDisposal<T, E, OR, M, D> {
fn dispose(self) {
self.delivery.stop();
}
}
#[derive(Educe)]
#[educe(Debug, Clone)]
pub struct WeakSubscriptionContext<T, E, OR, M, D: Disposable = ()> {
delivery: WeakContextDelivery<T, E, OR, M, D>,
}
impl<T, E, OR, M, D: Disposable> WeakSubscriptionContext<T, E, OR, M, D> {
pub fn upgrade(&self) -> Option<SubscriptionContext<T, E, OR, M, D>> {
self.delivery
.upgrade()
.map(|delivery| SubscriptionContext { delivery })
}
}
fn debug_assert_observer_compatibility<OR>() {
debug_assert!(
!is_auto_dispose_on_termination_observer::<OR>(),
"Do not combine subscribe_with_auto_dispose_on_termination with a context subscription. \
Using subscribe_with_context_owning_source handles \"auto dispose on termination\"."
);
}