Files
ReadViewSDK/Sources/RDReaderView/EPUBUI/ReaderController/ChapterRuntime/RDEPUBChapterWindowSnapshot.swift
T
shenandshen d20196ee34 feat: configurable chapter window & parallel metadata parsing with benchmark
1. Configurable chapter window size (onDemandChapterWindowSize: 3-15)
   - Parameterized window radius in RDEPUBChapterRuntimeStore
   - Updated RDEPUBChapterWindowCoordinator to use configurable radius
   - RDEPUBChapterWindowSnapshot.from() accepts chapter array instead of fixed prev/next
   - Even numbers round up to odd (4→5), min 3, max 15

2. Configurable metadata parsing concurrency (metadataParsingConcurrency)
   - Default equals CPU core count
   - Parallel execution via OperationQueue in paginateMetadataOnly
   - Each worker creates independent builder instance
   - NSLock protects result aggregation

3. Per-chapter and total wall-clock timing instrumentation
   - Separated render vs I/O timing per chapter
   - Summary log with wallClockMs, renderTotalMs, writeTotalMs, avgRenderMs
   - Timing stored in RDEPUBReaderContext for test access

4. UI automation test infrastructure
   - Added --demo-window-size, --demo-concurrency, --demo-clear-cache launch args
   - DemoReaderState exposes windowSize, parseMs, parseConcurrency
   - ConfigurableWindowTests: 5 test cases for window size 3/5/15
   - ConcurrentParsingTests: 4 test cases for concurrency 2/4
   - MetadataParseBenchmarkTests: serial vs parallel benchmark

5. Bug fixes
   - Fixed page snap-back during background parsing (isUserInteracting check)
   - Reduced BookPageMap refresh frequency from 16 to 32 chapters
   - Moved waitForReadingInteractionToSettle outside operation loop

6. Design doc: dual-layer PageMap (estimated + precise mixed)
2026-06-03 23:38:11 +08:00

74 lines
2.5 KiB
Swift

import Foundation
struct RDEPUBChapterWindowSnapshot {
/// 窗口中的章节(有序)
let chapters: [RDEPUBRuntimeChapter]
/// 展平后的连续页数组(供 RDReaderView 消费)
/// 窗口内页码不写回 RDEPUBTextPage 模型;
/// flattenedPages 的数组下标就是窗口内连续页码(从 0 开始)。
let flattenedPages: [RDEPUBTextPage]
/// 当前章在 chapters 数组中的索引
let anchorChapterIndex: Int
/// 当前章在 flattenedPages 中的起始页码(从 0 开始,窗口内编号)
let anchorPageOffset: Int
/// 当前窗口首章的 spineIndex,用于调试日志和跨窗口映射
let windowStartSpineIndex: Int
// MARK: - 构建
/// 从章节窗口构建快照
static func from(
chapters: [RDEPUBRuntimeChapter],
anchorSpineIndex: Int
) -> RDEPUBChapterWindowSnapshot {
let sortedChapters = chapters.sorted { $0.spineIndex < $1.spineIndex }
let anchorIndex = sortedChapters.firstIndex { $0.spineIndex == anchorSpineIndex } ?? 0
let pageOffset = sortedChapters.prefix(anchorIndex).reduce(0) { $0 + $1.pages.count }
// 展平页数组
var allPages: [RDEPUBTextPage] = []
for (chIdx, ch) in sortedChapters.enumerated() {
for var page in ch.pages {
page.chapterIndex = chIdx
allPages.append(page)
}
}
let windowStartSpineIndex = sortedChapters.first?.spineIndex ?? anchorSpineIndex
return RDEPUBChapterWindowSnapshot(
chapters: sortedChapters,
flattenedPages: allPages,
anchorChapterIndex: anchorIndex,
anchorPageOffset: pageOffset,
windowStartSpineIndex: windowStartSpineIndex
)
}
// MARK: - 查询
/// 窗口内页码(即 flattenedPages 下标)-> 所属章节
func chapterForPage(flattenedPageIndex: Int) -> RDEPUBRuntimeChapter? {
var offset = 0
for ch in chapters {
if flattenedPageIndex < offset + ch.pages.count {
return ch
}
offset += ch.pages.count
}
return nil
}
/// 窗口内页码(即 flattenedPages 下标)-> 所属章节的 spineIndex
func spineIndexForPage(flattenedPageIndex: Int) -> Int? {
return chapterForPage(flattenedPageIndex: flattenedPageIndex)?.spineIndex
}
/// 总页数(窗口内)
var pageCount: Int { flattenedPages.count }
}