Skip to content

Commit ec37e9e

Browse files
committed
add merge combinator
1 parent 059e16d commit ec37e9e

4 files changed

Lines changed: 145 additions & 2 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/uactor/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "uactor"
3-
version = "0.17.2"
3+
version = "0.17.3"
44
edition = "2021"
55
repository = "https://github.com/EnvOut/uactor"
66
license = "MIT"
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
use crate::data::datasource::{DataSource, DataSourceErrors, DataSourceResult};
2+
3+
pub struct DataSourceMerge<D1, D2>
4+
where
5+
D1: DataSource + Send,
6+
D2: DataSource<Item = D1::Item> + Send,
7+
{
8+
left: Option<D1>,
9+
right: Option<D2>,
10+
}
11+
12+
impl<D1, D2> DataSource for DataSourceMerge<D1, D2>
13+
where
14+
D1: DataSource + Send,
15+
D2: DataSource<Item = D1::Item> + Send,
16+
{
17+
type Item = D1::Item;
18+
19+
async fn next(&mut self) -> DataSourceResult<Self::Item> {
20+
loop {
21+
match (&mut self.left, &mut self.right) {
22+
(Some(left), Some(right)) => {
23+
tokio::select! {
24+
result = left.next() => match result {
25+
ok @ Ok(_) => return ok,
26+
Err(DataSourceErrors::ChannelClosed) => self.left = None,
27+
err => return err,
28+
},
29+
result = right.next() => match result {
30+
ok @ Ok(_) => return ok,
31+
Err(DataSourceErrors::ChannelClosed) => self.right = None,
32+
err => return err,
33+
},
34+
}
35+
}
36+
(Some(left), None) => return left.next().await,
37+
(None, Some(right)) => return right.next().await,
38+
(None, None) => return Err(DataSourceErrors::ChannelClosed),
39+
}
40+
}
41+
}
42+
}
43+
44+
pub trait DataSourceMergeExt: DataSource + Send + Sized {
45+
fn merge<D2>(self, other: D2) -> DataSourceMerge<Self, D2>
46+
where
47+
D2: DataSource<Item = Self::Item> + Send;
48+
}
49+
50+
impl<D> DataSourceMergeExt for D
51+
where
52+
D: DataSource + Send,
53+
{
54+
fn merge<D2>(self, other: D2) -> DataSourceMerge<Self, D2>
55+
where
56+
D2: DataSource<Item = Self::Item> + Send,
57+
{
58+
DataSourceMerge {
59+
left: Some(self),
60+
right: Some(other),
61+
}
62+
}
63+
}
64+
65+
#[cfg(test)]
66+
mod tests {
67+
use crate::data::datasource::DataSource;
68+
use crate::data::datasource_combinators::DataSourceMergeExt;
69+
70+
#[tokio::test]
71+
async fn test_merge_interleaved() {
72+
let (tx_1, rx_1) = tokio::sync::mpsc::unbounded_channel();
73+
tx_1.send(1).unwrap();
74+
tx_1.send(3).unwrap();
75+
tx_1.send(5).unwrap();
76+
tx_1.send(7).unwrap();
77+
tx_1.send(9).unwrap();
78+
79+
let (tx_2, rx_2) = tokio::sync::mpsc::unbounded_channel();
80+
tx_2.send(2).unwrap();
81+
tx_2.send(4).unwrap();
82+
tx_2.send(6).unwrap();
83+
tx_2.send(8).unwrap();
84+
tx_2.send(10).unwrap();
85+
86+
drop(tx_1);
87+
drop(tx_2);
88+
89+
let mut stream = rx_1.merge(rx_2);
90+
91+
let mut sum = 0;
92+
while let Ok(value) = stream.next().await {
93+
sum += value;
94+
}
95+
96+
assert_eq!(sum, 55);
97+
}
98+
99+
#[tokio::test]
100+
async fn test_merge_one_empty() {
101+
let (tx_1, rx_1) = tokio::sync::mpsc::unbounded_channel();
102+
tx_1.send(1).unwrap();
103+
tx_1.send(2).unwrap();
104+
tx_1.send(3).unwrap();
105+
drop(tx_1);
106+
107+
let (_tx_2, rx_2) = tokio::sync::mpsc::unbounded_channel::<i32>();
108+
drop(_tx_2);
109+
110+
let mut stream = rx_1.merge(rx_2);
111+
112+
let mut sum = 0;
113+
while let Ok(value) = stream.next().await {
114+
sum += value;
115+
}
116+
117+
assert_eq!(sum, 6);
118+
}
119+
120+
#[tokio::test]
121+
async fn test_merge_different_channel_types() {
122+
let (tx_1, rx_1) = tokio::sync::mpsc::unbounded_channel();
123+
tx_1.send(10).unwrap();
124+
tx_1.send(20).unwrap();
125+
drop(tx_1);
126+
127+
let (tx_2, rx_2) = tokio::sync::mpsc::channel(8);
128+
tx_2.send(30).await.unwrap();
129+
tx_2.send(40).await.unwrap();
130+
drop(tx_2);
131+
132+
let mut stream = rx_1.merge(rx_2);
133+
134+
let mut sum = 0;
135+
while let Ok(value) = stream.next().await {
136+
sum += value;
137+
}
138+
139+
assert_eq!(sum, 100);
140+
}
141+
}

src/uactor/src/data/datasource_combinators/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ use std::time::Duration;
66
mod filter_impl;
77
mod filter_map_impl;
88
mod map_impl;
9+
mod merge_impl;
910
mod timeout_impl;
1011

1112
pub use filter_impl::DataSourceFilterExt;
1213
pub use filter_map_impl::DataSourceFilterMapExt;
1314
pub use map_impl::DataSourceMapExt;
15+
pub use merge_impl::DataSourceMergeExt;
1416

1517
pub fn timeout<M, D>(duration: Duration, datasource: D) -> TimeoutDecorator<M, D>
1618
where

0 commit comments

Comments
 (0)