优化 EPUB 翻页与后台分页刷新
This commit is contained in:
@@ -56,6 +56,8 @@ final class RDEPUBMetadataParseWorker {
|
||||
|
||||
private let catalog: [(key: RDEPUBChapterCacheKey, spineIndex: Int, href: String, title: String)]
|
||||
|
||||
private let progressLogStride = 4
|
||||
|
||||
init(
|
||||
context: RDEPUBReaderContext,
|
||||
cancellationController: RDEPUBMetadataParseCancellationController,
|
||||
@@ -116,19 +118,38 @@ final class RDEPUBMetadataParseWorker {
|
||||
func start(token: UUID, restoreLocation: RDEPUBLocation?) {
|
||||
let cancellationController = self.cancellationController
|
||||
|
||||
DispatchQueue.global(qos: .utility).async { [weak self] in
|
||||
guard let self else { return }
|
||||
DispatchQueue.global(qos: .utility).async { [self] in
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"workerDispatched token=\(token.uuidString)"
|
||||
)
|
||||
let context = self.context
|
||||
guard let context,
|
||||
context.controller != nil,
|
||||
!cancellationController.isCancelled,
|
||||
context.paginationToken == token else { return }
|
||||
context.paginationToken == token else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"workerAbortedBeforeStart reason=contextUnavailableOrTokenMismatch"
|
||||
)
|
||||
return
|
||||
}
|
||||
defer { context.runtime?.paginationCoordinator.finishMetadataParseCancellationController(cancellationController) }
|
||||
guard context.controller != nil,
|
||||
!cancellationController.isCancelled,
|
||||
context.paginationToken == token else { return }
|
||||
context.paginationToken == token else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"workerAbortedAfterStart reason=contextUnavailableOrTokenMismatch"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if let restoredPageMap = self.restoreBookPageMapIfPossible() {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"restoreBookPageMapIfPossible hit totalChapters=\(restoredPageMap.totalChapters) totalPages=\(restoredPageMap.totalPages)"
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == token,
|
||||
context.controller != nil,
|
||||
@@ -175,13 +196,26 @@ final class RDEPUBMetadataParseWorker {
|
||||
|
||||
let uncachedSpineIndices = prioritizedSpineIndices
|
||||
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"waitingForInteractionCooldown elapsedSinceNavigation=\(String(format: "%.2f", context.secondsSinceLastUserNavigation())) uncached=\(uncachedSpineIndices.count)"
|
||||
)
|
||||
self.waitForReadingInteractionToSettle(cancellationController: cancellationController)
|
||||
guard !cancellationController.isCancelled,
|
||||
context.controller != nil,
|
||||
context.paginationToken == token else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"workerAbortedDuringCooldown"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"start totalBuildable=\(self.allBuildableIndices.count) cached=\(cachedSpineIndices.count) uncached=\(uncachedSpineIndices.count) concurrency=\(self.workerCount) refreshInterval=\(self.pageMapRefreshInterval)"
|
||||
)
|
||||
|
||||
let wallClockStart = CFAbsoluteTimeGetCurrent()
|
||||
var totalRenderMs: Double = 0
|
||||
var totalWriteMs: Double = 0
|
||||
@@ -283,6 +317,10 @@ final class RDEPUBMetadataParseWorker {
|
||||
resultLock.lock()
|
||||
parseState.summariesBySpineIndex[spineIndex] = renderResult
|
||||
parseState.totalResolvedCount += 1
|
||||
let resolvedCount = parseState.totalResolvedCount
|
||||
let shouldLogProgress = resolvedCount == self.allBuildableIndices.count
|
||||
|| resolvedCount == cachedSpineIndices.count + 1
|
||||
|| resolvedCount % self.progressLogStride == 0
|
||||
if parseState.totalResolvedCount - parseState.lastAppliedCount >= refreshInterval
|
||||
|| parseState.totalResolvedCount == self.allBuildableIndices.count {
|
||||
parseState.lastAppliedCount = parseState.totalResolvedCount
|
||||
@@ -290,6 +328,17 @@ final class RDEPUBMetadataParseWorker {
|
||||
}
|
||||
resultLock.unlock()
|
||||
|
||||
if shouldLogProgress {
|
||||
let progressPercent = Self.progressPercent(
|
||||
resolved: resolvedCount,
|
||||
total: self.allBuildableIndices.count
|
||||
)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"chapterReady spine=\(spineIndex) resolved=\(resolvedCount)/\(self.allBuildableIndices.count) progress=\(progressPercent)% pageCount=\(renderResult.pageCount)"
|
||||
)
|
||||
}
|
||||
|
||||
if let snapshot {
|
||||
let mergeStart = CFAbsoluteTimeGetCurrent()
|
||||
let partialMap = self.buildPageMap(summaries: snapshot)
|
||||
@@ -297,6 +346,10 @@ final class RDEPUBMetadataParseWorker {
|
||||
timingLock.lock()
|
||||
totalMergeMs += mergeElapsed
|
||||
timingLock.unlock()
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"partialMap resolvedChapters=\(snapshot.count) totalPages=\(partialMap.totalPages) mergeMs=\(Int(mergeElapsed))"
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == token,
|
||||
context.controller != nil,
|
||||
@@ -314,6 +367,10 @@ final class RDEPUBMetadataParseWorker {
|
||||
timingLock.lock()
|
||||
failedChapters += 1
|
||||
timingLock.unlock()
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"chapterFailed spine=\(spineIndex) retryScheduled=true error=\(String(describing: error))"
|
||||
)
|
||||
|
||||
self.scheduleRetry(
|
||||
spineIndex: spineIndex,
|
||||
@@ -354,6 +411,10 @@ final class RDEPUBMetadataParseWorker {
|
||||
let finalMergeStart = CFAbsoluteTimeGetCurrent()
|
||||
let pageMap = self.buildPageMap(summaries: parseState.summariesBySpineIndex)
|
||||
let finalMergeMs = Int((CFAbsoluteTimeGetCurrent() - finalMergeStart) * 1000)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"finish resolved=\(parseState.summariesBySpineIndex.count)/\(self.allBuildableIndices.count) totalPages=\(pageMap.totalPages) elapsedMs=\(wallClockMs) renderMs=\(renderTotal) writeMs=\(writeTotal) mergeMs=\(mergeTotal + finalMergeMs) failed=\(failed)"
|
||||
)
|
||||
|
||||
if let coverageStore = context.runtime?.backgroundCoverageStore {
|
||||
let resolvedSpineIndices = Set(parseState.summariesBySpineIndex.keys)
|
||||
@@ -407,10 +468,18 @@ final class RDEPUBMetadataParseWorker {
|
||||
cancellationController: RDEPUBMetadataParseCancellationController
|
||||
) {
|
||||
guard retryCount < Self.maxRetryCount else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"retryAborted spine=\(spineIndex) retryCount=\(retryCount)"
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let delay = Self.retryDelays[min(retryCount, Self.retryDelays.count - 1)]
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"retryScheduled spine=\(spineIndex) retryCount=\(retryCount + 1) delayMs=\(Int(delay * 1000))"
|
||||
)
|
||||
|
||||
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + delay) { [weak self] in
|
||||
guard let self, let context = self.context else { return }
|
||||
@@ -474,6 +543,10 @@ final class RDEPUBMetadataParseWorker {
|
||||
|
||||
if shouldRefresh {
|
||||
let partialMap = self.buildPageMap(summaries: parseState.summariesBySpineIndex)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Metadata",
|
||||
"retryPartialMap resolvedChapters=\(parseState.summariesBySpineIndex.count) totalPages=\(partialMap.totalPages)"
|
||||
)
|
||||
DispatchQueue.main.async {
|
||||
guard context.paginationToken == self.token,
|
||||
context.controller != nil,
|
||||
@@ -495,6 +568,11 @@ final class RDEPUBMetadataParseWorker {
|
||||
}
|
||||
}
|
||||
|
||||
private static func progressPercent(resolved: Int, total: Int) -> Int {
|
||||
guard total > 0 else { return 0 }
|
||||
return Int((Double(resolved) / Double(total) * 100.0).rounded())
|
||||
}
|
||||
|
||||
private func restoreBookPageMapIfPossible() -> RDEPUBBookPageMap? {
|
||||
guard let summaryDiskCache else { return nil }
|
||||
guard summaryDiskCache.isCacheComplete(keys: catalog.map(\.key)) else {
|
||||
|
||||
Reference in New Issue
Block a user