diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/Cleaner.kt b/app/src/main/java/de/westnordost/streetcomplete/data/Cleaner.kt index f99ff9065e..7220cadebc 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/Cleaner.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/Cleaner.kt @@ -7,6 +7,7 @@ import de.westnordost.streetcomplete.data.osmnotes.NoteController import de.westnordost.streetcomplete.data.quest.QuestTypeRegistry import de.westnordost.streetcomplete.util.ktx.format import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds +import kotlin.system.measureTimeMillis /** Deletes old unused data in the background */ class Cleaner( @@ -15,15 +16,14 @@ class Cleaner( private val questTypeRegistry: QuestTypeRegistry ) { fun clean() { - val time = nowAsEpochMilliseconds() - - val oldDataTimestamp = nowAsEpochMilliseconds() - ApplicationConstants.DELETE_OLD_DATA_AFTER - noteController.deleteOlderThan(oldDataTimestamp, MAX_DELETE_ELEMENTS) - mapDataController.deleteOlderThan(oldDataTimestamp, MAX_DELETE_ELEMENTS) - /* do this after cleaning map data and notes, because some metadata rely on map data */ - questTypeRegistry.forEach { it.deleteMetadataOlderThan(oldDataTimestamp) } - - Log.i(TAG, "Cleaning took ${((nowAsEpochMilliseconds() - time) / 1000.0).format(1)}s") + val execTimeMs = measureTimeMillis { + val oldDataTimestamp = nowAsEpochMilliseconds() - ApplicationConstants.DELETE_OLD_DATA_AFTER + noteController.deleteOlderThan(oldDataTimestamp, MAX_DELETE_ELEMENTS) + mapDataController.deleteOlderThan(oldDataTimestamp, MAX_DELETE_ELEMENTS) + /* do this after cleaning map data and notes, because some metadata rely on map data */ + questTypeRegistry.forEach { it.deleteMetadataOlderThan(oldDataTimestamp) } + } + Log.i(TAG, "Cleaning took ${(execTimeMs / 1000.0).format(1)}s") } companion object { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/Preloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/Preloader.kt index 85ffe2e5b1..2d2de3e5e7 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/Preloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/Preloader.kt @@ -4,12 +4,12 @@ import android.util.Log import de.westnordost.countryboundaries.CountryBoundaries import de.westnordost.osmfeatures.FeatureDictionary import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.concurrent.FutureTask +import kotlin.system.measureTimeMillis /** Initialize certain singleton classes used elsewhere throughout the app in the background */ class Preloader( @@ -18,30 +18,26 @@ class Preloader( ) { suspend fun preload() { - val time = nowAsEpochMilliseconds() - coroutineScope { - // country boundaries are necessary latest for when a quest is opened or on a download - launch { preloadCountryBoundaries() } - // names dictionary is necessary when displaying an element that has no name or - // when downloading the place name quest (etc) - launch { preloadFeatureDictionary() } + val execTimeMs = measureTimeMillis { + coroutineScope { + // country boundaries are necessary latest for when a quest is opened or on a download + launch { preloadCountryBoundaries() } + // names dictionary is necessary when displaying an element that has no name or + // when downloading the place name quest (etc) + launch { preloadFeatureDictionary() } + } } - - Log.i(TAG, "Preloading data took ${((nowAsEpochMilliseconds() - time) / 1000.0).format(1)}s") + Log.i(TAG, "Preloading data took ${(execTimeMs / 1000.0).format(1)}s") } private suspend fun preloadFeatureDictionary() = withContext(Dispatchers.IO) { - val time = nowAsEpochMilliseconds() - featuresDictionaryFuture.run() - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Loaded features dictionary in ${seconds.format(1)}s") + val execTimeMs = measureTimeMillis { featuresDictionaryFuture.run() } + Log.i(TAG, "Loaded features dictionary in ${(execTimeMs / 1000.0).format(1)}s") } private suspend fun preloadCountryBoundaries() = withContext(Dispatchers.IO) { - val time = nowAsEpochMilliseconds() - countryBoundariesFuture.run() - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Loaded country boundaries in ${seconds.format(1)}s") + val execTimeMs = measureTimeMillis { countryBoundariesFuture.run() } + Log.i(TAG, "Loaded country boundaries in ${(execTimeMs / 1000.0).format(1)}s") } companion object { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/Downloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/Downloader.kt index 197d75d2e4..c0c93431de 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/Downloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/Downloader.kt @@ -15,7 +15,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -import kotlin.math.max +import kotlin.system.measureTimeMillis /** Downloads all the things */ class Downloader( @@ -36,25 +36,23 @@ class Downloader( } Log.i(TAG, "Starting download ($sqkm km², bbox: $bboxString)") - val time = nowAsEpochMilliseconds() - - mutex.withLock { - coroutineScope { - // all downloaders run concurrently - launch { notesDownloader.download(bbox) } - launch { mapDataDownloader.download(bbox) } - launch { mapTilesDownloader.download(bbox) } + val execTimeMs = measureTimeMillis { + mutex.withLock { + coroutineScope { + // all downloaders run concurrently + launch { notesDownloader.download(bbox) } + launch { mapDataDownloader.download(bbox) } + launch { mapTilesDownloader.download(bbox) } + } } + putDownloadedAlready(tiles) } - putDownloadedAlready(tiles) - - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Finished download ($sqkm km², bbox: $bboxString) in ${seconds.format(1)}s") + Log.i(TAG, "Finished download ($sqkm km², bbox: $bboxString) in ${(execTimeMs / 1000.0).format(1)}s") } private fun hasDownloadedAlready(tiles: TilesRect): Boolean { - val freshTime = ApplicationConstants.REFRESH_DATA_AFTER - val ignoreOlderThan = max(0, nowAsEpochMilliseconds() - freshTime) + val ignoreOlderThan = (nowAsEpochMilliseconds() - ApplicationConstants.REFRESH_DATA_AFTER) + .coerceAtLeast(0L) return downloadedTilesDb.get(tiles, ignoreOlderThan).contains(DownloadedTilesType.ALL) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/download/strategy/AVariableRadiusStrategy.kt b/app/src/main/java/de/westnordost/streetcomplete/data/download/strategy/AVariableRadiusStrategy.kt index 99bd34bbd1..e46ba8df4d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/download/strategy/AVariableRadiusStrategy.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/download/strategy/AVariableRadiusStrategy.kt @@ -16,7 +16,6 @@ import de.westnordost.streetcomplete.util.math.enclosingBoundingBox import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.math.PI -import kotlin.math.max import kotlin.math.min import kotlin.math.sqrt @@ -79,8 +78,8 @@ abstract class AVariableRadiusStrategy( /** return if data in the given tiles rect that hasn't been downloaded yet */ private suspend fun hasMissingDataFor(tilesRect: TilesRect): Boolean { - val dataExpirationTime = ApplicationConstants.REFRESH_DATA_AFTER - val ignoreOlderThan = max(0, nowAsEpochMilliseconds() - dataExpirationTime) + val ignoreOlderThan = (nowAsEpochMilliseconds() - ApplicationConstants.REFRESH_DATA_AFTER) + .coerceAtLeast(0L) val downloadedTiles = withContext(Dispatchers.IO) { downloadedTilesDao.get(tilesRect, ignoreOlderThan) } return !downloadedTiles.contains(DownloadedTilesType.ALL) diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/maptiles/MapTilesDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/maptiles/MapTilesDownloader.kt index e6141a7a16..906407eeb4 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/maptiles/MapTilesDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/maptiles/MapTilesDownloader.kt @@ -6,7 +6,6 @@ import de.westnordost.streetcomplete.data.download.tiles.enclosingTilesRect import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox import de.westnordost.streetcomplete.screens.main.map.VectorTileProvider import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch @@ -21,6 +20,7 @@ import okhttp3.Response import okhttp3.internal.Version import java.io.IOException import kotlin.coroutines.resume +import kotlin.system.measureTimeMillis class MapTilesDownloader( private val vectorTileProvider: VectorTileProvider, @@ -35,30 +35,29 @@ class MapTilesDownloader( var failureCount = 0 var downloadedSize = 0 var cachedSize = 0 - val time = nowAsEpochMilliseconds() - - coroutineScope { - for (tile in getDownloadTileSequence(bbox)) { - launch { - val result = try { - downloadTile(tile.zoom, tile.x, tile.y) - } catch (e: Exception) { - DownloadFailure - } - ++tileCount - when (result) { - is DownloadFailure -> ++failureCount - is DownloadSuccess -> { - if (result.alreadyCached) cachedSize += result.size - else downloadedSize += result.size + val execTimeMs = measureTimeMillis { + coroutineScope { + for (tile in getDownloadTileSequence(bbox)) { + launch { + val result = try { + downloadTile(tile.zoom, tile.x, tile.y) + } catch (e: Exception) { + DownloadFailure + } + ++tileCount + when (result) { + is DownloadFailure -> ++failureCount + is DownloadSuccess -> { + if (result.alreadyCached) cachedSize += result.size + else downloadedSize += result.size + } } } } } } - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 val failureText = if (failureCount > 0) ". $failureCount tiles failed to download" else "" - Log.i(TAG, "Downloaded $tileCount tiles (${downloadedSize / 1000}kB downloaded, ${cachedSize / 1000}kB already cached) in ${seconds.format(1)}s$failureText") + Log.i(TAG, "Downloaded $tileCount tiles (${downloadedSize / 1000}kB downloaded, ${cachedSize / 1000}kB already cached) in ${(execTimeMs / 1000.0).format(1)}s$failureText") } private fun getDownloadTileSequence(bbox: BoundingBox): Sequence = diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt index 73bc434edc..5d014d5d11 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataController.kt @@ -7,8 +7,9 @@ import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryCreator import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryDao import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometryEntry import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import java.util.concurrent.CopyOnWriteArrayList +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue /** Controller to access element data and its geometry and handle updates to it (from OSM API) */ class MapDataController internal constructor( @@ -52,37 +53,39 @@ class MapDataController internal constructor( /** update element data with [mapData] in the given [bbox] (fresh data from the OSM API has been * downloaded) */ + @OptIn(ExperimentalTime::class) fun putAllForBBox(bbox: BoundingBox, mapData: MutableMapData) { - val time = nowAsEpochMilliseconds() - - val oldElementKeys: Set - val geometryEntries: Collection - synchronized(this) { - // for incompletely downloaded relations, complete the map data (as far as possible) with - // local data, i.e. with local nodes and ways (still) in local storage - completeMapData(mapData) + val (pair, execDuration) = measureTimedValue { + val oldElementKeys: Set + val geometryEntries: Collection + synchronized(this) { + // for incompletely downloaded relations, complete the map data (as far as possible) with + // local data, i.e. with local nodes and ways (still) in local storage + completeMapData(mapData) + + geometryEntries = createGeometries(mapData, mapData) + + // don't use cache here, because if not everything is already cached, db call will be faster + oldElementKeys = elementDB.getAllKeys(mapData.boundingBox!!).toMutableSet() + for (element in mapData) { + oldElementKeys.remove(ElementKey(element.type, element.id)) + } - geometryEntries = createGeometries(mapData, mapData) + // for the cache, use bbox and not mapData.boundingBox because the latter is padded, + // see comment for QUEST_FILTER_PADDING + cache.update(oldElementKeys, mapData, geometryEntries, bbox) - // don't use cache here, because if not everything is already cached, db call will be faster - oldElementKeys = elementDB.getAllKeys(mapData.boundingBox!!).toMutableSet() - for (element in mapData) { - oldElementKeys.remove(ElementKey(element.type, element.id)) + elementDB.deleteAll(oldElementKeys) + geometryDB.deleteAll(oldElementKeys) + geometryDB.putAll(geometryEntries) + elementDB.putAll(mapData) } - - // for the cache, use bbox and not mapData.boundingBox because the latter is padded, - // see comment for QUEST_FILTER_PADDING - cache.update(oldElementKeys, mapData, geometryEntries, bbox) - - elementDB.deleteAll(oldElementKeys) - geometryDB.deleteAll(oldElementKeys) - geometryDB.putAll(geometryEntries) - elementDB.putAll(mapData) + Pair(geometryEntries, oldElementKeys) } - + val (geometryEntries, oldElementKeys) = pair Log.i(TAG, "Persisted ${geometryEntries.size} and deleted ${oldElementKeys.size} elements and geometries" + - " in ${((nowAsEpochMilliseconds() - time) / 1000.0).format(1)}s" + " in ${(execDuration.inWholeMilliseconds / 1000.0).format(1)}s" ) val mapDataWithGeometry = MutableMapDataWithGeometry(mapData, geometryEntries) @@ -166,10 +169,10 @@ class MapDataController internal constructor( fun getGeometries(keys: Collection): List = cache.getGeometries(keys, geometryDB::getAllEntries) + @OptIn(ExperimentalTime::class) fun getMapDataWithGeometry(bbox: BoundingBox): MutableMapDataWithGeometry { - val time = nowAsEpochMilliseconds() - val result = cache.getMapDataWithGeometry(bbox) - Log.i(TAG, "Fetched ${result.size} elements and geometries in ${nowAsEpochMilliseconds() - time}ms") + val (result, execDuration) = measureTimedValue { cache.getMapDataWithGeometry(bbox) } + Log.i(TAG, "Fetched ${result.size} elements and geometries in ${execDuration.inWholeMilliseconds}ms") return result } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataDownloader.kt index 349f67b395..594ae9bf12 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/mapdata/MapDataDownloader.kt @@ -4,29 +4,30 @@ import android.util.Log import de.westnordost.streetcomplete.ApplicationConstants import de.westnordost.streetcomplete.data.download.QueryTooBigException import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.math.enlargedBy import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.yield +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue /** Takes care of downloading all note and osm quests */ class MapDataDownloader( private val mapDataApi: MapDataApi, private val mapDataController: MapDataController ) { + @OptIn(ExperimentalTime::class) suspend fun download(bbox: BoundingBox) = withContext(Dispatchers.IO) { - val time = nowAsEpochMilliseconds() - - val mapData = MutableMapData() - val expandedBBox = bbox.enlargedBy(ApplicationConstants.QUEST_FILTER_PADDING) - getMapAndHandleTooBigQuery(expandedBBox, mapData) - /* The map data might be filled with several bboxes one after another if the download is - split up in several, so lets set the bbox back to the bbox of the complete download */ - mapData.boundingBox = expandedBBox - - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Downloaded ${mapData.nodes.size} nodes, ${mapData.ways.size} ways and ${mapData.relations.size} relations in ${seconds.format(1)}s") + val (mapData, execDuration) = measureTimedValue { + MutableMapData().also { mapData -> + val expandedBBox = bbox.enlargedBy(ApplicationConstants.QUEST_FILTER_PADDING) + getMapAndHandleTooBigQuery(expandedBBox, mapData) + /* The map data might be filled with several bboxes one after another if the download is + split up in several, so lets set the bbox back to the bbox of the complete download */ + mapData.boundingBox = expandedBBox + } + } + Log.i(TAG, "Downloaded ${mapData.nodes.size} nodes, ${mapData.ways.size} ways and ${mapData.relations.size} relations in ${(execDuration.inWholeMilliseconds / 1000.0).format(1)}s") yield() diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestController.kt index 5367a733bf..4be6d2c35e 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osm/osmquests/OsmQuestController.kt @@ -38,6 +38,9 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.FutureTask +import kotlin.system.measureTimeMillis +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue /** Controller for managing OsmQuests. Takes care of persisting OsmQuest objects and notifying * listeners about changes */ @@ -138,53 +141,52 @@ class OsmQuestController internal constructor( notesSource.addListener(notesSourceListener) } + @OptIn(ExperimentalTime::class) private fun createQuestsForBBox( bbox: BoundingBox, mapDataWithGeometry: MapDataWithGeometry, questTypes: Collection>, ): Collection { - val time = nowAsEpochMilliseconds() - - val countryBoundaries = countryBoundariesFuture.get() - - // Remove elements without tags, to be used for quests that are never applicable without - // tags. These quests are usually OsmFilterQuestType, where questType.filter.mayEvaluateToTrueWithNoTags - // guarantees we can skip elements without tags completely. Also those quests don't use geometry. - // This shortcut reduces time for creating quests by ~15-30%. - val onlyElementsWithTags = MutableMapDataWithGeometry(mapDataWithGeometry.filter { it.tags.isNotEmpty() }, emptyList()) - - val deferredQuests: List>> = questTypes.map { questType -> - scope.async { - val questsForType = ArrayList() - val questTypeName = questType.name - if (!countryBoundaries.intersects(bbox, questType.enabledInCountries)) { - Log.d(TAG, "$questTypeName: Skipped because it is disabled for this country") - emptyList() - } else { - val questTime = nowAsEpochMilliseconds() - var questCount = 0 - val mapDataToUse = if (questType is OsmFilterQuestType && !questType.filter.mayEvaluateToTrueWithNoTags) - onlyElementsWithTags - else - mapDataWithGeometry - for (element in questType.getApplicableElements(mapDataToUse)) { - val geometry = mapDataWithGeometry.getGeometry(element.type, element.id) - ?: continue - if (!mayCreateQuest(questType, geometry, bbox)) continue - questsForType.add(OsmQuest(questType, element.type, element.id, geometry)) - questCount++ + val (quests, execDuration) = measureTimedValue { + val countryBoundaries = countryBoundariesFuture.get() + + // Remove elements without tags, to be used for quests that are never applicable without + // tags. These quests are usually OsmFilterQuestType, where questType.filter.mayEvaluateToTrueWithNoTags + // guarantees we can skip elements without tags completely. Also those quests don't use geometry. + // This shortcut reduces time for creating quests by ~15-30%. + val onlyElementsWithTags = MutableMapDataWithGeometry(mapDataWithGeometry.filter { it.tags.isNotEmpty() }, emptyList()) + + val deferredQuests: List>> = questTypes.map { questType -> + scope.async { + val questsForType = ArrayList() + val questTypeName = questType.name + if (!countryBoundaries.intersects(bbox, questType.enabledInCountries)) { + Log.d(TAG, "$questTypeName: Skipped because it is disabled for this country") + emptyList() + } else { + var questCount = 0 + val questSeconds = measureTimeMillis { + val mapDataToUse = if (questType is OsmFilterQuestType && !questType.filter.mayEvaluateToTrueWithNoTags) + onlyElementsWithTags + else + mapDataWithGeometry + for (element in questType.getApplicableElements(mapDataToUse)) { + val geometry = mapDataWithGeometry.getGeometry(element.type, element.id) + ?: continue + if (!mayCreateQuest(questType, geometry, bbox)) continue + questsForType.add(OsmQuest(questType, element.type, element.id, geometry)) + questCount++ + } + } + Log.d(TAG, "$questTypeName: Found $questCount quests in ${questSeconds}ms") + questsForType } - - val questSeconds = nowAsEpochMilliseconds() - questTime - Log.d(TAG, "$questTypeName: Found $questCount quests in ${questSeconds}ms") - questsForType } } + runBlocking { deferredQuests.awaitAll().flatten() } } - val quests = runBlocking { deferredQuests.awaitAll().flatten() } - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Created ${quests.size} quests for bbox in ${seconds.format(1)}s") + Log.i(TAG, "Created ${quests.size} quests for bbox in ${(execDuration.inWholeMilliseconds / 1000.0).format(1)}s") return quests } @@ -233,13 +235,11 @@ class OsmQuestController internal constructor( } private fun updateQuests(questsNow: Collection, obsoleteQuestKeys: Collection) { - val time = nowAsEpochMilliseconds() - - db.deleteAll(obsoleteQuestKeys) - db.putAll(questsNow) - - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Persisted ${questsNow.size} new and removed ${obsoleteQuestKeys.size} already resolved quests in ${seconds.format(1)}s") + val execTimeMs = measureTimeMillis { + db.deleteAll(obsoleteQuestKeys) + db.putAll(questsNow) + } + Log.i(TAG, "Persisted ${questsNow.size} new and removed ${obsoleteQuestKeys.size} already resolved quests in ${(execTimeMs / 1000.0).format(1)}s") } private fun mayCreateQuest( diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloader.kt index 9bfec14c05..1a02fc36a8 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/AvatarsDownloader.kt @@ -3,11 +3,11 @@ package de.westnordost.streetcomplete.data.osmnotes import android.util.Log import de.westnordost.osmapi.user.UserApi import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import de.westnordost.streetcomplete.util.ktx.saveToFile import java.io.File import java.io.IOException import java.net.URL +import kotlin.system.measureTimeMillis /** Downloads and stores the OSM avatars of users */ class AvatarsDownloader( @@ -21,15 +21,15 @@ class AvatarsDownloader( return } - val time = nowAsEpochMilliseconds() - for (userId in userIds) { - val avatarUrl = getProfileImageUrl(userId) - if (avatarUrl != null) { - download(userId, avatarUrl) + val execTimeMs = measureTimeMillis { + for (userId in userIds) { + val avatarUrl = getProfileImageUrl(userId) + if (avatarUrl != null) { + download(userId, avatarUrl) + } } } - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Downloaded ${userIds.size} avatar images in ${seconds.format(1)}s") + Log.i(TAG, "Downloaded ${userIds.size} avatar images in ${(execTimeMs / 1000.0).format(1)}s") } private fun getProfileImageUrl(userId: Long): String? { diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt index 8ad78dd6a4..8f43b8886c 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NoteController.kt @@ -4,8 +4,8 @@ import android.util.Log import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox import de.westnordost.streetcomplete.data.osm.mapdata.LatLon import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import java.util.concurrent.CopyOnWriteArrayList +import kotlin.system.measureTimeMillis /** Manages access to the notes storage */ class NoteController( @@ -25,29 +25,27 @@ class NoteController( /** Replace all notes in the given bounding box with the given notes */ fun putAllForBBox(bbox: BoundingBox, notes: Collection) { - val time = nowAsEpochMilliseconds() - val oldNotesById = mutableMapOf() val addedNotes = mutableListOf() val updatedNotes = mutableListOf() - synchronized(this) { - dao.getAll(bbox).associateByTo(oldNotesById) { it.id } - - for (note in notes) { - if (oldNotesById.containsKey(note.id)) { - updatedNotes.add(note) - } else { - addedNotes.add(note) + val execTimeMs = measureTimeMillis { + synchronized(this) { + dao.getAll(bbox).associateByTo(oldNotesById) { it.id } + + for (note in notes) { + if (oldNotesById.containsKey(note.id)) { + updatedNotes.add(note) + } else { + addedNotes.add(note) + } + oldNotesById.remove(note.id) } - oldNotesById.remove(note.id) - } - dao.putAll(notes) - dao.deleteAll(oldNotesById.keys) + dao.putAll(notes) + dao.deleteAll(oldNotesById.keys) + } } - - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Persisted ${addedNotes.size} and deleted ${oldNotesById.size} notes in ${seconds.format(1)}s") + Log.i(TAG, "Persisted ${addedNotes.size} and deleted ${oldNotesById.size} notes in ${(execTimeMs / 1000.0).format(1)}s") onUpdated(added = addedNotes, updated = updatedNotes, deleted = oldNotesById.keys) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloader.kt b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloader.kt index 6bf52fbfba..46c29171f1 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloader.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/data/osmnotes/NotesDownloader.kt @@ -3,26 +3,25 @@ package de.westnordost.streetcomplete.data.osmnotes import android.util.Log import de.westnordost.streetcomplete.data.osm.mapdata.BoundingBox import de.westnordost.streetcomplete.util.ktx.format -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlinx.coroutines.yield +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue /** Takes care of downloading notes and referenced avatar pictures into persistent storage */ class NotesDownloader( private val notesApi: NotesApi, private val noteController: NoteController ) { + @OptIn(ExperimentalTime::class) suspend fun download(bbox: BoundingBox) = withContext(Dispatchers.IO) { - val time = nowAsEpochMilliseconds() - - val notes = notesApi - .getAll(bbox, 10000, 0) - // exclude invalid notes (#1338) - .filter { it.comments.isNotEmpty() } - - val seconds = (nowAsEpochMilliseconds() - time) / 1000.0 - Log.i(TAG, "Downloaded ${notes.size} notes in ${seconds.format(1)}s") + val (notes, execDuration) = measureTimedValue { + notesApi.getAll(bbox, 10000, 0) + // exclude invalid notes (#1338) + .filter { it.comments.isNotEmpty() } + } + Log.i(TAG, "Downloaded ${notes.size} notes in ${(execDuration.inWholeMilliseconds / 1000.0).format(1)}s") yield() diff --git a/app/src/main/java/de/westnordost/streetcomplete/screens/user/statistics/PhysicsWorldController.kt b/app/src/main/java/de/westnordost/streetcomplete/screens/user/statistics/PhysicsWorldController.kt index 9c9b41c639..d49c423653 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/screens/user/statistics/PhysicsWorldController.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/screens/user/statistics/PhysicsWorldController.kt @@ -2,7 +2,6 @@ package de.westnordost.streetcomplete.screens.user.statistics import android.os.Handler import android.os.HandlerThread -import de.westnordost.streetcomplete.util.ktx.nowAsEpochMilliseconds import kotlinx.coroutines.android.asCoroutineDispatcher import kotlinx.coroutines.withContext import org.jbox2d.collision.shapes.Shape @@ -11,6 +10,7 @@ import org.jbox2d.dynamics.Body import org.jbox2d.dynamics.BodyDef import org.jbox2d.dynamics.World import kotlin.math.max +import kotlin.system.measureTimeMillis /** Contains the physics simulation world and the physics simulation loop */ class PhysicsWorldController(gravity: Vec2) { @@ -65,12 +65,10 @@ class PhysicsWorldController(gravity: Vec2) { } private fun loop() { - val startTime = nowAsEpochMilliseconds() - world.step(DELAY / 1000f, 6, 2) - val executionTime = nowAsEpochMilliseconds() - startTime + val execTimeMs = measureTimeMillis { world.step(DELAY / 1000f, 6, 2) } listener?.onWorldStep() if (isRunning) { - handler.postDelayed(this::loop, max(0, DELAY - executionTime)) + handler.postDelayed(this::loop, (DELAY - execTimeMs).coerceAtLeast(0L)) } }