import Foundation /// 后台补全优先级策略 /// /// 控制后台元数据解析的优先级排序,确保当前阅读区段优先补全 struct RDEPUBBackgroundPriorityPolicy { /// 热区半径:当前章节附近的范围 let hotRadius: Int /// 温区半径:远距跳转锚点附近的范围 let warmRadius: Int /// 最大温区跳转锚点数量 let maxWarmJumpAnchors: Int /// 冷区份额:每轮补全中冷区任务的比例 let coldLaneShare: Double /// 默认策略 static let `default` = RDEPUBBackgroundPriorityPolicy( hotRadius: 24, warmRadius: 96, maxWarmJumpAnchors: 2, coldLaneShare: 0.15 ) /// 根据总章节数动态计算策略 static func adaptive(totalBuildableChapters: Int) -> RDEPUBBackgroundPriorityPolicy { let hotRadius = min(max(12, Int(sqrt(Double(totalBuildableChapters)))), 48) let warmRadius = min(max(hotRadius * 3, 32), 192) return RDEPUBBackgroundPriorityPolicy( hotRadius: hotRadius, warmRadius: warmRadius, maxWarmJumpAnchors: 2, coldLaneShare: 0.15 ) } } /// 优先级带 enum RDEPUBPriorityBand: Int, Comparable { case hot = 0 case warmPrimary = 1 case warmSecondary = 2 case cold = 3 static func < (lhs: RDEPUBPriorityBand, rhs: RDEPUBPriorityBand) -> Bool { lhs.rawValue < rhs.rawValue } } /// 温区跳转锚点 struct RDEPUBWarmJumpAnchor { let spineIndex: Int let timestamp: CFAbsoluteTime let sequenceNumber: Int } /// 元数据解析工作项 struct RDEPUBMetadataParseWorkItem { let spineIndex: Int let generation: Int let priorityBand: RDEPUBPriorityBand /// 排序键 var sortKey: (bandRank: Int, distanceToCurrent: Int, distanceToNewestJump: Int, spineIndex: Int) { (priorityBand.rawValue, 0, 0, spineIndex) } } /// 后台补全优先级管理器 final class RDEPUBBackgroundPriorityManager { private unowned let context: RDEPUBReaderContext /// 当前策略 private(set) var policy: RDEPUBBackgroundPriorityPolicy /// 温区跳转锚点列表(按时间倒序) private var warmAnchors: [RDEPUBWarmJumpAnchor] = [] /// 当前 generation private(set) var currentGeneration: Int = 0 /// 冷区游标 private var coldCursor: Int = 0 init(context: RDEPUBReaderContext) { self.context = context self.policy = .default } /// 更新策略 func updatePolicy(_ newPolicy: RDEPUBBackgroundPriorityPolicy) { policy = newPolicy } /// 添加温区跳转锚点 func addWarmAnchor(spineIndex: Int) { let anchor = RDEPUBWarmJumpAnchor( spineIndex: spineIndex, timestamp: CFAbsoluteTimeGetCurrent(), sequenceNumber: currentGeneration ) warmAnchors.insert(anchor, at: 0) // 保留最近 N 个锚点 if warmAnchors.count > policy.maxWarmJumpAnchors { warmAnchors = Array(warmAnchors.prefix(policy.maxWarmJumpAnchors)) } currentGeneration += 1 coldCursor = 0 // 重置冷区游标 RDEPUBBackgroundTrace.log( "PriorityManager", "addWarmAnchor spine=\(spineIndex) generation=\(currentGeneration) anchors=\(warmAnchors.count)" ) } /// 生成优先级排序的 spineIndex 列表 func makeMetadataPriorityOrder( allBuildableIndices: [Int], currentSpineIndex: Int?, cachedSpineIndices: Set ) -> [Int] { let uncachedIndices = allBuildableIndices.filter { !cachedSpineIndices.contains($0) } guard !uncachedIndices.isEmpty else { return [] } // 为每个 spineIndex 计算优先级带 let items = uncachedIndices.map { spineIndex -> (spineIndex: Int, band: RDEPUBPriorityBand) in let band = classifySpineIndex( spineIndex: spineIndex, currentSpineIndex: currentSpineIndex ) return (spineIndex, band) } // 排序 let sorted = items.sorted { lhs, rhs in // 先按优先级带排序 if lhs.band != rhs.band { return lhs.band < rhs.band } // 同一带内按距离排序 let lhsDistanceToCurrent = currentSpineIndex.map { abs(lhs.spineIndex - $0) } ?? Int.max let rhsDistanceToCurrent = currentSpineIndex.map { abs(rhs.spineIndex - $0) } ?? Int.max if lhsDistanceToCurrent != rhsDistanceToCurrent { return lhsDistanceToCurrent < rhsDistanceToCurrent } // 距离相同时按 spineIndex 排序 return lhs.spineIndex < rhs.spineIndex } return sorted.map { $0.spineIndex } } /// 分类 spineIndex 到优先级带 private func classifySpineIndex( spineIndex: Int, currentSpineIndex: Int? ) -> RDEPUBPriorityBand { // 检查是否在热区 if let current = currentSpineIndex { let distance = abs(spineIndex - current) if distance <= policy.hotRadius { return .hot } } // 检查是否在温区 for (index, anchor) in warmAnchors.enumerated() { let distance = abs(spineIndex - anchor.spineIndex) if distance <= policy.warmRadius { return index == 0 ? .warmPrimary : .warmSecondary } } // 冷区 return .cold } /// 获取当前温区锚点(用于后台任务调度) func currentWarmAnchors() -> [RDEPUBWarmJumpAnchor] { warmAnchors } /// 重置状态 func reset() { warmAnchors.removeAll() currentGeneration = 0 coldCursor = 0 } }