Skip to main content

futures_concurrency_dynamic/
handle.rs

1//! Handle-based API for DynamicMerge that allows separate mutable access.
2//!
3//! This module provides a way to split the dynamic merge into two components:
4//! - A `DynamicMergeStream` that implements `Stream` and can be polled
5//! - A `DynamicMergeHandle` that can push new streams
6//!
7//! This allows different parts of your code to hold mutable references to each independently.
8
9use crate::DynamicMerge;
10use futures_core::Stream;
11use futures_util::stream::SelectAll;
12use std::pin::Pin;
13use std::sync::{Arc, Mutex};
14use std::task::{Context, Poll};
15
16/// A handle for pushing new streams into a `DynamicMergeStream`.
17///
18/// This handle shares ownership of the underlying `DynamicMerge` with the
19/// `DynamicMergeStream`, allowing you to push new streams while the stream is
20/// being polled elsewhere.
21///
22/// # Examples
23///
24/// ```
25/// use futures_concurrency_dynamic::dynamic_merge_with_handle;
26/// use futures_util::stream::{self, StreamExt};
27///
28/// # async fn example() {
29/// let (mut stream, mut handle) = dynamic_merge_with_handle::<i32>();
30///
31/// // Push streams via the handle
32/// handle.push(stream::iter(vec![1, 2, 3]));
33/// handle.push(stream::iter(vec![4, 5, 6]));
34///
35/// // Poll the stream elsewhere
36/// let items: Vec<i32> = stream.collect().await;
37/// # }
38/// ```
39pub struct DynamicMergeHandle<'a, T> {
40    shared: Arc<Mutex<DynamicMerge<'a, T>>>,
41}
42
43impl<'a, T> DynamicMergeHandle<'a, T> {
44    /// Pushes a new stream into the merge.
45    ///
46    /// The stream will be polled concurrently with other streams. When the stream
47    /// produces an item, it will be returned from the merge. When the stream
48    /// completes, it is automatically removed.
49    ///
50    /// # Examples
51    ///
52    /// ```
53    /// use futures_concurrency_dynamic::dynamic_merge_with_handle;
54    /// use futures_util::stream;
55    ///
56    /// let (stream, mut handle) = dynamic_merge_with_handle::<i32>();
57    /// handle.push(stream::iter(vec![1, 2, 3]));
58    /// handle.push(stream::iter(vec![4, 5, 6]));
59    /// ```
60    pub fn push<S>(&mut self, stream: S)
61    where
62        S: Stream<Item = T> + Send + 'a,
63    {
64        self.shared.lock().unwrap().push(Box::pin(stream));
65    }
66
67    /// Returns the number of active streams currently in the merge.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use futures_concurrency_dynamic::dynamic_merge_with_handle;
73    /// use futures_util::stream;
74    ///
75    /// let (stream, mut handle) = dynamic_merge_with_handle::<i32>();
76    /// assert_eq!(handle.len(), 0);
77    ///
78    /// handle.push(stream::iter(vec![1, 2, 3]));
79    /// assert_eq!(handle.len(), 1);
80    /// ```
81    pub fn len(&self) -> usize {
82        self.shared.lock().unwrap().len()
83    }
84
85    /// Returns `true` if there are no active streams in the merge.
86    ///
87    /// # Examples
88    ///
89    /// ```
90    /// use futures_concurrency_dynamic::dynamic_merge_with_handle;
91    ///
92    /// let (stream, handle) = dynamic_merge_with_handle::<i32>();
93    /// assert!(handle.is_empty());
94    /// ```
95    pub fn is_empty(&self) -> bool {
96        self.shared.lock().unwrap().is_empty()
97    }
98
99    /// Clears all streams from the merge, removing all active streams.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use futures_concurrency_dynamic::dynamic_merge_with_handle;
105    /// use futures_util::stream;
106    ///
107    /// let (stream, mut handle) = dynamic_merge_with_handle::<i32>();
108    /// handle.push(stream::iter(vec![1, 2, 3]));
109    /// assert_eq!(handle.len(), 1);
110    ///
111    /// handle.clear();
112    /// assert_eq!(handle.len(), 0);
113    /// ```
114    pub fn clear(&mut self) {
115        self.shared.lock().unwrap().clear();
116    }
117}
118
119impl<'a, T> Clone for DynamicMergeHandle<'a, T> {
120    fn clone(&self) -> Self {
121        Self {
122            shared: Arc::clone(&self.shared),
123        }
124    }
125}
126
127/// The stream component of a split dynamic merge.
128///
129/// This stream can be polled to receive items from all streams added via the
130/// corresponding `DynamicMergeHandle`.
131///
132/// Created via [`dynamic_merge_with_handle`].
133pub struct DynamicMergeStream<'a, T> {
134    shared: Arc<Mutex<DynamicMerge<'a, T>>>,
135}
136
137impl<'a, T> Stream for DynamicMergeStream<'a, T> {
138    type Item = T;
139
140    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
141        let mut shared = self.shared.lock().unwrap();
142        Pin::new(&mut *shared).poll_next(cx)
143    }
144}
145
146/// Creates a new dynamic merge with a separate handle for pushing streams.
147///
148/// Returns a tuple of `(stream, handle)` where:
149/// - `stream` implements `Stream` and yields items from all added streams
150/// - `handle` allows pushing new streams and controlling the merge lifecycle
151///
152/// This allows the stream and handle to be held by different parts of your code
153/// with independent mutable access.
154///
155/// # Examples
156///
157/// ```
158/// use futures_concurrency_dynamic::dynamic_merge_with_handle;
159/// use futures_util::stream::{self, StreamExt};
160///
161/// # async fn example() {
162/// let (mut stream, mut handle) = dynamic_merge_with_handle::<i32>();
163///
164/// // In one part of code: push streams
165/// handle.push(stream::iter(vec![1, 2, 3]));
166/// handle.push(stream::iter(vec![4, 5, 6]));
167///
168/// // In another part: consume the stream
169/// while let Some(item) = stream.next().await {
170///     println!("Got: {}", item);
171/// }
172/// # }
173/// ```
174///
175/// # Type Parameters
176///
177/// - `T`: The item type that all streams must produce
178///
179/// Note: All streams must be `Send` but no longer require `'static` lifetime.
180pub fn dynamic_merge_with_handle<'a, T>() -> (DynamicMergeStream<'a, T>, DynamicMergeHandle<'a, T>)
181{
182    let shared = Arc::new(Mutex::new(SelectAll::new()));
183    let stream = DynamicMergeStream {
184        shared: Arc::clone(&shared),
185    };
186    let handle = DynamicMergeHandle { shared };
187    (stream, handle)
188}
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use futures_util::stream::{self, StreamExt};
194
195    #[tokio::test]
196    async fn test_basic_handle_usage() {
197        let (mut stream, mut handle) = dynamic_merge_with_handle::<i32>();
198
199        handle.push(stream::iter(vec![1, 2, 3]));
200        handle.push(stream::iter(vec![4, 5, 6]));
201
202        let mut items = Vec::new();
203        while let Some(item) = stream.next().await {
204            items.push(item);
205        }
206
207        items.sort();
208        assert_eq!(items, vec![1, 2, 3, 4, 5, 6]);
209    }
210
211    #[tokio::test]
212    async fn test_separate_mutable_access() {
213        let (mut stream, mut handle) = dynamic_merge_with_handle::<i32>();
214
215        // Push both streams first
216        handle.push(stream::iter(vec![1, 2, 3]));
217        handle.push(stream::iter(vec![4, 5, 6]));
218
219        // Then consume from the stream
220        let mut items = Vec::new();
221        while let Some(item) = stream.next().await {
222            items.push(item);
223        }
224
225        items.sort();
226        assert_eq!(items, vec![1, 2, 3, 4, 5, 6]);
227    }
228
229    #[tokio::test]
230    async fn test_handle_clone() {
231        let (mut stream, mut handle1) = dynamic_merge_with_handle::<i32>();
232        let mut handle2 = handle1.clone();
233
234        handle1.push(stream::iter(vec![1, 2, 3]));
235        handle2.push(stream::iter(vec![4, 5, 6]));
236
237        let mut items = Vec::new();
238        while let Some(item) = stream.next().await {
239            items.push(item);
240        }
241
242        items.sort();
243        assert_eq!(items, vec![1, 2, 3, 4, 5, 6]);
244    }
245
246    #[test]
247    fn test_handle_len_and_empty() {
248        let (_stream, mut handle) = dynamic_merge_with_handle::<i32>();
249        assert!(handle.is_empty());
250        assert_eq!(handle.len(), 0);
251
252        handle.push(stream::iter(vec![1, 2, 3]));
253        assert!(!handle.is_empty());
254        assert_eq!(handle.len(), 1);
255
256        handle.push(stream::iter(vec![4, 5, 6]));
257        assert_eq!(handle.len(), 2);
258    }
259
260    #[test]
261    fn test_handle_clear() {
262        let (_stream, mut handle) = dynamic_merge_with_handle::<i32>();
263
264        handle.push(stream::iter(vec![1, 2, 3]));
265        handle.push(stream::iter(vec![4, 5, 6]));
266        assert_eq!(handle.len(), 2);
267
268        handle.clear();
269        assert_eq!(handle.len(), 0);
270        assert!(handle.is_empty());
271    }
272
273    #[tokio::test]
274    async fn test_dynamic_push_while_polling() {
275        let (mut stream, mut handle) = dynamic_merge_with_handle::<i32>();
276
277        handle.push(stream::iter(vec![1, 2]));
278
279        let item1 = stream.next().await;
280        assert!(item1.is_some());
281
282        // Push more while already polling
283        handle.push(stream::iter(vec![3, 4]));
284
285        let item2 = stream.next().await;
286        assert!(item2.is_some());
287
288        let item3 = stream.next().await;
289        assert!(item3.is_some());
290
291        let item4 = stream.next().await;
292        assert!(item4.is_some());
293    }
294}