Skip to main content

tuwunel_core/utils/stream/
broadband.rs

1//! Broadband stream combinator extensions to futures::Stream
2
3use std::convert::identity;
4
5use futures::stream::{Stream, StreamExt};
6
7use super::{ReadyExt, automatic_width};
8
9/// Adds concurrent transformations with completion-ordered output.
10///
11/// Item futures may run ahead of downstream demand. Outputs are yielded as they
12/// become ready rather than in source order.
13pub trait BroadbandExt<Item>
14where
15	Self: Stream<Item = Item> + Send + Sized,
16{
17	/// Tests all items concurrently with an explicit width.
18	///
19	/// `n` limits in-flight predicates, while `None` selects the automatic
20	/// width. An explicit zero cannot make progress. False short-circuits the
21	/// operation, and an empty stream resolves true.
22	fn broadn_all<F, Fut, N>(self, n: N, f: F) -> impl Future<Output = bool> + Send
23	where
24		N: Into<Option<usize>>,
25		F: Fn(Item) -> Fut + Send,
26		Fut: Future<Output = bool> + Send;
27
28	/// Tests items concurrently for any match with an explicit width.
29	///
30	/// `n` limits in-flight predicates, while `None` selects the automatic
31	/// width. An explicit zero cannot make progress. True short-circuits the
32	/// operation, and an empty stream resolves false.
33	fn broadn_any<F, Fut, N>(self, n: N, f: F) -> impl Future<Output = bool> + Send
34	where
35		N: Into<Option<usize>>,
36		F: Fn(Item) -> Fut + Send,
37		Fut: Future<Output = bool> + Send;
38
39	/// Maps and filters items concurrently with an explicit width.
40	///
41	/// `n` limits in-flight item futures, while `None` selects the automatic
42	/// width. An explicit zero cannot make progress. Present outputs are
43	/// yielded in completion order and absent outputs are omitted.
44	fn broadn_filter_map<F, Fut, U, N>(self, n: N, f: F) -> impl Stream<Item = U> + Send
45	where
46		N: Into<Option<usize>>,
47		F: Fn(Item) -> Fut + Send,
48		Fut: Future<Output = Option<U>> + Send,
49		U: Send;
50
51	/// Finds the first concurrently completed mapped value.
52	///
53	/// `n` limits in-flight item futures, while `None` selects the automatic
54	/// width. An explicit zero cannot make progress. The first completed `Some`
55	/// wins, which may differ from the earliest matching source item.
56	fn broadn_find_map<'a, F, Fut, U, N>(
57		self,
58		n: N,
59		f: F,
60	) -> impl Future<Output = Option<U>> + Send
61	where
62		N: Into<Option<usize>>,
63		F: Fn(Item) -> Fut + Send + 'a,
64		Fut: Future<Output = Option<U>> + Send,
65		U: Send + 'a,
66		Self: Unpin + 'a;
67
68	/// Flattens mapped streams concurrently with an explicit width.
69	///
70	/// A nonzero `n` limits active inner streams, zero disables the limit, and
71	/// `None` selects the automatic width. Inner outputs are interleaved
72	/// according to readiness rather than source order.
73	fn broadn_flat_map<F, Fut, U, N>(self, n: N, f: F) -> impl Stream<Item = U> + Send
74	where
75		N: Into<Option<usize>>,
76		F: Fn(Item) -> Fut + Send,
77		Fut: Stream<Item = U> + Send + Unpin,
78		U: Send;
79
80	/// Maps items concurrently with an explicit width.
81	///
82	/// `n` limits in-flight item futures, while `None` selects the automatic
83	/// width. An explicit zero cannot make progress. Outputs are yielded in
84	/// completion order.
85	fn broadn_then<F, Fut, U, N>(self, n: N, f: F) -> impl Stream<Item = U> + Send
86	where
87		N: Into<Option<usize>>,
88		F: Fn(Item) -> Fut + Send,
89		Fut: Future<Output = U> + Send,
90		U: Send;
91
92	/// Tests all items concurrently with the automatic width.
93	///
94	/// False short-circuits the operation, while true requires every predicate
95	/// to return true. An empty stream resolves true.
96	#[inline]
97	fn broad_all<F, Fut>(self, f: F) -> impl Future<Output = bool> + Send
98	where
99		F: Fn(Item) -> Fut + Send,
100		Fut: Future<Output = bool> + Send,
101	{
102		self.broadn_all(None, f)
103	}
104
105	/// Tests items concurrently for any match with the automatic width.
106	///
107	/// True short-circuits the operation, while false requires every predicate
108	/// to return false. An empty stream resolves false.
109	#[inline]
110	fn broad_any<F, Fut>(self, f: F) -> impl Future<Output = bool> + Send
111	where
112		F: Fn(Item) -> Fut + Send,
113		Fut: Future<Output = bool> + Send,
114	{
115		self.broadn_any(None, f)
116	}
117
118	/// Maps and filters items concurrently with the automatic width.
119	///
120	/// Present outputs are yielded in completion order rather than source
121	/// order. Absent outputs are omitted.
122	#[inline]
123	fn broad_filter_map<F, Fut, U>(self, f: F) -> impl Stream<Item = U> + Send
124	where
125		F: Fn(Item) -> Fut + Send,
126		Fut: Future<Output = Option<U>> + Send,
127		U: Send,
128	{
129		self.broadn_filter_map(None, f)
130	}
131
132	/// Finds the first mapped value to complete with `Some`.
133	///
134	/// Item futures run at the automatic width. Completion order determines the
135	/// winner rather than source order.
136	#[inline]
137	fn broad_find_map<'a, F, Fut, U>(self, f: F) -> impl Future<Output = Option<U>> + Send
138	where
139		F: Fn(Item) -> Fut + Send + 'a,
140		Fut: Future<Output = Option<U>> + Send,
141		U: Send + 'a,
142		Self: Unpin + 'a,
143	{
144		self.broadn_find_map(None, f)
145	}
146
147	/// Flattens mapped streams concurrently with the automatic width.
148	///
149	/// Inner streams are polled together and their outputs are interleaved by
150	/// readiness. Source ordering is not preserved.
151	#[inline]
152	fn broad_flat_map<F, Fut, U>(self, f: F) -> impl Stream<Item = U> + Send
153	where
154		F: Fn(Item) -> Fut + Send,
155		Fut: Stream<Item = U> + Send + Unpin,
156		U: Send,
157	{
158		self.broadn_flat_map(None, f)
159	}
160
161	/// Maps items concurrently with the automatic width.
162	///
163	/// Item futures may run ahead of downstream demand. Outputs are yielded in
164	/// completion order rather than source order.
165	#[inline]
166	fn broad_then<F, Fut, U>(self, f: F) -> impl Stream<Item = U> + Send
167	where
168		F: Fn(Item) -> Fut + Send,
169		Fut: Future<Output = U> + Send,
170		U: Send,
171	{
172		self.broadn_then(None, f)
173	}
174}
175
176impl<Item, S> BroadbandExt<Item> for S
177where
178	S: Stream<Item = Item> + Send + Sized,
179{
180	#[inline]
181	fn broadn_all<F, Fut, N>(self, n: N, f: F) -> impl Future<Output = bool> + Send
182	where
183		N: Into<Option<usize>>,
184		F: Fn(Item) -> Fut + Send,
185		Fut: Future<Output = bool> + Send,
186	{
187		self.map(f)
188			.buffer_unordered(n.into().unwrap_or_else(automatic_width))
189			.ready_all(identity)
190	}
191
192	#[inline]
193	fn broadn_any<F, Fut, N>(self, n: N, f: F) -> impl Future<Output = bool> + Send
194	where
195		N: Into<Option<usize>>,
196		F: Fn(Item) -> Fut + Send,
197		Fut: Future<Output = bool> + Send,
198	{
199		self.map(f)
200			.buffer_unordered(n.into().unwrap_or_else(automatic_width))
201			.ready_any(identity)
202	}
203
204	#[inline]
205	fn broadn_filter_map<F, Fut, U, N>(self, n: N, f: F) -> impl Stream<Item = U> + Send
206	where
207		N: Into<Option<usize>>,
208		F: Fn(Item) -> Fut + Send,
209		Fut: Future<Output = Option<U>> + Send,
210		U: Send,
211	{
212		self.map(f)
213			.buffer_unordered(n.into().unwrap_or_else(automatic_width))
214			.ready_filter_map(identity)
215	}
216
217	#[inline]
218	fn broadn_find_map<'a, F, Fut, U, N>(
219		self,
220		n: N,
221		f: F,
222	) -> impl Future<Output = Option<U>> + Send
223	where
224		N: Into<Option<usize>>,
225		F: Fn(Item) -> Fut + Send + 'a,
226		Fut: Future<Output = Option<U>> + Send,
227		U: Send + 'a,
228		Self: Unpin + 'a,
229	{
230		self.map(f)
231			.buffer_unordered(n.into().unwrap_or_else(automatic_width))
232			.ready_find_map(identity)
233	}
234
235	#[inline]
236	fn broadn_flat_map<F, Fut, U, N>(self, n: N, f: F) -> impl Stream<Item = U> + Send
237	where
238		N: Into<Option<usize>>,
239		F: Fn(Item) -> Fut + Send,
240		Fut: Stream<Item = U> + Send + Unpin,
241		U: Send,
242	{
243		self.flat_map_unordered(n.into().unwrap_or_else(automatic_width), f)
244	}
245
246	#[inline]
247	fn broadn_then<F, Fut, U, N>(self, n: N, f: F) -> impl Stream<Item = U> + Send
248	where
249		N: Into<Option<usize>>,
250		F: Fn(Item) -> Fut + Send,
251		Fut: Future<Output = U> + Send,
252		U: Send,
253	{
254		self.map(f)
255			.buffer_unordered(n.into().unwrap_or_else(automatic_width))
256	}
257}