|
| 1 | +//===----------------------------------------------------------------------===// |
| 2 | +// |
| 3 | +// This source file is part of the Swift Async Algorithms open source project |
| 4 | +// |
| 5 | +// Copyright (c) 2022 Apple Inc. and the Swift project authors |
| 6 | +// Licensed under Apache License v2.0 with Runtime Library Exception |
| 7 | +// |
| 8 | +// See https://swift.org/LICENSE.txt for license information |
| 9 | +// |
| 10 | +//===----------------------------------------------------------------------===// |
| 11 | + |
| 12 | +extension AsyncSequence { |
| 13 | + /// Creates an asynchronous sequence that emits the latest element after a given quiescence period |
| 14 | + /// has elapsed by using a specified Clock. |
| 15 | + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) |
| 16 | + public func debounce<C: Clock>(for interval: C.Instant.Duration, tolerance: C.Instant.Duration? = nil, clock: C) -> AsyncDebounceSequence<Self, C> where Self: Sendable { |
| 17 | + AsyncDebounceSequence(self, interval: interval, tolerance: tolerance, clock: clock) |
| 18 | + } |
| 19 | + |
| 20 | + /// Creates an asynchronous sequence that emits the latest element after a given quiescence period |
| 21 | + /// has elapsed. |
| 22 | + @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) |
| 23 | + public func debounce(for interval: Duration, tolerance: Duration? = nil) -> AsyncDebounceSequence<Self, ContinuousClock> where Self: Sendable { |
| 24 | + self.debounce(for: interval, tolerance: tolerance, clock: .continuous) |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +/// An `AsyncSequence` that emits the latest element after a given quiescence period |
| 29 | +/// has elapsed. |
| 30 | +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) |
| 31 | +public struct AsyncDebounceSequence<Base: AsyncSequence, C: Clock>: Sendable where Base: Sendable { |
| 32 | + /// This class is needed to hook the deinit to observe once all references to the ``AsyncDebounceSequence`` are dropped. |
| 33 | + /// |
| 34 | + /// If we get move-only types we should be able to drop this class and use the `deinit` of the ``AsyncDebounceSequence`` struct itself. |
| 35 | + final class InternalClass: Sendable { |
| 36 | + fileprivate let storage: DebounceStorage<Base, C> |
| 37 | + |
| 38 | + fileprivate init(storage: DebounceStorage<Base, C>) { |
| 39 | + self.storage = storage |
| 40 | + } |
| 41 | + |
| 42 | + deinit { |
| 43 | + storage.sequenceDeinitialized() |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + /// The internal class to hook the `deinit`. |
| 48 | + let internalClass: InternalClass |
| 49 | + |
| 50 | + /// The underlying storage |
| 51 | + fileprivate var storage: DebounceStorage<Base, C> { |
| 52 | + self.internalClass.storage |
| 53 | + } |
| 54 | + |
| 55 | + /// Initializes a new ``AsyncDebounceSequence``. |
| 56 | + /// |
| 57 | + /// - Parameters: |
| 58 | + /// - base: The base sequence. |
| 59 | + /// - interval: The interval to debounce. |
| 60 | + /// - tolerance: The tolerance of the clock. |
| 61 | + /// - clock: The clock. |
| 62 | + public init(_ base: Base, interval: C.Instant.Duration, tolerance: C.Instant.Duration?, clock: C) { |
| 63 | + let storage = DebounceStorage<Base, C>( |
| 64 | + base: base, |
| 65 | + interval: interval, |
| 66 | + tolerance: tolerance, |
| 67 | + clock: clock |
| 68 | + ) |
| 69 | + self.internalClass = .init(storage: storage) |
| 70 | + } |
| 71 | +} |
| 72 | + |
| 73 | +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) |
| 74 | +extension AsyncDebounceSequence: AsyncSequence { |
| 75 | + public typealias Element = Base.Element |
| 76 | + |
| 77 | + public func makeAsyncIterator() -> AsyncIterator { |
| 78 | + AsyncIterator(storage: self.internalClass.storage) |
| 79 | + } |
| 80 | +} |
| 81 | + |
| 82 | +@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *) |
| 83 | +extension AsyncDebounceSequence { |
| 84 | + public struct AsyncIterator: AsyncIteratorProtocol { |
| 85 | + /// This class is needed to hook the deinit to observe once all references to the ``AsyncIterator`` are dropped. |
| 86 | + /// |
| 87 | + /// If we get move-only types we should be able to drop this class and use the `deinit` of the ``AsyncIterator`` struct itself. |
| 88 | + final class InternalClass: Sendable { |
| 89 | + private let storage: DebounceStorage<Base, C> |
| 90 | + |
| 91 | + fileprivate init(storage: DebounceStorage<Base, C>) { |
| 92 | + self.storage = storage |
| 93 | + self.storage.iteratorInitialized() |
| 94 | + } |
| 95 | + |
| 96 | + deinit { |
| 97 | + self.storage.iteratorDeinitialized() |
| 98 | + } |
| 99 | + |
| 100 | + func next() async rethrows -> Element? { |
| 101 | + try await self.storage.next() |
| 102 | + } |
| 103 | + } |
| 104 | + |
| 105 | + let internalClass: InternalClass |
| 106 | + |
| 107 | + fileprivate init(storage: DebounceStorage<Base, C>) { |
| 108 | + self.internalClass = InternalClass(storage: storage) |
| 109 | + } |
| 110 | + |
| 111 | + public mutating func next() async rethrows -> Element? { |
| 112 | + try await self.internalClass.next() |
| 113 | + } |
| 114 | + } |
| 115 | +} |
0 commit comments