Fix page loading for webgpuviewer and bump version (#3823)

This commit is contained in:
w
2026-08-20 11:15:18 -07:00
committed by GitHub
parent 0974e787ad
commit 497e266256
4 changed files with 72 additions and 32 deletions
@@ -37,7 +37,7 @@ class ImageDecoder(private val resources: ImageSource, private val options: Opti
}
override suspend fun decode(): DecodeResult {
val decoder = resources.sourceOrNull()?.use {
val decoder = resources.source().use {
try {
ImageDecoder.new(it.inputStream())
} catch (e: ImageDecoder.DecodeException) {
@@ -49,6 +49,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.takeWhile
import kotlinx.coroutines.launch
import logcat.LogPriority
@@ -57,12 +58,13 @@ import tachiyomi.core.common.util.system.logcat
import java.util.concurrent.Executors
import kotlin.math.abs
import kotlin.math.min
import kotlin.time.Duration.Companion.milliseconds
open class WebGpuViewer(
val activity: ReaderActivity,
val isReversed: Boolean,
val isVertical: Boolean,
val pager: ImageView = ImageView(activity, isVertical = isVertical),
val pager: ImageView = ImageView(activity, isVertical = isVertical, isReversed = isReversed),
) : Viewer {
open val isContinuous: Boolean = false
@@ -198,8 +200,10 @@ open class WebGpuViewer(
@Volatile
var currentPage: ViewerPage? = null
val preloadCount = 3
open val cacheSize = 9
open val preloadAhead = 3
open val preloadBehind = 2
open val cacheSize get() = 1 + preloadAhead + preloadBehind
/**
* Page processing state
@@ -227,6 +231,11 @@ open class WebGpuViewer(
candidates.find {
it is ViewerReaderPage && it.page.chapter.chapter.id == chapterId && it.page.index == nextIndex
} ?: candidates.find { it is TransitionPage && it.prevChapter?.chapter?.id == chapterId }
?: page.nextChapter?.chapter?.id?.let { nextChapterId ->
candidates.find {
it is ViewerReaderPage && it.page.chapter.chapter.id == nextChapterId && it.page.index == 0
}
}
}
is TransitionPage -> {
@@ -246,6 +255,14 @@ open class WebGpuViewer(
candidates.find {
it is ViewerReaderPage && it.page.chapter.chapter.id == chapterId && it.page.index == prevIndex
} ?: candidates.find { it is TransitionPage && it.nextChapter?.chapter?.id == chapterId }
?: page.prevChapter?.let { prevChapter ->
prevChapter.pages?.lastIndex?.let { lastIndex ->
candidates.find {
it is ViewerReaderPage && it.page.chapter.chapter.id == prevChapter.chapter.id &&
it.page.index == lastIndex
}
}
}
}
is TransitionPage -> {
@@ -315,6 +332,26 @@ open class WebGpuViewer(
}
}
/**
* Kicks off loading [chapter] and, once its pages actually show up, re-runs
* [preloadPages] from the current page - [ReaderActivity]'s viewModel.preload isn't
* guaranteed to have finished loading by the time it returns, so a single immediate
* retry can race it and silently never queue the adjacent chapter's edge page for
* decode. Gives up after 5 seconds if the chapter never finishes loading.
*/
private fun preloadChapterThenRetry(chapter: ReaderChapter) {
scope.launch(Dispatchers.Default) {
activity.viewModel.preload(chapter)
repeat(25) {
if (chapter.state is ReaderChapter.State.Loaded) {
currentPage?.let { preloadPages(it) }
return@launch
}
delay(200.milliseconds)
}
}
}
abstract class ViewerPage {
abstract val prevChapter: ReaderChapter?
abstract val nextChapter: ReaderChapter?
@@ -331,10 +368,10 @@ open class WebGpuViewer(
inner class TransitionPage(override val prevChapter: ReaderChapter?, override val nextChapter: ReaderChapter?) :
ViewerPage() {
override val prev: ViewerPage?
get() = prevChapter?.pages?.lastOrNull()?.let { getPage(it) }
get() = prevChapter?.pages?.lastOrNull()?.let { getPage(it, currentPage) }
override val next: ViewerPage?
get() = nextChapter?.pages?.firstOrNull()?.let { getPage(it) }
get() = nextChapter?.pages?.firstOrNull()?.let { getPage(it, currentPage) }
}
inner class ViewerReaderPage(val page: ReaderPage) : ViewerPage() {
@@ -357,36 +394,36 @@ open class WebGpuViewer(
override val prev: ViewerPage?
get() = page.chapter.pages?.let { pages ->
pages.getOrNull(page.index - 1)?.let { getPage(it) } ?: prevChapter?.let { prevChapter ->
pages.getOrNull(page.index - 1)?.let { getPage(it, currentPage) } ?: run {
val prevChapter = prevChapter ?: return@run getPage(null, page.chapter, currentPage)
if (prevChapter.state !is ReaderChapter.State.Loaded) {
scope.launch(Dispatchers.Default) {
activity.viewModel.preload(prevChapter)
currentPage?.let { preloadPages(it) }
}
preloadChapterThenRetry(prevChapter)
}
if (config.alwaysShowChapterTransition) {
getPage(prevChapter, page.chapter)
getPage(prevChapter, page.chapter, currentPage)
} else {
prevChapter.pages?.lastOrNull()?.let { getPage(it) }
prevChapter.pages?.lastOrNull()?.let { getPage(it, currentPage) }
}
}
} ?: getPage(null, page.chapter)
}
override val next: ViewerPage?
get() = page.chapter.pages?.let { pages ->
pages.getOrNull(page.index + 1)?.let { getPage(it) } ?: nextChapter?.let { nextChapter ->
pages.getOrNull(page.index + 1)?.let { getPage(it, currentPage) } ?: run {
val nextChapter = nextChapter ?: return@run getPage(page.chapter, null, currentPage)
if (nextChapter.state !is ReaderChapter.State.Loaded) {
scope.launch(Dispatchers.Default) {
activity.viewModel.preload(nextChapter)
currentPage?.let { preloadPages(it) }
}
preloadChapterThenRetry(nextChapter)
}
if (config.alwaysShowChapterTransition) {
getPage(page.chapter, nextChapter)
getPage(page.chapter, nextChapter, currentPage)
} else {
nextChapter.pages?.firstOrNull()?.let { getPage(it) }
nextChapter.pages?.firstOrNull()?.let { getPage(it, currentPage) }
}
}
} ?: getPage(page.chapter, null)
}
}
@@ -498,18 +535,17 @@ open class WebGpuViewer(
init {
pager.state.apply {
fetchPage = fetch@{ index ->
val i = if (isReversed) -index else index
val current = currentPage ?: return@fetch null
// For index 0, return the current spread
if (i == 0) {
if (index == 0) {
return@fetch buildSpreadPage(getSpreadAnchor(current))
}
// Navigate by spreads from current
var page = current
val step = if (i > 0) 1 else -1
repeat(abs(i)) {
val step = if (index > 0) 1 else -1
repeat(abs(index)) {
page = nextPage(page, step) ?: return@fetch null
}
@@ -801,6 +837,7 @@ open class WebGpuViewer(
}
val errorPage = ImagePage(bitmap, createMipMaps = false).also {
it.image?.position = Image.Position.SINGLE
it.highQuality = false
}
synchronized(lock) {
if (pageInCache(page) && !page.imagePage.isDecoded && !page.imagePage.destroyed) {
@@ -959,6 +996,7 @@ open class WebGpuViewer(
val imagePage = ImagePage(bitmap, createMipMaps = false)
imagePage.image?.position = Image.Position.SINGLE
imagePage.highQuality = false
synchronized(lock) {
if (pageInCache(page) && !page.imagePage.isDecoded && !page.imagePage.destroyed) {
@@ -1103,7 +1141,7 @@ open class WebGpuViewer(
// Add prev pages (lowest priority)
val prevPages = mutableListOf<ViewerPage>()
var p: ViewerPage? = cachedPage
for (i in 0 until preloadCount) {
for (i in 0 until preloadBehind) {
p = p?.prev ?: break
prevPages.add(p)
}
@@ -1112,7 +1150,7 @@ open class WebGpuViewer(
// Add next pages (medium priority)
val nextPages = mutableListOf<ViewerPage>()
p = cachedPage
for (i in 0 until preloadCount) {
for (i in 0 until preloadAhead) {
p = p?.next ?: break
nextPages.add(p)
}
@@ -1148,13 +1186,12 @@ open class WebGpuViewer(
// The viewer already showed the page at fetchPage(delta).
// We need to update currentPage to match that.
val direction = if (isReversed) -delta else delta
val current = currentPage ?: return@onPageChange
// Navigate the same way fetchPage does
var page = current
val step = if (direction > 0) 1 else -1
repeat(abs(direction)) {
val step = if (delta > 0) 1 else -1
repeat(abs(delta)) {
page = nextPage(page, step) ?: return@onPageChange
}
@@ -9,6 +9,9 @@ class WebGpuViewerContinuous(activity: ReaderActivity) :
override val isContinuous: Boolean = true
override val preloadAhead = 1
override val preloadBehind = 1
private fun scrollByHalfPage(direction: Int) {
val state = (pager as ImageViewContinuous).state
val totalDistance = direction * state.height / 2f
+1 -1
View File
@@ -76,7 +76,7 @@ swipe = "1.3.0"
tapmoc = "0.4.2"
unifile = "08f224c8f9"
voyager = "2.2.21-1.10.3"
webgpuviewer = "28"
webgpuviewer = "31"
xmlutil = "1.0.2"
[libraries]