You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
52 lines
1.8 KiB
52 lines
1.8 KiB
/// Decides when a receiver counts as stalled and when to try reconnecting.
|
|
///
|
|
/// Frames and a confirmed connection both count as signs of life: some senders (e.g. NDI Test Patterns)
|
|
/// send a still image only once, which is not an outage.
|
|
public struct ConnectionWatchdog: Sendable, Equatable {
|
|
public enum State: Equatable, Sendable {
|
|
case connecting
|
|
case live
|
|
/// No frames for a while. `retryIn` is the time until the next reconnect attempt.
|
|
case stalled(retryIn: Double)
|
|
}
|
|
|
|
public var stallAfter: Double
|
|
public var retryInterval: Double
|
|
|
|
private var lastFrame: Double?
|
|
private var lastAlive: Double?
|
|
private var lastRetry: Double?
|
|
private let startedAt: Double
|
|
|
|
public init(now: Double, stallAfter: Double = 2, retryInterval: Double = 3) {
|
|
self.startedAt = now
|
|
self.stallAfter = stallAfter
|
|
self.retryInterval = retryInterval
|
|
}
|
|
|
|
public mutating func frameArrived(at now: Double) {
|
|
lastFrame = now
|
|
connectionConfirmed(at: now)
|
|
}
|
|
|
|
/// The sender still holds a connection to us, even if no new frames arrive.
|
|
public mutating func connectionConfirmed(at now: Double) {
|
|
lastAlive = now
|
|
lastRetry = nil
|
|
}
|
|
|
|
public func state(at now: Double) -> State {
|
|
let reference = lastAlive ?? startedAt
|
|
guard now - reference >= stallAfter else { return lastFrame == nil ? .connecting : .live }
|
|
let since = lastRetry ?? reference + stallAfter
|
|
return .stalled(retryIn: max(0, retryInterval - (now - since)))
|
|
}
|
|
|
|
/// Returns true when a reconnect should be attempted now, and records the attempt.
|
|
public mutating func shouldRetry(at now: Double) -> Bool {
|
|
guard case .stalled(let retryIn) = state(at: now), retryIn == 0 else { return false }
|
|
lastRetry = now
|
|
return true
|
|
}
|
|
}
|