ReadViewSDK/Sources/RDEpubReaderView/EPUBUI/ReaderController/RDEPUBBackgroundPriorityPolicy.swift
shenlei d7fcda345d refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView
- Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/
- Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec
- Update Podfile, demo project, and CocoaPods config for new pod name
- Delete old RDReaderView pod support files from ReadViewDemo/Pods
- Add new RDEpubReaderView pod support files
- Update documentation (API ref, architecture, UML, conventions, etc.)
- Add FixedLayoutRotationTests
- Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
2026-07-10 19:44:53 +09:00

179 lines
4.9 KiB
Swift

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] = []
private let warmAnchorsLock = NSLock()
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
)
warmAnchorsLock.lock()
warmAnchors.insert(anchor, at: 0)
if warmAnchors.count > policy.maxWarmJumpAnchors {
warmAnchors = Array(warmAnchors.prefix(policy.maxWarmJumpAnchors))
}
warmAnchorsLock.unlock()
currentGeneration += 1
coldCursor = 0
}
func makeMetadataPriorityOrder(
allBuildableIndices: [Int],
currentSpineIndex: Int?,
cachedSpineIndices: Set<Int>
) -> [Int] {
let uncachedIndices = allBuildableIndices.filter { !cachedSpineIndices.contains($0) }
guard !uncachedIndices.isEmpty else { return [] }
// M-03: Snapshot warmAnchors under lock since it's written on main thread
// and read on background thread.
warmAnchorsLock.lock()
let warmAnchorsSnapshot = warmAnchors
warmAnchorsLock.unlock()
let items = uncachedIndices.map { spineIndex -> (spineIndex: Int, band: RDEPUBPriorityBand) in
let band = classifySpineIndex(
spineIndex: spineIndex,
currentSpineIndex: currentSpineIndex,
warmAnchors: warmAnchorsSnapshot
)
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
}
return lhs.spineIndex < rhs.spineIndex
}
return sorted.map { $0.spineIndex }
}
private func classifySpineIndex(
spineIndex: Int,
currentSpineIndex: Int?,
warmAnchors: [RDEPUBWarmJumpAnchor]
) -> 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] {
warmAnchorsLock.lock()
defer { warmAnchorsLock.unlock() }
return warmAnchors
}
func reset() {
warmAnchorsLock.lock()
warmAnchors.removeAll()
warmAnchorsLock.unlock()
currentGeneration = 0
coldCursor = 0
}
}