Skip to content

Commit 7b44635

Browse files
committed
Add concatArrayEager operator
1 parent 55793ec commit 7b44635

File tree

7 files changed

+167
-23
lines changed

7 files changed

+167
-23
lines changed

.travis.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Table of contents
2828
- Sources
2929
- `range`
3030
- `timer`
31+
- [`concatArrayEager`](#concatarrayeager)
3132
- Intermediate Flow operators (`FlowExtensions`)
3233
- `Flow.concatWith`
3334
- `Flow.groupBy`
@@ -259,4 +260,24 @@ uws.take(5).collect { println(it) }
259260

260261
// prints lines 11..15
261262
uws.take(5).collect { println(it) }
263+
```
264+
265+
## concatArrayEager
266+
267+
Launches all at once and emits all items from a source before items of the next are emitted.
268+
269+
For example, given two sources, if the first is slow, the items of the second won't be emitted until the first has
270+
finished emitting its items. This operators allows all sources to generate items in parallel but then still emit those
271+
items in the order their respective `Flow`s are listed.
272+
273+
Note that each source is consumed in an unbounded manner and thus, depending on the speed of
274+
the current source and the collector, the operator may retain items longer and may use more memory
275+
during its execution.
276+
277+
```kotlin
278+
concatArrayEager(
279+
range(1, 5).onStart { delay(200) },
280+
range(6, 5)
281+
)
282+
.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
262283
```

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
GROUP=com.github.akarnokd
2-
VERSION_NAME=0.0.8
2+
VERSION_NAME=0.0.9
33

44
POM_ARTIFACT_ID=kotlin-flow-extensions
55
POM_NAME=Kotlin Flow Extensions

src/main/kotlin/hu/akarnokd/kotlin/flow/FlowExtensions.kt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ fun <T> Flow<T>.toList() : Flow<List<T>> {
196196
* Drops items from the upstream when the downstream is not ready to receive them.
197197
*/
198198
@FlowPreview
199-
fun <T> Flow<T>.onBackpressurureDrop() : Flow<T> = FlowOnBackpressureDrop(this)
199+
fun <T> Flow<T>.onBackpressureDrop() : Flow<T> = FlowOnBackpressureDrop(this)
200200

201201
/**
202202
* Maps items from the upstream to [Flow] and relays its items while dropping upstream items
@@ -211,12 +211,21 @@ fun <T, R> Flow<T>.flatMapDrop(mapper: suspend (T) -> Flow<R>) : Flow<R> = FlowF
211211
@FlowPreview
212212
fun <T> mergeArray(vararg sources: Flow<T>) : Flow<T> = FlowMergeArray(sources)
213213

214+
/**
215+
* Launches all [sources] at once and emits all items from a source before items of the next are emitted.
216+
* Note that each source is consumed in an unbounded manner and thus, depending on the speed of
217+
* the current source and the collector, the operator may retain items longer and may use more memory
218+
* during its execution.
219+
*/
220+
@FlowPreview
221+
fun <T> concatArrayEager(vararg sources: Flow<T>) : Flow<T> = FlowConcatArrayEager(sources)
222+
214223
// -----------------------------------------------------------------------------------------
215224
// Parallel Extensions
216225
// -----------------------------------------------------------------------------------------
217226

218227
/**
219-
* Consumes the upstream and dispatches individual items to a parrallel rail
228+
* Consumes the upstream and dispatches individual items to a parallel rail
220229
* of the parallel flow for further consumption.
221230
*/
222231
fun <T> Flow<T>.parallel(parallelism: Int, runOn: (Int) -> CoroutineDispatcher) : ParallelFlow<T> =
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2019-2020 David Karnok
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package hu.akarnokd.kotlin.flow.impl
18+
19+
import hu.akarnokd.kotlin.flow.Resumable
20+
import kotlinx.coroutines.*
21+
import kotlinx.coroutines.flow.AbstractFlow
22+
import kotlinx.coroutines.flow.Flow
23+
import kotlinx.coroutines.flow.FlowCollector
24+
import java.util.concurrent.ConcurrentLinkedQueue
25+
import java.util.concurrent.atomic.AtomicInteger
26+
import kotlinx.coroutines.flow.collect
27+
import java.util.concurrent.atomic.AtomicIntegerArray
28+
29+
@FlowPreview
30+
class FlowConcatArrayEager<T>(private val sources: Array<out Flow<T>>) : AbstractFlow<T>() {
31+
32+
@InternalCoroutinesApi
33+
override suspend fun collectSafely(collector: FlowCollector<T>) {
34+
val n = sources.size
35+
val queues = Array(n) { ConcurrentLinkedQueue<T>() }
36+
val done = AtomicIntegerArray(n)
37+
var index = 0;
38+
val reader = Resumable()
39+
40+
coroutineScope {
41+
for (i in 0 until n) {
42+
val f = sources[i]
43+
val q = queues[i]
44+
val j = i
45+
launch {
46+
try {
47+
f.collect {
48+
q.offer(it)
49+
reader.resume()
50+
}
51+
} finally {
52+
done.set(j, 1)
53+
reader.resume()
54+
}
55+
}
56+
}
57+
58+
59+
while (isActive && index < n) {
60+
val q = queues[index]
61+
val d = done.get(index) != 0
62+
63+
if (d && q.isEmpty()) {
64+
index++
65+
continue
66+
}
67+
68+
val v = q.poll()
69+
if (v != null) {
70+
collector.emit(v)
71+
continue;
72+
}
73+
74+
reader.await()
75+
}
76+
}
77+
}
78+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package hu.akarnokd.kotlin.flow.impl
2+
3+
import hu.akarnokd.kotlin.flow.assertResult
4+
import hu.akarnokd.kotlin.flow.concatArrayEager
5+
import hu.akarnokd.kotlin.flow.range
6+
import kotlinx.coroutines.FlowPreview
7+
import kotlinx.coroutines.delay
8+
import kotlinx.coroutines.flow.onEach
9+
import kotlinx.coroutines.flow.onStart
10+
import kotlinx.coroutines.flow.take
11+
import kotlinx.coroutines.runBlocking
12+
import org.junit.Test
13+
import java.util.concurrent.atomic.AtomicInteger
14+
import kotlin.test.assertEquals
15+
16+
@FlowPreview
17+
class FlowConcatArrayEagerTest {
18+
@Test
19+
fun basic() = runBlocking {
20+
val state1 = AtomicInteger()
21+
val state2 = AtomicInteger()
22+
concatArrayEager(
23+
range(1, 5).onStart {
24+
delay(200)
25+
state1.set(1)
26+
}.onEach { println(it) },
27+
range(6, 5).onStart {
28+
state2.set(state1.get())
29+
}.onEach { println(it) },
30+
)
31+
.assertResult(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
32+
33+
assertEquals(0, state2.get())
34+
}
35+
36+
@Test
37+
fun basic1() = runBlocking {
38+
concatArrayEager(
39+
range(1, 5)
40+
)
41+
.assertResult(1, 2, 3, 4, 5)
42+
43+
}
44+
45+
@Test
46+
fun take() = runBlocking {
47+
concatArrayEager(
48+
range(1, 5).onStart { delay(100) },
49+
range(6, 5)
50+
)
51+
.take(6)
52+
.assertResult(1, 2, 3, 4, 5, 6)
53+
}
54+
}

src/test/kotlin/hu/akarnokd/kotlin/flow/impl/FlowOnBackpressureDropTest.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@
1717
package hu.akarnokd.kotlin.flow.impl
1818

1919
import hu.akarnokd.kotlin.flow.assertResult
20-
import hu.akarnokd.kotlin.flow.onBackpressurureDrop
21-
import hu.akarnokd.kotlin.flow.startCollectOn
20+
import hu.akarnokd.kotlin.flow.onBackpressureDrop
2221
import kotlinx.coroutines.*
2322
import kotlinx.coroutines.flow.*
2423
import org.junit.Test
@@ -35,7 +34,7 @@ class FlowOnBackpressureDropTest {
3534
delay(100)
3635
}
3736
}
38-
.onBackpressurureDrop()
37+
.onBackpressureDrop()
3938
.map {
4039
delay(130)
4140
it

0 commit comments

Comments
 (0)