Skip to content

Commit 3b5c306

Browse files
Added AsyncBufferedIterator
1 parent 7d397f8 commit 3b5c306

File tree

2 files changed

+179
-0
lines changed

2 files changed

+179
-0
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// AsyncBufferedIterator.swift
3+
// AsyncSequenceReader
4+
//
5+
// Created by Dimitri Bouniol on 2021-11-17.
6+
// Copyright © 2021 Mochi Development, Inc. All rights reserved.
7+
//
8+
9+
#if compiler(>=5.5) && canImport(_Concurrency)
10+
11+
/// An iterator that wraps and can buffer another iterator, allowing safe reading ahead.
12+
public struct AsyncBufferedIterator<BaseIterator: AsyncIteratorProtocol>: AsyncIteratorProtocol {
13+
@usableFromInline
14+
var baseIterator: BaseIterator
15+
16+
/// The unconsumed buffer, including all reads that have been made before the user specifically requested them.
17+
///
18+
/// - Note:Ideally, this should be implemented using some sort of cyclical buffer like a Deque, but in practice, it will only ever have one entry.
19+
@usableFromInline
20+
var unconsumedBuffer: [BaseIterator.Element] = []
21+
22+
@usableFromInline
23+
init(_ baseIterator: BaseIterator) {
24+
self.baseIterator = baseIterator
25+
}
26+
27+
@inlinable
28+
public mutating func next() async rethrows -> BaseIterator.Element? {
29+
guard unconsumedBuffer.isEmpty else {
30+
return unconsumedBuffer.removeFirst()
31+
}
32+
33+
return try await baseIterator.next()
34+
}
35+
36+
/// Read ahead, and store the value for later, or throw if the base iterator also throws.
37+
/// - Returns: The read-ahead value.
38+
@usableFromInline
39+
mutating func nextUnconsumed() async rethrows -> BaseIterator.Element? {
40+
let next = try await baseIterator.next()
41+
if let value = next {
42+
unconsumedBuffer.append(value)
43+
}
44+
45+
return next
46+
}
47+
48+
/// Returns if the iterator has more elements to consume.
49+
///
50+
/// If it does, the iterator saves the elements, and will deliver them immediately on the next call to `next()`
51+
/// - Returns: A Bool indicating if there is more to consume or not.
52+
@inlinable
53+
public mutating func hasMoreData() async rethrows -> Bool {
54+
guard unconsumedBuffer.isEmpty else {
55+
return true
56+
}
57+
58+
return try await nextUnconsumed() != nil
59+
}
60+
}
61+
62+
#endif
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//
2+
// AsyncBufferedIteratorTests.swift
3+
// AsyncSequenceReader
4+
//
5+
// Created by Dimitri Bouniol on 2021-11-16.
6+
// Copyright © 2021 Mochi Development, Inc. All rights reserved.
7+
//
8+
9+
import XCTest
10+
@testable import AsyncSequenceReader
11+
12+
final class AsyncBufferedIteratorTests: XCTestCase {
13+
func testBufferIteratorFromStream() async throws {
14+
let testStream = AsyncStream<Int> { continuation in
15+
for value in 0..<10 {
16+
continuation.yield(value)
17+
}
18+
continuation.finish()
19+
}
20+
21+
var iterator = testStream.makeAsyncIterator()
22+
23+
await AsyncXCTAssertEqual(await iterator.next(), 0)
24+
await AsyncXCTAssertEqual(await iterator.next(), 1)
25+
26+
iterator = testStream.makeAsyncIterator()
27+
28+
await AsyncXCTAssertEqual(await iterator.next(), 2)
29+
await AsyncXCTAssertEqual(await iterator.next(), 3)
30+
31+
var bufferedIterator = AsyncBufferedIterator(iterator)
32+
33+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 4)
34+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
35+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
36+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
37+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 5)
38+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 6)
39+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 7)
40+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 8)
41+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
42+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 9)
43+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
44+
await AsyncXCTAssertEqual(await bufferedIterator.next(), nil)
45+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
46+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
47+
await AsyncXCTAssertEqual(await bufferedIterator.next(), nil)
48+
}
49+
50+
func testBufferIteratorFromTestSequence() async throws {
51+
let testStream = TestSequence(base: 0..<10)
52+
53+
var iterator = testStream.makeAsyncIterator()
54+
55+
await AsyncXCTAssertEqual(await iterator.next(), 0)
56+
await AsyncXCTAssertEqual(await iterator.next(), 1)
57+
58+
iterator = testStream.makeAsyncIterator()
59+
60+
await AsyncXCTAssertEqual(await iterator.next(), 0)
61+
await AsyncXCTAssertEqual(await iterator.next(), 1)
62+
63+
var bufferedIterator = AsyncBufferedIterator(iterator)
64+
65+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 2)
66+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 3)
67+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 4)
68+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
69+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
70+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
71+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 5)
72+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 6)
73+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 7)
74+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 8)
75+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
76+
await AsyncXCTAssertEqual(await bufferedIterator.next(), 9)
77+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
78+
await AsyncXCTAssertEqual(await bufferedIterator.next(), nil)
79+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
80+
await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
81+
await AsyncXCTAssertEqual(await bufferedIterator.next(), nil)
82+
}
83+
84+
func testReadSequenceFromThrowingTestSequence() async throws {
85+
let testStream = ThrowingTestSequence(base: 0..<10)
86+
87+
var iterator = testStream.makeAsyncIterator()
88+
89+
try await AsyncXCTAssertEqual(await iterator.next(), 0)
90+
try await AsyncXCTAssertEqual(await iterator.next(), 1)
91+
92+
iterator = testStream.makeAsyncIterator()
93+
94+
try await AsyncXCTAssertEqual(await iterator.next(), 0)
95+
try await AsyncXCTAssertEqual(await iterator.next(), 1)
96+
97+
var bufferedIterator = AsyncBufferedIterator(iterator)
98+
99+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 2)
100+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 3)
101+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 4)
102+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
103+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
104+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
105+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 5)
106+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 6)
107+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 7)
108+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 8)
109+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), true)
110+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), 9)
111+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
112+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), nil)
113+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
114+
try await AsyncXCTAssertEqual(await bufferedIterator.hasMoreData(), false)
115+
try await AsyncXCTAssertEqual(await bufferedIterator.next(), nil)
116+
}
117+
}

0 commit comments

Comments
 (0)