Skip to content

Commit 1874989

Browse files
authored
#113 Define the first element of throttled sequences by not exceeding the rate (and fix a expectation misalignment of events) (#133)
* #113 Define the first element of throttled sequences by not exceeding the rate (and fix a expectation misalignment of events) * Update guide for throttle referencing the first element and duration measurement
1 parent 6971c8f commit 1874989

File tree

4 files changed

+15
-10
lines changed

4 files changed

+15
-10
lines changed

Guides/Throttle.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ extension AsyncThrottleSequence.Iterator: Sendable
7777

7878
The `AsyncThrottleSequence` and its `Iterator` are conditionally `Sendable` if the base types comprising it are `Sendable`.
7979

80+
The time in which events are measured are from the previous emission if present. If a duration has elapsed between the last emission and the point in time the throttle is measured then that duration is counted as elapsed. The first element is considered not throttled because no interval can be constructed from the start to the first element.
81+
8082
## Alternatives Considered
8183

8284
It was considered to only provide the "latest" style APIs, however the reduction version grants more flexibility and can act as a funnel to the implementations of `latest`.

Sources/AsyncAlgorithms/AsyncThrottleSequence.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ extension AsyncThrottleSequence: AsyncSequence {
6464
/// The iterator for an `AsyncThrottleSequence` instance.
6565
public struct Iterator: AsyncIteratorProtocol {
6666
var base: Base.AsyncIterator
67+
var last: C.Instant?
6768
let interval: C.Instant.Duration
6869
let clock: C
6970
let reducing: @Sendable (Reduced?, Base.Element) async -> Reduced
@@ -77,13 +78,15 @@ extension AsyncThrottleSequence: AsyncSequence {
7778

7879
public mutating func next() async rethrows -> Reduced? {
7980
var reduced: Reduced?
80-
let start = clock.now
81+
let start = last ?? clock.now
8182
repeat {
8283
guard let element = try await base.next() else {
8384
return nil
8485
}
8586
let reduction = await reducing(reduced, element)
86-
if start.duration(to: clock.now) >= interval {
87+
let now = clock.now
88+
if start.duration(to: now) >= interval || last == nil {
89+
last = now
8790
return reduction
8891
} else {
8992
reduced = reduction

Sources/AsyncSequenceValidation/Expectation.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ extension AsyncSequenceValidationDiagram {
4242
}
4343

4444
func reconstitute<Theme: AsyncSequenceValidationTheme>(_ events: [Clock.Instant : [Result<String?, Error>]], theme: Theme, end: Clock.Instant) -> String {
45-
var now = Clock.Instant(when: .zero)
45+
var now = Clock.Instant(when: .steps(1)) // adjust for the offset index
4646
var reconstituted = ""
4747
while now <= end {
4848
if let results = events[now] {

Tests/AsyncAlgorithmsTests/TestThrottle.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,47 +49,47 @@ final class TestThrottle: XCTestCase {
4949
validate {
5050
"abcdefghijk|"
5151
$0.inputs[0].throttle(for: .steps(2), clock: $0.clock)
52-
"-b-d-f-h-j-|"
52+
"a-c-e-g-i-k|"
5353
}
5454
}
5555

5656
func test_rate_2_leading_edge() {
5757
validate {
5858
"abcdefghijk|"
5959
$0.inputs[0].throttle(for: .steps(2), clock: $0.clock, latest: false)
60-
"-a-c-e-g-i-|"
60+
"a-b-d-f-h-j|"
6161
}
6262
}
6363

6464
func test_rate_3() {
6565
validate {
6666
"abcdefghijk|"
6767
$0.inputs[0].throttle(for: .steps(3), clock: $0.clock)
68-
"--c--f--i--|"
68+
"a--d--g--j-|"
6969
}
7070
}
7171

7272
func test_rate_3_leading_edge() {
7373
validate {
7474
"abcdefghijk|"
7575
$0.inputs[0].throttle(for: .steps(3), clock: $0.clock, latest: false)
76-
"--a--d--g--|"
76+
"a--b--e--h-|"
7777
}
7878
}
7979

8080
func test_throwing() {
8181
validate {
8282
"abcdef^hijk|"
8383
$0.inputs[0].throttle(for: .steps(2), clock: $0.clock)
84-
"-b-d-f^"
84+
"a-c-e-^"
8585
}
8686
}
8787

8888
func test_throwing_leading_edge() {
8989
validate {
9090
"abcdef^hijk|"
9191
$0.inputs[0].throttle(for: .steps(2), clock: $0.clock, latest: false)
92-
"-a-c-e^"
92+
"a-b-d-^"
9393
}
9494
}
9595

@@ -121,7 +121,7 @@ final class TestThrottle: XCTestCase {
121121
validate {
122122
"-a-b-c-d-e-f-g-h-i-j-k-|"
123123
$0.inputs[0].throttle(for: .steps(3), clock: $0.clock)
124-
"---b---d---f---h---j---|"
124+
"-a---c---e---g---i---k-|"
125125
}
126126
}
127127
}

0 commit comments

Comments
 (0)