Download this file
| 1 | import Foundation |
| 2 | |
| 3 | /// Serializes refreshes so bursts of events do not start overlapping fetches. |
| 4 | /// Calls arriving during a pass wait for a trailing pass that starts after them. |
| 5 | /// Use one runner per operation: trailing passes reuse the first caller's closure. |
| 6 | /// Cancelling a waiter does not cancel the shared work. Do not call this runner |
| 7 | /// recursively from its pass, since that would wait for itself. |
| 8 | @MainActor |
| 9 | public final class CoalescedRunner { |
| 10 | private var current: Task<Void, Never>? |
| 11 | private var queued = false |
| 12 | |
| 13 | public init() {} |
| 14 | |
| 15 | public func run(_ pass: @escaping @MainActor () async -> Void) async { |
| 16 | if let current { |
| 17 | queued = true |
| 18 | await current.value |
| 19 | return |
| 20 | } |
| 21 | let task = Task { @MainActor [weak self] in |
| 22 | repeat { |
| 23 | self?.queued = false |
| 24 | await pass() |
| 25 | } while self?.queued == true |
| 26 | // No suspension after clearing: a later caller must never attach |
| 27 | // to completed work and return without a fresh pass. |
| 28 | self?.current = nil |
| 29 | } |
| 30 | current = task |
| 31 | await task.value |
| 32 | } |
| 33 | } |
| 34 | |