Files
Alex Hoppen 6074529dac Manually fix swift-format lint rules
- I think there are valid calls with mixing inline and trailing closures (notably `Debouncer`), so I’m considering whether we should disable that rule.
- The `forEach` rule is a little annoying because we have `forEach` on `SKDResponseArray`. But it caught two cases of using `forEach` on arrays, so I think it’s worth keeping.
2025-08-11 09:31:09 +02:00

94 lines
3.8 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
/// Debounces calls to a function/closure. If multiple calls to the closure are made, it allows aggregating the
/// parameters.
package actor Debouncer<Parameter: Sendable> {
/// How long to wait for further `scheduleCall` calls before committing to actually calling `makeCall`.
private let debounceDuration: Duration
/// When `scheduleCall` is called while another `scheduleCall` was waiting to commit its call, combines the parameters
/// of those two calls.
///
/// ### Example
///
/// Two `scheduleCall` calls that are made within a time period shorter than `debounceDuration` like the following
/// ```swift
/// debouncer.scheduleCall(5)
/// debouncer.scheduleCall(10)
/// ```
/// will call `combineParameters(5, 10)`
private let combineParameters: (Parameter, Parameter) -> Parameter
/// After the debounce duration has elapsed, commit the call.
private let makeCall: (Parameter) async -> Void
/// In the time between the call to `scheduleCall` and the call actually being committed (ie. in the time that the
/// call can be debounced), the task that would commit the call (unless cancelled), the parameter with which this
/// call should be made and the time at which the call should be made. Keeping track of the time ensures that we don't
/// indefinitely debounce if a new `scheduleCall` is made every 0.4s but we debounce for 0.5s.
private var inProgressData: (parameter: Parameter, targetDate: ContinuousClock.Instant, task: Task<Void, Never>)?
package init(
debounceDuration: Duration,
combineResults: @escaping (Parameter, Parameter) -> Parameter,
makeCall: @Sendable @escaping (Parameter) async -> Void
) {
self.debounceDuration = debounceDuration
self.combineParameters = combineResults
self.makeCall = makeCall
}
/// Schedule a debounced call. If `scheduleCall` is called within `debounceDuration`, the parameters of the two
/// `scheduleCall` calls will be combined using `combineParameters` and the new debounced call will be scheduled
/// `debounceDuration` after the second `scheduleCall` call.
package func scheduleCall(_ parameter: Parameter) {
var parameter = parameter
var targetDate = ContinuousClock.now + debounceDuration
if let (inProgressParameter, inProgressTargetDate, inProgressTask) = inProgressData {
inProgressTask.cancel()
parameter = combineParameters(inProgressParameter, parameter)
targetDate = inProgressTargetDate
}
let task = Task {
do {
try await Task.sleep(until: targetDate)
try Task.checkCancellation()
} catch {
return
}
await self.flush()
}
inProgressData = (parameter, ContinuousClock.now + debounceDuration, task)
}
/// If any debounced calls are in progress, make them now, even if the debounce duration hasn't expired yet.
package func flush() async {
guard let inProgressDataValue = inProgressData else {
return
}
inProgressData = nil
let parameter = inProgressDataValue.parameter
await makeCall(parameter)
}
}
extension Debouncer<Void> {
package init(debounceDuration: Duration, _ makeCall: @Sendable @escaping () async -> Void) {
self.init(debounceDuration: debounceDuration, combineResults: { _, _ in }, makeCall: makeCall)
}
package func scheduleCall() {
self.scheduleCall(())
}
}