Skip to main content

keyed_stream_map/
stream_map.rs

1use futures_core::Stream;
2use std::borrow::Borrow;
3use std::collections::HashMap;
4use std::future::poll_fn;
5use std::hash::Hash;
6use std::pin::Pin;
7use std::task::{Context, Poll, ready};
8
9/// Merge many streams, tagged by key.
10///
11/// Yields `(key, item)` in arrival order. Insert and remove at any time.
12/// Polling starts at a random entry to reduce fixed-order bias. This does not
13/// guarantee a maximum wait time for any stream.
14///
15/// Unlike `tokio_stream::StreamMap`, keyed ops are `O(1)` via a hash index.
16/// Polling still scans entries, same as upstream.
17///
18/// # Example
19///
20/// ```
21/// use keyed_stream_map::StreamMap;
22/// use tokio_stream::{self as stream, StreamExt};
23///
24/// # #[tokio::main(flavor = "current_thread")]
25/// # async fn main() {
26/// let mut map = StreamMap::new();
27/// map.insert("a", stream::iter(vec![1]));
28/// map.insert("b", stream::iter(vec![2]));
29/// assert_eq!(map.len(), 2);
30/// assert_eq!(map.get(&"a").is_some(), true);
31/// while let Some((k, v)) = map.next().await {
32///     println!("{k}: {v}");
33/// }
34/// # }
35/// ```
36#[derive(Debug, Clone)]
37pub struct StreamMap<K, V> {
38    entries: Vec<(K, V)>,
39    // Clone of each key -> position in `entries`. Fixed up on every swap_remove.
40    index: HashMap<K, usize>,
41}
42
43impl<K, V> StreamMap<K, V> {
44    /// Empty map. Allocates on first insert.
45    ///
46    /// ```
47    /// use keyed_stream_map::StreamMap;
48    /// use tokio_stream::Pending;
49    /// let map: StreamMap<&str, Pending<()>> = StreamMap::new();
50    /// assert!(map.is_empty());
51    /// ```
52    pub fn new() -> Self {
53        Self {
54            entries: Vec::new(),
55            index: HashMap::new(),
56        }
57    }
58
59    /// Empty map that can hold `capacity` entries without reallocating.
60    pub fn with_capacity(capacity: usize) -> Self {
61        Self {
62            entries: Vec::with_capacity(capacity),
63            index: HashMap::with_capacity(capacity),
64        }
65    }
66
67    /// Iterate over `(key, stream)` pairs.
68    pub fn iter(&self) -> impl Iterator<Item = &(K, V)> {
69        self.entries.iter()
70    }
71
72    /// Iterate over keys and mutable references to their streams.
73    ///
74    /// Keys are immutable because the map keeps a separate index of them.
75    pub fn iter_mut(&mut self) -> impl Iterator<Item = (&K, &mut V)> {
76        self.entries.iter_mut().map(|(key, stream)| (&*key, stream))
77    }
78
79    /// All keys.
80    pub fn keys(&self) -> impl Iterator<Item = &K> {
81        self.entries.iter().map(|(k, _)| k)
82    }
83
84    /// All streams.
85    pub fn values(&self) -> impl Iterator<Item = &V> {
86        self.entries.iter().map(|(_, v)| v)
87    }
88
89    /// All streams, mutably.
90    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut V> {
91        self.entries.iter_mut().map(|(_, v)| v)
92    }
93
94    /// Room for more entries without reallocating. Lower bound.
95    pub fn capacity(&self) -> usize {
96        self.entries.capacity()
97    }
98
99    /// Count of streams in the map.
100    pub fn len(&self) -> usize {
101        self.entries.len()
102    }
103
104    /// True when no streams are stored.
105    pub fn is_empty(&self) -> bool {
106        self.entries.is_empty()
107    }
108
109    /// Drop all streams. Keeps allocated memory.
110    pub fn clear(&mut self) {
111        self.entries.clear();
112        self.index.clear();
113    }
114
115    /// Insert a stream. Returns the old stream when the key existed.
116    ///
117    /// Replace keeps the entry position, so polling order is stable.
118    pub fn insert(&mut self, k: K, stream: V) -> Option<V>
119    where
120        K: Hash + Eq + Clone,
121    {
122        if let Some(&pos) = self.index.get(&k) {
123            let slot = &mut self.entries[pos].1;
124            return Some(std::mem::replace(slot, stream));
125        }
126        let pos = self.entries.len();
127        self.index.insert(k.clone(), pos);
128        self.entries.push((k, stream));
129        None
130    }
131
132    /// Remove by key. Returns the stream when present.
133    pub fn remove<Q>(&mut self, k: &Q) -> Option<V>
134    where
135        K: Borrow<Q> + Hash + Eq + Clone,
136        Q: Hash + Eq + ?Sized,
137    {
138        let idx = *self.index.get(k)?;
139        self.index.remove(k);
140        Some(self.swap_remove_idx(idx).1)
141    }
142
143    /// True when the key has a stream.
144    pub fn contains_key<Q>(&self, k: &Q) -> bool
145    where
146        K: Borrow<Q> + Hash + Eq,
147        Q: Hash + Eq + ?Sized,
148    {
149        self.index.contains_key(k)
150    }
151
152    /// Reference to the stream for `k`, if present.
153    pub fn get<Q>(&self, k: &Q) -> Option<&V>
154    where
155        K: Borrow<Q> + Hash + Eq,
156        Q: Hash + Eq + ?Sized,
157    {
158        let &idx = self.index.get(k)?;
159        self.entries.get(idx).map(|(_, v)| v)
160    }
161
162    /// Mutable reference to the stream for `k`, if present.
163    pub fn get_mut<Q>(&mut self, k: &Q) -> Option<&mut V>
164    where
165        K: Borrow<Q> + Hash + Eq,
166        Q: Hash + Eq + ?Sized,
167    {
168        let &idx = self.index.get(k)?;
169        self.entries.get_mut(idx).map(|(_, v)| v)
170    }
171}
172
173impl<K, V> StreamMap<K, V>
174where
175    K: Clone,
176{
177    // Remove entries[idx]. Caller already dropped the index row for it.
178    // Points the swapped-in entry at its new slot.
179    fn swap_remove_idx(&mut self, idx: usize) -> (K, V)
180    where
181        K: Hash + Eq,
182    {
183        let out = self.entries.swap_remove(idx);
184        if idx < self.entries.len() {
185            let moved = self.entries[idx].0.clone();
186            self.index.insert(moved, idx);
187        }
188        out
189    }
190}
191
192impl<K, V> StreamMap<K, V>
193where
194    K: Unpin,
195    V: Stream + Unpin,
196{
197    fn poll_next_entry(&mut self, cx: &mut Context<'_>) -> Poll<Option<(usize, V::Item)>>
198    where
199        K: Hash + Eq + Clone,
200    {
201        if self.entries.is_empty() {
202            return Poll::Ready(None);
203        }
204        let start = crate::rand::thread_rng_n(self.entries.len() as u32) as usize;
205        let mut idx = start;
206
207        for _ in 0..self.entries.len() {
208            let (_, stream) = &mut self.entries[idx];
209            match Pin::new(stream).poll_next(cx) {
210                Poll::Ready(Some(val)) => return Poll::Ready(Some((idx, val))),
211                Poll::Ready(None) => {
212                    // Stream finished, drop it and keep polling.
213                    let key = self.entries[idx].0.clone();
214                    self.index.remove(&key);
215                    self.swap_remove_idx(idx);
216                    if self.entries.is_empty() {
217                        return Poll::Ready(None);
218                    }
219                    if idx == self.entries.len() {
220                        idx = 0;
221                    } else if idx < start && start <= self.entries.len() {
222                        // Swapped-in entry was already polled, skip it.
223                        idx = idx.wrapping_add(1) % self.entries.len();
224                    }
225                }
226                Poll::Pending => {
227                    idx = idx.wrapping_add(1) % self.entries.len();
228                }
229            }
230        }
231
232        if self.entries.is_empty() {
233            Poll::Ready(None)
234        } else {
235            Poll::Pending
236        }
237    }
238}
239
240impl<K, V> Default for StreamMap<K, V> {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246impl<K, V> StreamMap<K, V>
247where
248    K: Clone + Unpin,
249    V: Stream + Unpin,
250{
251    /// Pull up to `limit` items into `buffer`. Returns count appended.
252    ///
253    /// Returns 0 only when the map is empty or `limit` is 0.
254    /// Cancel safe: dropping the future loses nothing.
255    pub async fn next_many(&mut self, buffer: &mut Vec<(K, V::Item)>, limit: usize) -> usize
256    where
257        K: Hash + Eq,
258    {
259        poll_fn(|cx| self.poll_next_many(cx, buffer, limit)).await
260    }
261
262    /// Poll version of [`StreamMap::next_many`].
263    pub fn poll_next_many(
264        &mut self,
265        cx: &mut Context<'_>,
266        buffer: &mut Vec<(K, V::Item)>,
267        limit: usize,
268    ) -> Poll<usize>
269    where
270        K: Hash + Eq,
271    {
272        if limit == 0 || self.entries.is_empty() {
273            return Poll::Ready(0);
274        }
275
276        let mut added = 0;
277        let start = crate::rand::thread_rng_n(self.entries.len() as u32) as usize;
278        let mut idx = start;
279
280        while added < limit {
281            let mut made_progress = false;
282
283            for _ in 0..self.entries.len() {
284                if self.entries.is_empty() {
285                    break;
286                }
287                let (_, stream) = &mut self.entries[idx];
288                match Pin::new(stream).poll_next(cx) {
289                    Poll::Ready(Some(val)) => {
290                        added += 1;
291                        buffer.push((self.entries[idx].0.clone(), val));
292                        made_progress = true;
293                        idx = idx.wrapping_add(1) % self.entries.len();
294                        if added == limit {
295                            break;
296                        }
297                    }
298                    Poll::Ready(None) => {
299                        let key = self.entries[idx].0.clone();
300                        self.index.remove(&key);
301                        self.swap_remove_idx(idx);
302                        if self.entries.is_empty() {
303                            break;
304                        }
305                        if idx == self.entries.len() {
306                            idx = 0;
307                        } else if idx < start && start <= self.entries.len() {
308                            idx = idx.wrapping_add(1) % self.entries.len();
309                        }
310                    }
311                    Poll::Pending => {
312                        idx = idx.wrapping_add(1) % self.entries.len();
313                    }
314                }
315            }
316
317            if !made_progress {
318                break;
319            }
320        }
321
322        if added > 0 {
323            Poll::Ready(added)
324        } else if self.entries.is_empty() {
325            Poll::Ready(0)
326        } else {
327            Poll::Pending
328        }
329    }
330}
331
332impl<K, V> Stream for StreamMap<K, V>
333where
334    K: Clone + Unpin,
335    V: Stream + Unpin,
336    K: Hash + Eq,
337{
338    type Item = (K, V::Item);
339
340    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
341        if let Some((idx, val)) = ready!(self.poll_next_entry(cx)) {
342            let key = self.entries[idx].0.clone();
343            Poll::Ready(Some((key, val)))
344        } else {
345            Poll::Ready(None)
346        }
347    }
348
349    fn size_hint(&self) -> (usize, Option<usize>) {
350        let mut lo: usize = 0;
351        let mut hi: Option<usize> = Some(0);
352        for (_, stream) in &self.entries {
353            let (a, b) = stream.size_hint();
354            lo = lo.saturating_add(a);
355            match (hi, b) {
356                (Some(x), Some(y)) => hi = x.checked_add(y),
357                (Some(_), None) => hi = None,
358                _ => {}
359            }
360        }
361        (lo, hi)
362    }
363}
364
365impl<K, V> FromIterator<(K, V)> for StreamMap<K, V>
366where
367    K: Hash + Eq + Clone,
368{
369    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
370        let iter = iter.into_iter();
371        let (lo, _) = iter.size_hint();
372        let mut map = Self::with_capacity(lo);
373        for (k, v) in iter {
374            map.insert(k, v);
375        }
376        map
377    }
378}
379
380impl<K, V> Extend<(K, V)> for StreamMap<K, V>
381where
382    K: Hash + Eq + Clone,
383{
384    fn extend<T>(&mut self, iter: T)
385    where
386        T: IntoIterator<Item = (K, V)>,
387    {
388        // Insert per entry so duplicate keys replace instead of doubling.
389        for (k, v) in iter {
390            self.insert(k, v);
391        }
392    }
393}