87 lines · 3.1 KB · Swift
RawDownload
import Foundation /// Canonical adaptive-refresh decision table shared by the app and offline replay tooling./// Platform adapters normalize their thermal signals before calling this type; thresholds and/// delays live here only.package struct AdaptiveRefreshPolicyCore: Sendable {    package struct Input: Sendable, Equatable {        package let now: Date        package let lastMenuOpenAt: Date?        package let lowPowerModeEnabled: Bool        package let thermalPressure: ThermalPressure         package init(            now: Date,            lastMenuOpenAt: Date?,            lowPowerModeEnabled: Bool,            thermalPressure: ThermalPressure)        {            self.now = now            self.lastMenuOpenAt = lastMenuOpenAt            self.lowPowerModeEnabled = lowPowerModeEnabled            self.thermalPressure = thermalPressure        }    }     package enum ThermalPressure: Sendable, Equatable {        case nominal        case constrained    }     package enum Reason: String, Sendable, Equatable {        case recentInteraction        case warm        case idle        case longIdle        case constrained    }     package struct Decision: Sendable, Equatable {        package let delay: Duration        package let reason: Reason         fileprivate init(delay: Duration, reason: Reason) {            self.delay = delay            self.reason = reason        }    }     private static let recentInteractionThreshold: TimeInterval = 5 * 60    private static let warmThreshold: TimeInterval = 60 * 60    private static let idleThreshold: TimeInterval = 4 * 60 * 60     private static let recentInteractionDelay: Duration = .seconds(2 * 60)    private static let warmDelay: Duration = .seconds(5 * 60)    private static let idleDelay: Duration = .seconds(15 * 60)    private static let longIdleDelay: Duration = .seconds(30 * 60)    private static let constrainedDelay: Duration = .seconds(30 * 60)     /// Representative cadence for consumers that need one interval but cannot access live state.    package static let nominalIntervalForHeuristics: TimeInterval = 5 * 60     package init() {}     package func nextDelay(for input: Input) -> Decision {        if input.lowPowerModeEnabled || input.thermalPressure == .constrained {            return Decision(delay: Self.constrainedDelay, reason: .constrained)        }         guard let lastMenuOpenAt = input.lastMenuOpenAt else {            return Decision(delay: Self.longIdleDelay, reason: .longIdle)        }         // A future or clock-adjusted timestamp yields a negative age, which reads as recent.        let age = input.now.timeIntervalSince(lastMenuOpenAt)         if age <= Self.recentInteractionThreshold {            return Decision(delay: Self.recentInteractionDelay, reason: .recentInteraction)        }        if age <= Self.warmThreshold {            return Decision(delay: Self.warmDelay, reason: .warm)        }        if age < Self.idleThreshold {            return Decision(delay: Self.idleDelay, reason: .idle)        }        return Decision(delay: Self.longIdleDelay, reason: .longIdle)    }}