use futures_core::Stream;
use futures_util::stream::SelectAll;
use pin_project::pin_project;
use std::pin::Pin;
use std::task::{Context, Poll};
#[pin_project]
pub struct DynamicMerge<T> {
#[pin]
select_all: SelectAll<Pin<Box<dyn Stream<Item = T> + Send>>>,
}
impl<T> DynamicMerge<T> {
pub fn new() -> Self {
Self {
select_all: SelectAll::new(),
}
}
pub fn with_capacity(_capacity: usize) -> Self {
Self::new()
}
pub fn push<S>(&mut self, stream: S)
where
S: Stream<Item = T> + Send + 'static,
{
self.select_all.push(Box::pin(stream));
}
pub fn len(&self) -> usize {
self.select_all.len()
}
pub fn is_empty(&self) -> bool {
self.select_all.is_empty()
}
pub fn clear(&mut self) {
self.select_all.clear();
}
}
impl<T> Default for DynamicMerge<T> {
fn default() -> Self {
Self::new()
}
}
impl<T> Stream for DynamicMerge<T> {
type Item = T;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.project();
this.select_all.poll_next(cx)
}
}
impl<T> From<Vec<Pin<Box<dyn Stream<Item = T> + Send>>>> for DynamicMerge<T> {
fn from(streams: Vec<Pin<Box<dyn Stream<Item = T> + Send>>>) -> Self {
Self {
select_all: SelectAll::from_iter(streams),
}
}
}
pub trait IntoDynamicMerge<T> {
fn into_dynamic_merge(self) -> DynamicMerge<T>;
}
impl<I, S, T> IntoDynamicMerge<T> for I
where
I: IntoIterator<Item = S>,
S: Stream<Item = T> + Send + 'static,
{
fn into_dynamic_merge(self) -> DynamicMerge<T> {
let streams: Vec<Pin<Box<dyn Stream<Item = T> + Send>>> = self
.into_iter()
.map(|s| Box::pin(s) as Pin<Box<dyn Stream<Item = T> + Send>>)
.collect();
DynamicMerge::from(streams)
}
}
#[cfg(test)]
mod tests {
use super::*;
use futures_util::stream::{self, StreamExt};
#[test]
fn test_empty_merge() {
let merge = DynamicMerge::<i32>::new();
assert!(merge.is_empty());
}
#[test]
fn test_push_stream() {
let mut merge = DynamicMerge::new();
merge.push(stream::iter(vec![1, 2, 3]));
assert_eq!(merge.len(), 1);
}
#[test]
fn test_clear() {
let mut merge = DynamicMerge::new();
merge.push(stream::iter(vec![1, 2, 3]));
merge.push(stream::iter(vec![4, 5, 6]));
assert_eq!(merge.len(), 2);
merge.clear();
assert_eq!(merge.len(), 0);
assert!(merge.is_empty());
}
#[tokio::test]
async fn test_basic_consumption() {
let mut merge = DynamicMerge::new();
merge.push(stream::iter(vec![1, 2, 3]));
merge.push(stream::iter(vec![4, 5, 6]));
let mut items = Vec::new();
while let Some(item) = merge.next().await {
items.push(item);
}
items.sort();
assert_eq!(items, vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn test_from_vec() {
let streams: Vec<Pin<Box<dyn Stream<Item = i32> + Send>>> = vec![
Box::pin(stream::iter(vec![1, 2, 3])),
Box::pin(stream::iter(vec![4, 5, 6])),
];
let merge = DynamicMerge::from(streams);
assert_eq!(merge.len(), 2);
}
#[test]
fn test_into_dynamic_merge() {
let streams = vec![stream::iter(vec![1, 2, 3]), stream::iter(vec![4, 5, 6])];
let merge = streams.into_dynamic_merge();
assert_eq!(merge.len(), 2);
}
}