diff --git a/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt b/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt index 37cc66dd..ace8dcf2 100644 --- a/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt +++ b/zoomable-image/sub-sampling-image/src/androidTest/kotlin/me/saket/telephoto/subsampling/SubSamplingImageTest.kt @@ -344,6 +344,11 @@ class SubSamplingImageTest { override suspend fun decodeRegion(region: BitmapRegionTile): ImageBitmap { val isBaseTile = region.sampleSize.size == 8 return if (isBaseTile || !firstNonBaseTileReceived.getAndSet(true)) { + if (!isBaseTile) { + check(region.bounds == IntRect(0, 1200, 1216, 3265)) { + "Incorrect first tile received: $region. Possible race condition?" + } + } real.decodeRegion(region) } else { delay(Long.MAX_VALUE) diff --git a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/BitmapCache.kt b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/BitmapCache.kt index 947e3c13..0d74bf7c 100644 --- a/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/BitmapCache.kt +++ b/zoomable-image/sub-sampling-image/src/main/kotlin/me/saket/telephoto/subsamplingimage/internal/BitmapCache.kt @@ -3,6 +3,8 @@ package me.saket.telephoto.subsamplingimage.internal import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.util.fastForEach import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay @@ -41,7 +43,11 @@ internal class BitmapCache( .collect { regions -> val tilesToLoad = regions.fastFilter { it !in cachedBitmaps.value } tilesToLoad.fastForEach { region -> - val job = launch { + // CoroutineStart.UNDISPATCHED is used to ensure that the coroutines are executed + // in the same order they were launched. Otherwise, the tiles may load in a different + // order than what was requested. SubSamplingImageTest#draw_tile_under_centroid_first() + // test will also become flaky. + val job = launch(start = CoroutineStart.UNDISPATCHED) { val bitmap = decoder.decodeRegion(region) cachedBitmaps.update { it + (region to Loaded(bitmap)) } }