优化 EPUB 翻页与后台分页刷新
This commit is contained in:
@@ -21,6 +21,8 @@ final class RDEPUBPresentationRuntime {
|
||||
|
||||
private unowned let reconciliationCoordinator: RDEPUBPageMapReconciliationCoordinator
|
||||
|
||||
private var pendingCommitRetryWorkItem: DispatchWorkItem?
|
||||
|
||||
init(
|
||||
context: RDEPUBReaderContext,
|
||||
locationCoordinator: RDEPUBReaderLocationCoordinator,
|
||||
@@ -62,11 +64,22 @@ final class RDEPUBPresentationRuntime {
|
||||
guard let readerView = context.readerView,
|
||||
let controller = context.controller else { return }
|
||||
|
||||
guard !controller.isRepaginating else { return }
|
||||
guard !readerView.isPageCurlTransitioning else {
|
||||
guard !context.pendingPageMapUpdates.isEmpty else {
|
||||
cancelPendingCommitRetry()
|
||||
return
|
||||
}
|
||||
|
||||
guard !controller.isRepaginating else {
|
||||
schedulePendingCommitRetry()
|
||||
return
|
||||
}
|
||||
guard !readerView.isPageCurlTransitioning else {
|
||||
schedulePendingCommitRetry()
|
||||
return
|
||||
}
|
||||
|
||||
cancelPendingCommitRetry()
|
||||
|
||||
let rankedUpdates = rankedPendingPageMapUpdates()
|
||||
for (index, update) in rankedUpdates {
|
||||
if commitPendingPageMapUpdate(
|
||||
@@ -75,9 +88,16 @@ final class RDEPUBPresentationRuntime {
|
||||
readerView: readerView,
|
||||
controller: controller
|
||||
) {
|
||||
if !context.pendingPageMapUpdates.isEmpty {
|
||||
schedulePendingCommitRetry()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if !context.pendingPageMapUpdates.isEmpty {
|
||||
schedulePendingCommitRetry()
|
||||
}
|
||||
}
|
||||
|
||||
func queueExtendedPartialPageMap(
|
||||
@@ -139,19 +159,40 @@ final class RDEPUBPresentationRuntime {
|
||||
controller: RDEPUBReaderController
|
||||
) {
|
||||
let currentLocation = locationCoordinator.currentVisibleLocation()
|
||||
let livePageBeforeApply = readerView.currentPage + 1
|
||||
|
||||
context.textBook = nil
|
||||
applyPageMapToLiveModel(newPageMap)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"applyFullPageMapReplacement livePageBeforeApply=\(livePageBeforeApply) totalPages=\(newPageMap.totalPages) totalChapters=\(newPageMap.totalChapters)"
|
||||
)
|
||||
|
||||
if let currentLocation {
|
||||
if rebindVisibleLocation(currentLocation, readerView: readerView, controller: controller) == false {
|
||||
let newPageNumber = controller.pageNumber(for: currentLocation) ?? (readerView.currentPage + 1)
|
||||
let newPage = max(0, newPageNumber - 1)
|
||||
rebindVisiblePage(
|
||||
to: newPage,
|
||||
readerView: readerView
|
||||
)
|
||||
let resolvedTargetPage = controller.pageNumber(for: currentLocation)
|
||||
let shouldTrustResolvedLocation = shouldTrustFullReplaceResolvedPage(
|
||||
resolvedTargetPage,
|
||||
livePageBeforeApply: livePageBeforeApply
|
||||
)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"applyFullPageMapReplacement decision livePage=\(livePageBeforeApply) resolvedTargetPage=\(resolvedTargetPage ?? -1) trustResolved=\(shouldTrustResolvedLocation) href=\(currentLocation.href)"
|
||||
)
|
||||
|
||||
if shouldTrustResolvedLocation,
|
||||
rebindVisibleLocation(currentLocation, readerView: readerView, controller: controller) {
|
||||
return
|
||||
}
|
||||
|
||||
let fallbackPage = max(livePageBeforeApply, 1)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"applyFullPageMapReplacement preserveLivePage fallbackPage=\(fallbackPage) currentReaderPage=\(readerView.currentPage + 1)"
|
||||
)
|
||||
rebindVisiblePage(
|
||||
to: fallbackPage - 1,
|
||||
readerView: readerView
|
||||
)
|
||||
} else {
|
||||
readerView.reloadPageCountOnly()
|
||||
}
|
||||
@@ -223,17 +264,23 @@ final class RDEPUBPresentationRuntime {
|
||||
return false
|
||||
}
|
||||
|
||||
case .extendPartial(_, let currentLocation):
|
||||
case .extendPartial(let capturedPageNumber, let currentLocation):
|
||||
removePendingPageMapUpdate(at: index)
|
||||
applyPageMapToLiveModel(update.pageMap)
|
||||
if let currentLocation,
|
||||
let livePageNumber = max(readerView.currentPage, 0) + 1
|
||||
let shouldTrustCapturedLocation = livePageNumber == max(capturedPageNumber, 1)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"extendPartial commit capturedPage=\(capturedPageNumber) livePage=\(livePageNumber) trustCaptured=\(shouldTrustCapturedLocation) totalPages=\(update.pageMap.totalPages) totalChapters=\(update.pageMap.totalChapters)"
|
||||
)
|
||||
if shouldTrustCapturedLocation,
|
||||
let currentLocation,
|
||||
rebindVisibleLocation(currentLocation, readerView: readerView, controller: controller) {
|
||||
return true
|
||||
}
|
||||
// Fallback: prefer the live readerView.currentPage over the stale captured
|
||||
// currentPageNumber, which may be outdated by the time this commit runs
|
||||
// (especially in pageCurl mode where the user may have turned several pages
|
||||
// since the extension was initiated).
|
||||
// Prefer the live readerView page when the user has moved since the
|
||||
// extension request was created; otherwise a stale captured location can
|
||||
// snap pageCurl back to the previous page after the turn completes.
|
||||
let livePageIndex = max(readerView.currentPage, 0)
|
||||
rebindVisiblePage(
|
||||
to: livePageIndex,
|
||||
@@ -250,6 +297,19 @@ final class RDEPUBPresentationRuntime {
|
||||
}
|
||||
|
||||
private func rebindVisiblePage(to pageIndex: Int, readerView: RDReaderView) {
|
||||
if pageIndex == readerView.currentPage {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"rebindVisiblePage pageUnchanged currentPage=\(readerView.currentPage + 1) displayType=\(readerView.currentDisplayType)"
|
||||
)
|
||||
readerView.reloadPageCountOnly()
|
||||
return
|
||||
}
|
||||
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"rebindVisiblePage targetPage=\(pageIndex + 1) currentPage=\(readerView.currentPage + 1) displayType=\(readerView.currentDisplayType) isPageCurlTransitioning=\(readerView.isPageCurlTransitioning)"
|
||||
)
|
||||
if readerView.currentDisplayType == .pageCurl {
|
||||
if readerView.isPageCurlTransitioning {
|
||||
// Defer the transition until the current page-curl animation completes,
|
||||
@@ -257,6 +317,10 @@ final class RDEPUBPresentationRuntime {
|
||||
DispatchQueue.main.async { [weak readerView] in
|
||||
guard let readerView, !readerView.isPageCurlTransitioning else { return }
|
||||
let livePageIndex = max(readerView.currentPage, 0)
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"rebindVisiblePage deferredTransition livePage=\(livePageIndex + 1)"
|
||||
)
|
||||
readerView.transitionToPage(pageNum: livePageIndex, animated: false)
|
||||
}
|
||||
} else {
|
||||
@@ -276,6 +340,10 @@ final class RDEPUBPresentationRuntime {
|
||||
controller: RDEPUBReaderController
|
||||
) -> Bool {
|
||||
guard let targetPageNumber = controller.pageNumber(for: location) else {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"rebindVisibleLocation failedToResolve locationHref=\(location.href) currentPage=\(readerView.currentPage + 1)"
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -284,9 +352,18 @@ final class RDEPUBPresentationRuntime {
|
||||
forAbsolutePageNumber: targetPageNumber,
|
||||
allowSynchronousLoad: false
|
||||
) == false {
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"rebindVisibleLocation prepareOnDemandBlocked targetPage=\(targetPageNumber) currentPage=\(readerView.currentPage + 1)"
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
RDEPUBBackgroundTrace.log(
|
||||
"Reconciliation",
|
||||
"rebindVisibleLocation targetPage=\(targetPageNumber) currentPage=\(readerView.currentPage + 1) href=\(location.href)"
|
||||
)
|
||||
|
||||
rebindVisiblePage(
|
||||
to: max(targetPageNumber - 1, 0),
|
||||
readerView: readerView
|
||||
@@ -353,4 +430,29 @@ final class RDEPUBPresentationRuntime {
|
||||
&& candidate.pageMap.totalPages >= existing.pageMap.totalPages
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func shouldTrustFullReplaceResolvedPage(
|
||||
_ resolvedTargetPage: Int?,
|
||||
livePageBeforeApply: Int
|
||||
) -> Bool {
|
||||
guard let resolvedTargetPage else { return false }
|
||||
return abs(resolvedTargetPage - livePageBeforeApply) <= 1
|
||||
}
|
||||
|
||||
private func schedulePendingCommitRetry() {
|
||||
guard pendingCommitRetryWorkItem == nil else { return }
|
||||
|
||||
let workItem = DispatchWorkItem { [weak self] in
|
||||
guard let self else { return }
|
||||
self.pendingCommitRetryWorkItem = nil
|
||||
self.commitPendingPageMapUpdateIfNeeded()
|
||||
}
|
||||
pendingCommitRetryWorkItem = workItem
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05, execute: workItem)
|
||||
}
|
||||
|
||||
private func cancelPendingCommitRetry() {
|
||||
pendingCommitRetryWorkItem?.cancel()
|
||||
pendingCommitRetryWorkItem = nil
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user