Skip to main content

rx_rust/operators/conditional_boolean/
amb.rs

1use crate::delegate_disposal;
2use crate::disposable::{Disposable, DisposableExt};
3use crate::utils::mutable::{Mutable, MutableExt, MutableHelper};
4use crate::utils::types::{MaybeSend, Shared, WeakShared};
5use crate::{
6    observable::{Observable, Subscription},
7    observer::{Flow, Observer, Termination},
8};
9use educe::Educe;
10
11/// Given two or more source Observables, emit all of the items from only the first of these Observables to emit an item or notification.
12/// See <https://reactivex.io/documentation/operators/amb.html>
13///
14/// # Examples
15/// ```rust
16/// use rx_rust::{
17///     observable::ObservableExt,
18///     observer::Termination,
19///     operators::{
20///         conditional_boolean::amb::Amb,
21///         creating::from_iter::FromIter,
22///     },
23/// };
24///
25/// let mut values = Vec::new();
26/// let mut terminations = Vec::new();
27///
28/// let observable = Amb::new([
29///     FromIter::new(vec![1, 2]),
30///     FromIter::new(vec![3, 4]),
31/// ]);
32/// observable.subscribe_with_callback(
33///     |value| values.push(value),
34///     |termination| terminations.push(termination),
35/// );
36///
37/// assert_eq!(values, vec![1, 2]);
38/// assert_eq!(terminations, vec![Termination::Completed]);
39/// ```
40#[derive(Educe)]
41#[educe(Debug, Clone)]
42pub struct Amb<I> {
43    sources: I,
44}
45
46impl<I> Amb<I> {
47    pub fn new(sources: I) -> Self {
48        Self { sources }
49    }
50}
51
52delegate_disposal!(
53    Disposal<D>,
54    AmbDisposal<D>,
55    where D: Disposable
56);
57
58impl<'or, T, E, OE, I> Observable<'or, T, E> for Amb<I>
59where
60    I: IntoIterator<Item = OE>,
61    OE: Observable<'or, T, E>,
62    OE::D: MaybeSend + 'or,
63{
64    type D = Disposal<OE::D>;
65
66    fn subscribe(self, observer: impl Observer<T, E> + MaybeSend + 'or) -> Subscription<Self::D> {
67        let sources = self.sources.into_iter();
68        let minimum_source_count = sources.size_hint().0;
69        let observer = Shared::new(Mutable::new(Some(observer)));
70        let context = Shared::new(Mutable::new(AmbState::Racing(Vec::with_capacity(
71            minimum_source_count,
72        ))));
73
74        let mut has_sources = false;
75        for source in sources {
76            has_sources = true;
77            let Some(key) = reserve_subscription_slot(&context) else {
78                break;
79            };
80            let amb_observer = AmbObserver(AmbObserverState::Racing {
81                observer: observer.clone(),
82                context: Shared::downgrade(&context),
83                key,
84            });
85            let subscription = source.subscribe(amb_observer);
86            if !store_subscription(&context, key, subscription) {
87                break;
88            }
89        }
90
91        if !has_sources {
92            // Without a source, no one can ever win the race, so it completes right away.
93            // The observer is taken out of its slot so that it is notified outside the lock.
94            let observer = observer
95                .take_value()
96                .expect("a new amb must retain its downstream observer");
97            observer.on_termination(Termination::Completed);
98        }
99
100        AmbDisposal(context).into_subscription()
101    }
102}
103
104enum AmbState<D: Disposable> {
105    Racing(Vec<Option<Subscription<D>>>),
106    Won {
107        key: usize,
108        subscription: Option<Subscription<D>>,
109    },
110    Stopped,
111}
112
113fn reserve_subscription_slot<D>(context: &Mutable<AmbState<D>>) -> Option<usize>
114where
115    D: Disposable,
116{
117    context.with_mut(|state| match state {
118        AmbState::Racing(subscriptions) => {
119            let key = subscriptions.len();
120            subscriptions.push(None);
121            Some(key)
122        }
123        AmbState::Won { .. } | AmbState::Stopped => None,
124    })
125}
126
127fn store_subscription<D>(
128    context: &Mutable<AmbState<D>>,
129    key: usize,
130    subscription: Subscription<D>,
131) -> bool
132where
133    D: Disposable,
134{
135    // Wrapped in an `Option` so that the branches which do not store the subscription leave it to
136    // be dropped outside the lock.
137    let mut subscription = Some(subscription);
138    let (keep_subscribing, replaced) = context.with_mut(|state| match state {
139        // The race is still open: the subscription belongs in the reserved slot.
140        AmbState::Racing(subscriptions) => {
141            let slot = subscriptions
142                .get_mut(key)
143                .expect("a racing source must retain its subscription slot");
144            let replaced = std::mem::replace(slot, subscription.take());
145            (true, replaced)
146        }
147        // This source won while it was still being subscribed to: it owns the winning slot.
148        AmbState::Won {
149            key: winner_key,
150            subscription: winner_subscription,
151        } if *winner_key == key => {
152            let replaced = std::mem::replace(winner_subscription, subscription.take());
153            (false, replaced)
154        }
155        // Another source won, or the race is over: this subscription is not needed anymore.
156        AmbState::Won { .. } | AmbState::Stopped => (false, None),
157    });
158    // Both slots are empty until they are written above, so nothing is ever replaced. This is
159    // asserted outside the lock so that a failing assertion cannot poison it.
160    debug_assert!(
161        replaced.is_none(),
162        "a subscription slot is written only once"
163    );
164    drop((replaced, subscription)); // Drop a late losing subscription outside the lock.
165    keep_subscribing
166}
167
168fn try_win<D, OR>(
169    context: &Mutable<AmbState<D>>,
170    shared_observer: &Mutable<Option<OR>>,
171    key: usize,
172) -> Option<OR>
173where
174    D: Disposable,
175{
176    let losing_subscriptions = context.with_mut(|state| {
177        if !matches!(state, AmbState::Racing(_)) {
178            return None;
179        }
180
181        let AmbState::Racing(mut subscriptions) = std::mem::replace(state, AmbState::Stopped)
182        else {
183            unreachable!()
184        };
185        let winner_subscription = subscriptions
186            .get_mut(key)
187            .expect("a racing source must retain its subscription slot")
188            .take();
189        *state = AmbState::Won {
190            key,
191            subscription: winner_subscription,
192        };
193        Some(subscriptions)
194    })?;
195    // The `Racing` -> `Won` transition above elects a single winner, so the downstream observer is
196    // taken after the context lock is released: no other source can reach this point.
197    let observer = shared_observer
198        .take_value()
199        .expect("a racing amb must retain its downstream observer");
200    drop(losing_subscriptions); // Dispose losing subscriptions outside the lock.
201    Some(observer)
202}
203
204enum AmbObserverState<D: Disposable, OR> {
205    Racing {
206        observer: Shared<Mutable<Option<OR>>>,
207        context: WeakShared<Mutable<AmbState<D>>>,
208        key: usize,
209    },
210    Won(OR),
211    Lost,
212}
213
214struct AmbObserver<D: Disposable, OR>(AmbObserverState<D, OR>);
215
216impl<T, E, D, OR> Observer<T, E> for AmbObserver<D, OR>
217where
218    D: Disposable,
219    OR: Observer<T, E>,
220{
221    fn on_next(&mut self, value: T) -> Flow {
222        match &mut self.0 {
223            AmbObserverState::Won(observer) => {
224                return observer.on_next(value);
225            }
226            // This source lost the race, so nothing it emits is wanted anymore.
227            AmbObserverState::Lost => return Flow::Stop,
228            AmbObserverState::Racing { .. } => {}
229        }
230
231        let AmbObserverState::Racing {
232            observer: shared_observer,
233            context,
234            key,
235        } = std::mem::replace(&mut self.0, AmbObserverState::Lost)
236        else {
237            unreachable!()
238        };
239        let Some(shared_context) = context.upgrade() else {
240            return Flow::Stop;
241        };
242        let Some(mut observer) = try_win(&shared_context, &shared_observer, key) else {
243            return Flow::Stop;
244        };
245        drop(shared_context);
246        drop(shared_observer);
247
248        let flow = observer.on_next(value);
249        self.0 = AmbObserverState::Won(observer);
250        flow
251    }
252
253    fn on_termination(self, termination: Termination<E>) {
254        match self.0 {
255            AmbObserverState::Won(observer) => observer.on_termination(termination),
256            AmbObserverState::Lost => {}
257            AmbObserverState::Racing {
258                observer: shared_observer,
259                context,
260                key,
261            } => {
262                let Some(shared_context) = context.upgrade() else {
263                    return;
264                };
265                let Some(observer) = try_win(&shared_context, &shared_observer, key) else {
266                    return;
267                };
268                drop(shared_context);
269                drop(shared_observer);
270                observer.on_termination(termination);
271            }
272        }
273    }
274}
275
276struct AmbDisposal<D: Disposable>(Shared<Mutable<AmbState<D>>>);
277
278impl<D> Disposable for AmbDisposal<D>
279where
280    D: Disposable,
281{
282    fn dispose(self) {
283        let old_state = self.0.replace_value(AmbState::Stopped);
284        drop(old_state); // Dispose the remaining subscriptions outside the lock.
285    }
286}