// // AsyncGate.swift // Meshtastic // // Created by Jake on 8/20/25. // import Foundation actor AsyncGate { private var waiters: [UUID: CheckedContinuation] = [:] private var isOpen = false /// Wait until the gate is opened. Respects cancellation. func wait() async throws { if isOpen { return } let id = UUID() try await withTaskCancellationHandler { try await withCheckedThrowingContinuation { (cont: CheckedContinuation) in waiters[id] = cont } } onCancel: { Task { [weak self] in await self?.cancelWaiter(id: id) } } } /// Opens the gate, resuming all current waiters. func open() { isOpen = true for (_, cont) in waiters { cont.resume() } waiters.removeAll() } /// Cancels all current waiters with `CancellationError`. func cancelAll() { for (_, cont) in waiters { cont.resume(throwing: CancellationError()) } waiters.removeAll() } /// Fails all current waiters with the provided error. /// - Parameter error: The error to throw to all waiters. func throwAll(_ error: Error) { for (_, cont) in waiters { cont.resume(throwing: error) } waiters.removeAll() } /// Alias for `throwAll(_:)` for readability. func fail(_ error: Error) { throwAll(error) } /// Resets the gate back to closed. /// Future waiters will suspend again until `open()` is called. func reset() { isOpen = false } private func cancelWaiter(id: UUID) { if let cont = waiters.removeValue(forKey: id) { cont.resume(throwing: CancellationError()) } } }