From 482f4d089bc319dbe072e1d317af01e802a99122 Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Thu, 5 Nov 2026 14:58:13 -0800 Subject: [PATCH] Redraw tiles once per burst of tiled-marker changes Same change as android-for-maplibre: relabelling street trees changes a few dozen tiled markers at once, and each update() replaced the raster source, so the map fetched every visible tile again per marker (on MapLibre, Pixel 5a: 45 replacements, 494 tile renders, last tile 6.1 s after the change). - Changes arriving within a frame share one tile refresh; a pending refresh is cancelled on destroy() so it cannot register the tile route again. - The tiling check reads the entity count without copying all entities. - The icon is baked only when the marker is drawn natively. --- .../marker/GoogleMapMarkerController.kt | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/mapconductor/googlemaps/marker/GoogleMapMarkerController.kt b/src/main/java/com/mapconductor/googlemaps/marker/GoogleMapMarkerController.kt index 03b9ca5..22a1f6e 100644 --- a/src/main/java/com/mapconductor/googlemaps/marker/GoogleMapMarkerController.kt +++ b/src/main/java/com/mapconductor/googlemaps/marker/GoogleMapMarkerController.kt @@ -22,8 +22,11 @@ import com.mapconductor.core.tileserver.TileServerRegistry import com.mapconductor.googlemaps.GoogleMapActualMarker import com.mapconductor.googlemaps.GoogleMapViewHolder import java.util.UUID +import kotlin.time.Duration.Companion.milliseconds import android.util.Log import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext @@ -139,11 +142,12 @@ internal class GoogleMapMarkerController private constructor( if (currentFinger == prevFinger) return semaphore.withPermit { + // 件数だけが要る。allEntities() は全件の写しを作るので、ラベルの付け替えのように + // 数十本が続けて変わると、そのたびに 14 万件を写すことになる。 val tilingEnabled = - markerTiling.enabled && markerManager.allEntities().size >= markerManager.minMarkerCount + markerTiling.enabled && markerManager.getMemoryStats().entityCount >= markerManager.minMarkerCount val wantsTiled = tilingEnabled && !state.draggable && state.getAnimation() == null val wasTiled = tiledMarkerIds.contains(state.id) - val markerIcon = state.icon?.toBitmapIcon() ?: defaultMarkerIcon if (wantsTiled) { if (!wasTiled) { @@ -161,13 +165,15 @@ internal class GoogleMapMarkerController private constructor( tiling = true, ), ) - syncTiledOverlay() + scheduleTileRefresh() return } if (wasTiled) { tiledMarkerIds.remove(state.id) } + // ネイティブマーカーとして描くときだけ焼き込む。タイル担当なら要らない。 + val markerIcon = state.icon?.toBitmapIcon() ?: defaultMarkerIcon val renderEntity = MarkerEntity( @@ -203,7 +209,7 @@ internal class GoogleMapMarkerController private constructor( renderer.onPostProcess() if (tiledMarkerIds.isNotEmpty()) { - syncTiledOverlay() + scheduleTileRefresh() } else { removeTileOverlay() } @@ -211,6 +217,25 @@ internal class GoogleMapMarkerController private constructor( viewportSwitch.requestReapply() } + private var tileRefreshJob: Job? = null + + /** + * 1 本ずつの変更からの描き直しを 1 回にまとめる。 + * + * ラベルの付け替えのように数十本が続けて変わると、1 本ごとにラスターソースを差し替え、 + * そのたびに地図が見えているタイルを全部取り直していた(MapLibre・Pixel 5a で 45 回、 + * 描き直したタイル 494 枚、最後のタイルまで 6 秒)。続けて来た変更は + * [TILE_REFRESH_COALESCE] の間に受けて、まとめて 1 回だけ差し替える。 + */ + private fun scheduleTileRefresh() { + if (tileRefreshJob?.isActive == true) return + tileRefreshJob = + (renderer as GoogleMapMarkerRenderer).coroutine.launch { + delay(TILE_REFRESH_COALESCE) + semaphore.withPermit { syncTiledOverlay() } + } + } + override suspend fun clear() { viewportSwitch.destroy() semaphore.withPermit { @@ -249,6 +274,8 @@ internal class GoogleMapMarkerController private constructor( override fun destroy() { viewportSwitch.destroy() + // 待っている描き直しが後から走ると、外したタイル経路をまた登録してしまう。 + tileRefreshJob?.cancel() // Clean up tile server registration // Unregister this map's route only. Never stop the server here: it is // a process-wide singleton shared by all map controllers and overlay @@ -376,6 +403,9 @@ internal class GoogleMapMarkerController private constructor( } companion object { + /** 続けて来る変更を 1 回の描き直しにまとめる窓。1 フレーム分。 */ + private val TILE_REFRESH_COALESCE = 16.milliseconds + private const val TAG = "GoogleMapMarkerController" fun create(