Skip to main content

rx_rust/operators/transforming/
buffer.rs

1use crate::utils::serialized_delivery::UpdateOutcome;
2use crate::utils::subscribe_with_context::{
3    self, SubscriptionContext, subscribe_with_context_owning_source,
4};
5use crate::utils::types::MaybeSend;
6use crate::{
7    disposable::Disposable,
8    observable::Observable,
9    observable::Subscription,
10    observer::{Flow, Observer, Termination},
11};
12use educe::Educe;
13
14/// Periodically gathers items from an Observable into bundles and emits these bundles as `Vec<T>`, when a `boundary` Observable emits an item.
15///
16/// Terminating the `boundary` terminates the outer Observable: completing it emits the pending
17/// bundle when non-empty and then completes, while an error from it discards the pending bundle
18/// and errors. Note that this differs from
19/// [`Window`](crate::operators::transforming::window::Window), where completing the `boundary`
20/// only stops rotation; a buffer has no consumer to release items to, so leaving it open after
21/// the `boundary` completed would accumulate items without bound.
22/// See <https://reactivex.io/documentation/operators/buffer.html>
23///
24/// # Relation to `Window`
25///
26/// Conceptually a buffer is a window whose items are collected into a `Vec`, that is
27/// `source.window(boundary).concat_map(|window| window.to_vec())`. It is implemented on its own
28/// rather than as that composition, because the two are not equivalent:
29///
30/// - Completing the `boundary` terminates a buffer, as described above, but only stops the
31///   rotation of a window.
32/// - Completing the `source` with an empty pending bundle emits nothing for a buffer, whereas the
33///   composition emits a trailing empty `Vec`, because collecting the empty open window yields
34///   the initial value.
35/// - [`Window`](crate::operators::transforming::window::Window) requires `E: Clone`, since an
36///   error is delivered to both the current window and the outer Observable. A buffer has no
37///   window to deliver to and so places no such bound on `E`.
38///
39/// The composition also pays for machinery a buffer does not need: a subject per window, and one
40/// queued action per item. Where the three points above do not matter, the equivalence holds; the
41/// `test_equivalent_to_window_and_collect` test in `tests/buffer.rs` pins it down, alongside two
42/// tests that pin down the divergences.
43///
44/// # Examples
45/// ```rust
46/// use rx_rust::{
47///     observable::ObservableExt,
48///     observer::{Observer, Termination},
49///     operators::transforming::buffer::Buffer,
50///     subject::publish_subject::PublishSubject,
51/// };
52/// use std::{convert::Infallible, sync::{Arc, Mutex}};
53///
54/// let values = Arc::new(Mutex::new(Vec::new()));
55/// let terminations = Arc::new(Mutex::new(Vec::new()));
56///
57/// let mut source: PublishSubject<'_, i32, Infallible> = PublishSubject::default();
58/// let mut boundary: PublishSubject<'_, (), Infallible> = PublishSubject::default();
59/// let values_observer = Arc::clone(&values);
60/// let terminations_observer = Arc::clone(&terminations);
61///
62/// let subscription = Buffer::new(source.clone(), boundary.clone()).subscribe_with_callback(
63///     move |value| values_observer.lock().unwrap().push(value),
64///     move |termination| terminations_observer
65///         .lock()
66///         .unwrap()
67///         .push(termination),
68/// );
69///
70/// source.on_next(1);
71/// source.on_next(2);
72/// boundary.on_next(());
73/// source.on_next(3);
74/// source.on_termination(Termination::Completed);
75/// drop(subscription);
76///
77/// assert_eq!(&*values.lock().unwrap(), &[vec![1, 2], vec![3]]);
78/// assert_eq!(
79///     &*terminations.lock().unwrap(),
80///     &[Termination::Completed]
81/// );
82/// ```
83#[derive(Educe)]
84#[educe(Debug, Clone)]
85pub struct Buffer<OE, OE1> {
86    source: OE,
87    boundary: OE1,
88}
89
90impl<OE, OE1> Buffer<OE, OE1> {
91    pub fn new<'or, T, E>(source: OE, boundary: OE1) -> Self
92    where
93        OE: Observable<'or, T, E>,
94        OE1: Observable<'or, (), E>,
95    {
96        Self { source, boundary }
97    }
98}
99
100impl<'or, T, E, OE, OE1> Observable<'or, Vec<T>, E> for Buffer<OE, OE1>
101where
102    T: MaybeSend + 'or,
103    E: MaybeSend + 'or,
104    OE: Observable<'or, T, E>,
105    OE::D: MaybeSend + 'or,
106    OE1: Observable<'or, (), E>,
107    OE1::D: MaybeSend + 'or,
108{
109    type D = subscribe_with_context::OwningDisposal<'or>;
110
111    fn subscribe(
112        self,
113        observer: impl Observer<Vec<T>, E> + MaybeSend + 'or,
114    ) -> Subscription<Self::D> {
115        subscribe_with_context_owning_source(observer, Vec::new(), |context| {
116            let subscription_1 = self.boundary.subscribe(BoundaryObserver(context.clone()));
117            let subscription_2 = self.source.subscribe(BufferObserver(context));
118            subscription_1.preceded_by_bound(subscription_2)
119        })
120    }
121}
122
123struct BufferObserver<T, E, OR, D: Disposable>(SubscriptionContext<Vec<T>, E, OR, Vec<T>, D>);
124
125impl<T, E, OR, D> Observer<T, E> for BufferObserver<T, E, OR, D>
126where
127    OR: Observer<Vec<T>, E>,
128    D: Disposable,
129{
130    fn on_next(&mut self, value: T) -> Flow {
131        self.0.update_flow(|values| {
132            values.push(value);
133            UpdateOutcome::empty()
134        })
135    }
136
137    fn on_termination(self, termination: Termination<E>) {
138        terminate(self.0, termination);
139    }
140}
141
142struct BoundaryObserver<T, E, OR, D: Disposable>(SubscriptionContext<Vec<T>, E, OR, Vec<T>, D>);
143
144impl<T, E, OR, D> Observer<(), E> for BoundaryObserver<T, E, OR, D>
145where
146    OR: Observer<Vec<T>, E>,
147    D: Disposable,
148{
149    fn on_next(&mut self, _: ()) -> Flow {
150        self.0.update_flow(|values| {
151            UpdateOutcome::empty()
152                .with_next_event(std::mem::replace(values, Vec::with_capacity(values.len())))
153        })
154    }
155
156    fn on_termination(self, termination: Termination<E>) {
157        terminate(self.0, termination);
158    }
159}
160
161fn terminate<T, E, OR, D>(
162    context: SubscriptionContext<Vec<T>, E, OR, Vec<T>, D>,
163    termination: Termination<E>,
164) where
165    OR: Observer<Vec<T>, E>,
166    D: Disposable,
167{
168    match termination {
169        completion @ Termination::Completed => {
170            let _ = context.update(|values| {
171                if values.is_empty() {
172                    UpdateOutcome::empty().with_termination_event(completion)
173                } else {
174                    UpdateOutcome::empty()
175                        .with_next_and_termination_events(std::mem::take(values), completion)
176                }
177            });
178        }
179        error @ Termination::Error(_) => {
180            context.send_termination(error);
181        }
182    }
183}