Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Throttle to use the same engine style as Debounce #290

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,19 @@
extension AsyncSequence {
/// Create a rate-limited `AsyncSequence` by emitting values at most every specified interval.
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public func throttle<C: Clock, Reduced>(for interval: C.Instant.Duration, clock: C, reducing: @Sendable @escaping (Reduced?, Element) async -> Reduced) -> AsyncThrottleSequence<Self, C, Reduced> {
public func throttle<C: Clock, Reduced>(for interval: C.Instant.Duration, clock: C, reducing: @Sendable @escaping (Reduced?, Element) async -> Reduced) -> AsyncThrottleSequence<Self, C, Reduced> where Self: Sendable {
AsyncThrottleSequence(self, interval: interval, clock: clock, reducing: reducing)
}

/// Create a rate-limited `AsyncSequence` by emitting values at most every specified interval.
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public func throttle<Reduced>(for interval: Duration, reducing: @Sendable @escaping (Reduced?, Element) async -> Reduced) -> AsyncThrottleSequence<Self, ContinuousClock, Reduced> {
public func throttle<Reduced>(for interval: Duration, reducing: @Sendable @escaping (Reduced?, Element) async -> Reduced) -> AsyncThrottleSequence<Self, ContinuousClock, Reduced> where Self: Sendable {
throttle(for: interval, clock: .continuous, reducing: reducing)
}

/// Create a rate-limited `AsyncSequence` by emitting values at most every specified interval.
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public func throttle<C: Clock>(for interval: C.Instant.Duration, clock: C, latest: Bool = true) -> AsyncThrottleSequence<Self, C, Element> {
public func throttle<C: Clock>(for interval: C.Instant.Duration, clock: C, latest: Bool = true) -> AsyncThrottleSequence<Self, C, Element> where Self: Sendable {
throttle(for: interval, clock: clock) { previous, element in
if latest {
return element
Expand All @@ -36,14 +36,14 @@ extension AsyncSequence {

/// Create a rate-limited `AsyncSequence` by emitting values at most every specified interval.
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public func throttle(for interval: Duration, latest: Bool = true) -> AsyncThrottleSequence<Self, ContinuousClock, Element> {
public func throttle(for interval: Duration, latest: Bool = true) -> AsyncThrottleSequence<Self, ContinuousClock, Element> where Self: Sendable {
throttle(for: interval, clock: .continuous, latest: latest)
}
}

/// A rate-limited `AsyncSequence` by emitting values at most every specified interval.
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
public struct AsyncThrottleSequence<Base: AsyncSequence, C: Clock, Reduced> {
public struct AsyncThrottleSequence<Base: AsyncSequence & Sendable, C: Clock, Reduced> {
let base: Base
let interval: C.Instant.Duration
let clock: C
Expand All @@ -60,44 +60,47 @@ public struct AsyncThrottleSequence<Base: AsyncSequence, C: Clock, Reduced> {
@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension AsyncThrottleSequence: AsyncSequence {
public typealias Element = Reduced

/// The iterator for an `AsyncThrottleSequence` instance.

public func makeAsyncIterator() -> Iterator {
let storage = ThrottleStorage(
base,
interval: interval,
clock: clock,
reducing: reducing
)
return Iterator(storage: storage)
}
}

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
extension AsyncThrottleSequence {
public struct Iterator: AsyncIteratorProtocol {
var base: Base.AsyncIterator
var last: C.Instant?
let interval: C.Instant.Duration
let clock: C
let reducing: @Sendable (Reduced?, Base.Element) async -> Reduced
final class InternalClass: Sendable {
private let storage: ThrottleStorage<Base, C, Reduced>

fileprivate init(storage: ThrottleStorage<Base, C, Reduced>) {
self.storage = storage
}

deinit {
self.storage.iteratorDeinitialized()
}

func next() async rethrows -> Element? {
try await self.storage.next()
}
}

let internalClass: InternalClass

init(_ base: Base.AsyncIterator, interval: C.Instant.Duration, clock: C, reducing: @Sendable @escaping (Reduced?, Base.Element) async -> Reduced) {
self.base = base
self.interval = interval
self.clock = clock
self.reducing = reducing
fileprivate init(storage: ThrottleStorage<Base, C, Reduced>) {
self.internalClass = InternalClass(storage: storage)
}

public mutating func next() async rethrows -> Reduced? {
var reduced: Reduced?
let start = last ?? clock.now
repeat {
guard let element = try await base.next() else {
return nil
}
let reduction = await reducing(reduced, element)
let now = clock.now
if start.duration(to: now) >= interval || last == nil {
last = now
return reduction
} else {
reduced = reduction
}
} while true
public mutating func next() async rethrows -> Element? {
try await self.internalClass.next()
}
}

public func makeAsyncIterator() -> Iterator {
Iterator(base.makeAsyncIterator(), interval: interval, clock: clock, reducing: reducing)
}
}

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
Expand Down
Loading