-
Notifications
You must be signed in to change notification settings - Fork 3
feat: add typed iterators for StreamMap and StreamSet
#10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
74540b5
cecebba
22c931a
59b6790
d40abf5
a153595
0f8514b
9ab63dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,21 @@ | ||
| use std::any::Any; | ||
| use std::mem; | ||
| use std::pin::Pin; | ||
| use std::task::{Context, Poll, Waker}; | ||
| use std::time::Duration; | ||
|
|
||
| use futures_util::stream::{BoxStream, SelectAll}; | ||
| use futures_util::stream::SelectAll; | ||
| use futures_util::{stream, FutureExt, Stream, StreamExt}; | ||
|
|
||
| use crate::{Delay, PushError, Timeout}; | ||
| use crate::{AnyStream, BoxStream, Delay, PushError, Timeout}; | ||
|
|
||
| /// Represents a map of [`Stream`]s. | ||
| /// | ||
| /// Each stream must finish within the specified time and the map never outgrows its capacity. | ||
| pub struct StreamMap<ID, O> { | ||
| make_delay: Box<dyn Fn() -> Delay + Send + Sync>, | ||
| capacity: usize, | ||
| inner: SelectAll<TaggedStream<ID, TimeoutStream<BoxStream<'static, O>>>>, | ||
| inner: SelectAll<TaggedStream<ID, TimeoutStream<BoxStream<O>>>>, | ||
| empty_waker: Option<Waker>, | ||
| full_waker: Option<Waker>, | ||
| } | ||
|
|
@@ -42,10 +43,10 @@ where | |
| /// Push a stream into the map. | ||
| pub fn try_push<F>(&mut self, id: ID, stream: F) -> Result<(), PushError<BoxStream<O>>> | ||
| where | ||
| F: Stream<Item = O> + Send + 'static, | ||
| F: AnyStream<Item = O>, | ||
| { | ||
| if self.inner.len() >= self.capacity { | ||
| return Err(PushError::BeyondCapacity(stream.boxed())); | ||
| return Err(PushError::BeyondCapacity(Box::pin(stream))); | ||
| } | ||
|
|
||
| if let Some(waker) = self.empty_waker.take() { | ||
|
|
@@ -56,7 +57,7 @@ where | |
| self.inner.push(TaggedStream::new( | ||
| id, | ||
| TimeoutStream { | ||
| inner: stream.boxed(), | ||
| inner: Box::pin(stream), | ||
| timeout: (self.make_delay)(), | ||
| }, | ||
| )); | ||
|
|
@@ -67,10 +68,10 @@ where | |
| } | ||
| } | ||
|
|
||
| pub fn remove(&mut self, id: ID) -> Option<BoxStream<'static, O>> { | ||
| pub fn remove(&mut self, id: ID) -> Option<BoxStream<O>> { | ||
| let tagged = self.inner.iter_mut().find(|s| s.key == id)?; | ||
|
|
||
| let inner = mem::replace(&mut tagged.inner.inner, stream::pending().boxed()); | ||
| let inner = mem::replace(&mut tagged.inner.inner, Box::pin(stream::pending())); | ||
| tagged.exhausted = true; // Setting this will emit `None` on the next poll and ensure `SelectAll` cleans up the resources. | ||
|
|
||
| Some(inner) | ||
|
|
@@ -113,6 +114,36 @@ where | |
| Some((id, None)) => Poll::Ready((id, None)), | ||
| } | ||
| } | ||
|
|
||
| /// Returns an iterator over all streams whose inner type is `T`. | ||
| /// | ||
| /// If downcasting a stream to `T` fails it will be skipped in the iterator. | ||
| pub fn iter_typed<T>(&self) -> impl Iterator<Item = (&ID, &T)> | ||
|
elenaf9 marked this conversation as resolved.
Outdated
|
||
| where | ||
| T: 'static, | ||
| { | ||
| self.inner.iter().filter_map(|a| { | ||
| let pin = a.inner.inner.as_ref(); | ||
| let any = Pin::into_inner(pin) as &(dyn Any + Send); | ||
| let inner = any.downcast_ref::<T>()?; | ||
| Some((&a.key, inner)) | ||
| }) | ||
| } | ||
|
|
||
| /// Returns an iterator with mutable access over all streams whose inner type is `T`. | ||
| /// | ||
| /// If downcasting a stream to `T` fails it will be skipped in the iterator. | ||
| pub fn iter_mut_typed<T>(&mut self) -> impl Iterator<Item = (&mut ID, &mut T)> | ||
| where | ||
| T: 'static, | ||
| { | ||
| self.inner.iter_mut().filter_map(|a| { | ||
| let pin = a.inner.inner.as_mut(); | ||
| let any = Pin::into_inner(pin) as &mut (dyn Any + Send); | ||
| let inner = any.downcast_mut::<T>()?; | ||
| Some((&mut a.key, inner)) | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| struct TimeoutStream<S> { | ||
|
|
@@ -304,6 +335,29 @@ mod tests { | |
| assert!(duration >= DELAY * NUM_STREAMS); | ||
| } | ||
|
|
||
| #[test] | ||
| fn can_iter_named_streams() { | ||
| const N: usize = 10; | ||
| let mut streams = StreamMap::new(|| Delay::futures_timer(Duration::from_millis(100)), N); | ||
| let mut sender = Vec::with_capacity(N); | ||
| for i in 0..N { | ||
| let (tx, rx) = mpsc::channel::<()>(1); | ||
| streams.try_push(format!("ID{i}"), rx).unwrap(); | ||
| sender.push(tx); | ||
| } | ||
| assert_eq!(streams.iter_typed::<mpsc::Receiver<()>>().count(), N); | ||
| for (i, (id, _)) in streams.iter_typed::<mpsc::Receiver<()>>().enumerate() { | ||
| let expect_id = format!("ID{}", N - i - 1); // Reverse order. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Huh? Why are we handing them out in reverse order?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why, but that's how the inner
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting. It is a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we can sort them in the
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could sort them as part of the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This would be better left to the caller: callers who want a sorted iterator can use the combinator, and those who don't care don't have to pay the performance cost of sorting/comparisons (and extra temporary memory). Documenting that the order is unspecified should be enough to inform people of the need for sorting.
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Makes sense to me! Wanna send a PR? |
||
| assert_eq!(id, &expect_id); | ||
| } | ||
| assert!(!sender.iter().any(|tx| tx.is_closed())); | ||
|
|
||
| for (_, rx) in streams.iter_mut_typed::<mpsc::Receiver<()>>() { | ||
| rx.close(); | ||
| } | ||
| assert!(sender.iter().all(|tx| tx.is_closed())); | ||
| } | ||
|
|
||
| struct Task { | ||
| item_delay: Duration, | ||
| num_streams: usize, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.