Fix tile loading lock

This commit is contained in:
Alexander Nozik 2022-07-16 21:50:47 +03:00
parent 9f6386a8c2
commit f49ff34a18
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
3 changed files with 32 additions and 21 deletions

View File

@ -17,6 +17,7 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.* import androidx.compose.ui.input.pointer.*
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import centre.sciprog.maps.* import centre.sciprog.maps.*
import io.ktor.utils.io.CancellationException
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mu.KotlinLogging import mu.KotlinLogging
import org.jetbrains.skia.Font import org.jetbrains.skia.Font
@ -185,8 +186,10 @@ actual fun MapView(
try { try {
mapTiles += deferred.await() mapTiles += deferred.await()
} catch (ex: Exception) { } catch (ex: Exception) {
//displaying the error is maps responsibility if (ex !is CancellationException) {
logger.error(ex) { "Failed to load tile with id=$id" } //displaying the error is maps responsibility
logger.error(ex) { "Failed to load tile with id=$id" }
}
} }
} }
} }

View File

@ -5,6 +5,7 @@ import androidx.compose.ui.graphics.toComposeImageBitmap
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.readBytes import io.ktor.client.statement.readBytes
import io.ktor.utils.io.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -37,6 +38,7 @@ class OpenStreetMapTileProvider(
* Download and cache the tile image * Download and cache the tile image
*/ */
private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) { private fun CoroutineScope.downloadImageAsync(id: TileId) = async(Dispatchers.IO) {
id.cacheFilePath()?.let { path -> id.cacheFilePath()?.let { path ->
if (path.exists()) { if (path.exists()) {
try { try {
@ -48,40 +50,46 @@ class OpenStreetMapTileProvider(
} }
} }
try { //semaphore works only for actual download
//semaphore works only for actual download semaphore.withPermit {
semaphore.withPermit { val url = id.osmUrl()
val url = id.osmUrl() val byteArray = client.get(url).readBytes()
val byteArray = client.get(url).readBytes()
logger.debug { "Finished downloading map tile with id $id from $url" } logger.debug { "Finished downloading map tile with id $id from $url" }
id.cacheFilePath()?.let { path -> id.cacheFilePath()?.let { path ->
logger.debug { "Caching map tile $id to $path" } logger.debug { "Caching map tile $id to $path" }
path.parent.createDirectories() path.parent.createDirectories()
path.writeBytes(byteArray) path.writeBytes(byteArray)
}
Image.makeFromEncoded(byteArray).toComposeImageBitmap()
} }
} catch (ex: Exception){
//if loading is failed for some reason, clear the cache Image.makeFromEncoded(byteArray).toComposeImageBitmap()
cache.remove(id)
throw ex
} }
} }
override fun CoroutineScope.loadTileAsync( override fun CoroutineScope.loadTileAsync(
tileId: TileId, tileId: TileId,
): Deferred<MapTile> { ): Deferred<MapTile> {
//start image download //start image download
val image = cache.getOrPut(tileId) { val imageDeferred = cache.getOrPut(tileId) {
downloadImageAsync(tileId) downloadImageAsync(tileId)
} }
//collect the result asynchronously //collect the result asynchronously
return async { MapTile(tileId, image.await()) } return async {
val image = try {
imageDeferred.await()
} catch (ex: Exception) {
cache.remove(tileId)
if(ex !is CancellationException) {
logger.error(ex) { "Failed to load tile image with id=$tileId" }
}
throw ex
}
MapTile(tileId, image)
}
} }