fix: make render orchestrator teardown safe
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
Some checks failed
validate-scripts / validate-scripts (push) Has been cancelled
This commit is contained in:
@@ -195,6 +195,7 @@ void MapRenderer::requestSnapshot(SnapshotCallback callback) {
|
||||
// Called on OpenGL thread //
|
||||
|
||||
void MapRenderer::resetRenderer() {
|
||||
Log::Info(Event::Android, "MapRenderer::resetRenderer entered");
|
||||
renderer.reset();
|
||||
|
||||
if (!asyncRendererCleanup) {
|
||||
@@ -203,6 +204,7 @@ void MapRenderer::resetRenderer() {
|
||||
|
||||
window.reset();
|
||||
swapBehaviorFlush = false;
|
||||
Log::Info(Event::Android, "MapRenderer::resetRenderer completed");
|
||||
}
|
||||
|
||||
void MapRenderer::scheduleSnapshot(std::unique_ptr<SnapshotCallback> callback) {
|
||||
@@ -322,9 +324,11 @@ void MapRenderer::onSurfaceChanged([[maybe_unused]] JNIEnv& env, jint width, jin
|
||||
}
|
||||
|
||||
void MapRenderer::onRendererReset(JNIEnv&) {
|
||||
Log::Info(Event::Android, "MapRenderer::nativeReset entered");
|
||||
// Make sure to destroy the renderer on the GL Thread
|
||||
auto self = ActorRef<MapRenderer>(*this, mailboxData.getMailbox());
|
||||
self.ask(&MapRenderer::resetRenderer).wait();
|
||||
Log::Info(Event::Android, "MapRenderer::nativeReset completed");
|
||||
}
|
||||
|
||||
// needs to be called on GL thread
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.maplibre.android.maps.renderer.surfaceview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
import android.view.View;
|
||||
|
||||
@@ -28,11 +29,13 @@ public class SurfaceViewMapRenderer extends MapRenderer {
|
||||
surfaceView.setDetachedListener(new MapLibreSurfaceView.OnSurfaceViewDetachedListener() {
|
||||
@Override
|
||||
public void onSurfaceViewDetached() {
|
||||
Log.i("Mbgl-SurfaceRenderer", "SurfaceView detached; nativeReset entered");
|
||||
// because the GL thread is destroyed when the view is detached from window,
|
||||
// we need to ensure releasing the native renderer as well.
|
||||
// This avoids releasing it only when the view is being recreated, which is already on a new GL thread,
|
||||
// and leads to JNI crashes like https://github.com/mapbox/mapbox-gl-native/issues/14618
|
||||
nativeReset();
|
||||
Log.i("Mbgl-SurfaceRenderer", "nativeReset returned");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -62,6 +62,79 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
private var step093Mode = false
|
||||
private var step097Topology: Step097StencilFixture.Topology? = null
|
||||
private var step099Scenario: Step099ReadinessFixture.Scenario? = null
|
||||
private var step101Scenario: String? = null
|
||||
private var step101Completed = 0
|
||||
private var step101DynamicTick = 0L
|
||||
private var step101ReadySinceMs = 0L
|
||||
private var step101LoadSinceMs = 0L
|
||||
private val step101Handler = Handler(Looper.getMainLooper())
|
||||
private val step101Poll = object : Runnable {
|
||||
override fun run() {
|
||||
val scenario = step101Scenario ?: return
|
||||
if (!this@ParticleCoreLayerValidationActivity::maplibreMap.isInitialized || !styleReady) {
|
||||
if (SystemClock.elapsedRealtime() - step101LoadSinceMs > 20000L) {
|
||||
Log.e(TAG, "STEP101_SCENARIO_BLOCKED scenario=$scenario completed=$step101Completed reason=style-not-ready")
|
||||
status.text = "STEP-101 BLOCKED: style not ready"
|
||||
step101Scenario = null
|
||||
return
|
||||
}
|
||||
step101Handler.postDelayed(this, 250L)
|
||||
return
|
||||
}
|
||||
val snapshot = JSONObject(maplibreMap.getRenderSegmentDiagnostics())
|
||||
val sources = snapshot.optJSONArray("sources")
|
||||
var tileReady = false
|
||||
for (index in 0 until (sources?.length() ?: 0)) {
|
||||
val source = sources?.optJSONObject(index) ?: continue
|
||||
if (source.optString("sourceId") == Step099ReadinessFixture.STATIC_SOURCE &&
|
||||
source.optBoolean("isLoaded") && source.optInt("tileCount") > 0) tileReady = true
|
||||
}
|
||||
if (!tileReady) {
|
||||
if (SystemClock.elapsedRealtime() - step101LoadSinceMs > 20000L) {
|
||||
Log.e(TAG, "STEP101_SCENARIO_BLOCKED scenario=$scenario completed=$step101Completed reason=tile-not-ready")
|
||||
status.text = "STEP-101 BLOCKED: tile not ready"
|
||||
step101Scenario = null
|
||||
return
|
||||
}
|
||||
step101Handler.postDelayed(this, 250L)
|
||||
return
|
||||
}
|
||||
if (step101ReadySinceMs == 0L) {
|
||||
step101ReadySinceMs = SystemClock.elapsedRealtime()
|
||||
Log.i(TAG, "STEP101_SOURCE_TILE_READY scenario=$scenario completed=$step101Completed " +
|
||||
"sources=${sources?.length()} diagnostics=${snapshot.optLong("contentRevision")}")
|
||||
}
|
||||
if (scenario == "T3_RESET_WITH_DYNAMIC_GEOJSON") {
|
||||
step101DynamicTick += 1
|
||||
updateStep099GeoJson(Step099ReadinessFixture.DYNAMIC_SOURCE, step101DynamicTick)
|
||||
Log.i(TAG, "STEP101_DYNAMIC_SOURCE_MUTATION scenario=$scenario completed=$step101Completed " +
|
||||
"tick=$step101DynamicTick")
|
||||
}
|
||||
if (SystemClock.elapsedRealtime() - step101ReadySinceMs < 2500L) {
|
||||
step101Handler.postDelayed(this, 250L)
|
||||
return
|
||||
}
|
||||
Log.i(TAG, "STEP101_NEW_RENDERER_READY scenario=$scenario completed=$step101Completed " +
|
||||
"styleReady=$styleReady diagnostics=${snapshot.optLong("contentRevision")}")
|
||||
val target = if (scenario == "T2_RESET_LOOP_20") 20 else 1
|
||||
if (step101Completed >= target) {
|
||||
Log.i(TAG, "STEP101_SCENARIO_DONE scenario=$scenario resetsRequested=$step101Completed " +
|
||||
"nativeResetPairsMustMatch=$step101Completed")
|
||||
status.text = "STEP-101 $scenario DONE ${step101Completed}/$target"
|
||||
step101Scenario = null
|
||||
return
|
||||
}
|
||||
if (scenario == "T4_RESET_WITH_DIAGNOSTICS") {
|
||||
Log.i(TAG, "STEP101_PRE_RESET_DIAGNOSTICS ${maplibreMap.getRenderSegmentDiagnostics()}")
|
||||
}
|
||||
val next = step101Completed + 1
|
||||
Log.i(TAG, "STEP101_RESET_REQUEST scenario=$scenario iteration=$next oldMapViewActive=true " +
|
||||
"tileReady=$tileReady")
|
||||
step101Completed = next
|
||||
step101Handler.removeCallbacks(this)
|
||||
recreate() // Activity destroys the old MapView and constructs a new native renderer.
|
||||
}
|
||||
}
|
||||
private var step099Tick = 0L
|
||||
private var step099LastOverallLoaded: Boolean? = null
|
||||
private var step099FalseSinceMs: Long? = null
|
||||
@@ -189,6 +262,12 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
step093Mode = savedInstanceState?.getBoolean(STATE_STEP093_MODE, false) ?: false
|
||||
step097Topology = savedInstanceState?.getString(STATE_STEP097_TOPOLOGY)
|
||||
?.let { value -> Step097StencilFixture.Topology.values().firstOrNull { it.name == value } }
|
||||
step101Scenario = if (savedInstanceState?.containsKey(STATE_STEP101_SCENARIO) == true) {
|
||||
savedInstanceState.getString(STATE_STEP101_SCENARIO)
|
||||
} else {
|
||||
intent.getStringExtra("step101_scenario")?.takeIf { it in STEP101_SCENARIOS }
|
||||
}
|
||||
step101Completed = savedInstanceState?.getInt(STATE_STEP101_COMPLETED, 0) ?: 0
|
||||
step093Topology = savedInstanceState?.getString(STATE_STEP093_TOPOLOGY)
|
||||
?.let { value -> Step093Topology.values().firstOrNull { it.name == value } }
|
||||
?: Step093Topology.BOTTOM_CONTINUOUS_TOP
|
||||
@@ -227,7 +306,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
updateNavSeaPbfButtonState()
|
||||
updatePbfModeButtonState()
|
||||
updateRetainedCacheButtonState()
|
||||
if (step097Topology != null) loadStep097StencilStyle()
|
||||
if (step101Scenario != null) loadStep101Style()
|
||||
else if (step097Topology != null) loadStep097StencilStyle()
|
||||
else if (step093Mode) loadStep093Style(configureBeforeStyle = false)
|
||||
else loadValidationStyle(active = active, enabled = enabled, reason = "INITIAL")
|
||||
}
|
||||
@@ -532,6 +612,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
menu.add(0, 15, 15, "RESET DEFAULT A")
|
||||
menu.add(0, 16, 16, "STEP-097 SAME SOURCE STENCIL")
|
||||
menu.add(0, 17, 17, "STEP-099 READINESS")
|
||||
menu.add(0, 18, 18, "STEP-101 TEARDOWN")
|
||||
setOnMenuItemClickListener { item ->
|
||||
when (item.itemId) {
|
||||
in Step093Topology.values().indices -> applyStep093Scene(Step093Topology.values()[item.itemId])
|
||||
@@ -543,6 +624,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
15 -> applyStep093Scene(Step093Topology.BOTTOM_CONTINUOUS_TOP)
|
||||
16 -> showStep097StencilMenu(anchor)
|
||||
17 -> showStep099Menu(anchor)
|
||||
18 -> showStep101Menu(anchor)
|
||||
}
|
||||
true
|
||||
}
|
||||
@@ -589,6 +671,45 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showStep101Menu(anchor: android.view.View) {
|
||||
PopupMenu(this, anchor).apply {
|
||||
STEP101_SCENARIOS.forEachIndexed { index, scenario -> menu.add(0, index, index, scenario) }
|
||||
setOnMenuItemClickListener { item ->
|
||||
val scenario = STEP101_SCENARIOS.getOrNull(item.itemId) ?: return@setOnMenuItemClickListener false
|
||||
step101Scenario = scenario
|
||||
step101Completed = 0
|
||||
loadStep101Style()
|
||||
true
|
||||
}
|
||||
show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadStep101Style() {
|
||||
val scenario = step101Scenario ?: return
|
||||
stopStep099Scenario()
|
||||
step101Handler.removeCallbacksAndMessages(null)
|
||||
step101ReadySinceMs = 0L
|
||||
step101LoadSinceMs = SystemClock.elapsedRealtime()
|
||||
val fixture = Step099ReadinessFixture.Scenario.T4_RCR
|
||||
val accepted = maplibreMap.setRenderSegmentPlan(Step099ReadinessFixture.planJson(fixture))
|
||||
styleReady = false
|
||||
val epoch = ++styleLoadEpoch
|
||||
Log.i(TAG, "STEP101_OLD_RESET_COMPLETED_OR_INITIAL scenario=$scenario completed=$step101Completed " +
|
||||
"planAccepted=$accepted")
|
||||
maplibreMap.setStyle(Style.Builder().fromJson(Step099ReadinessFixture.styleJson(fixture))) {
|
||||
if (step101Scenario != scenario || epoch != styleLoadEpoch) return@setStyle
|
||||
styleReady = true
|
||||
maplibreMap.moveCamera(CameraUpdateFactory.newCameraPosition(
|
||||
CameraPosition.Builder().target(LatLng(28.0, 135.0)).zoom(3.0).build()
|
||||
))
|
||||
Log.i(TAG, "STEP101_STYLE_READY scenario=$scenario completed=$step101Completed")
|
||||
step101Handler.removeCallbacks(step101Poll)
|
||||
step101Handler.post(step101Poll)
|
||||
}
|
||||
step101Handler.post(step101Poll)
|
||||
}
|
||||
|
||||
private fun applyStep099Scenario(scenario: Step099ReadinessFixture.Scenario) {
|
||||
stopStep093DiagnosticsPolling()
|
||||
step093Mode = false
|
||||
@@ -2135,6 +2256,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
mapView.onStart()
|
||||
if (step101Scenario != null && styleReady) step101Handler.post(step101Poll)
|
||||
if (step099Scenario != null) {
|
||||
step099Running = true
|
||||
step099LastOverallLoaded = null
|
||||
@@ -2149,6 +2271,7 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
override fun onResume() { super.onResume(); mapView.onResume() }
|
||||
override fun onPause() { mapView.onPause(); super.onPause() }
|
||||
override fun onStop() {
|
||||
step101Handler.removeCallbacksAndMessages(null)
|
||||
step093Handler.removeCallbacks(step093DiagnosticsPoll)
|
||||
step099Running = false
|
||||
step099Handler.removeCallbacksAndMessages(null)
|
||||
@@ -2156,11 +2279,14 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
super.onStop()
|
||||
}
|
||||
override fun onDestroy() {
|
||||
step101Handler.removeCallbacksAndMessages(null)
|
||||
if (step101Scenario != null) Log.i(TAG, "STEP101_MAPVIEW_DESTROY_ENTER scenario=$step101Scenario completed=$step101Completed")
|
||||
step093Handler.removeCallbacks(step093DiagnosticsPoll)
|
||||
stopStep099Scenario()
|
||||
weatherRuntime?.dispose()
|
||||
weatherRuntime = null
|
||||
mapView.onDestroy()
|
||||
if (step101Scenario != null) Log.i(TAG, "STEP101_MAPVIEW_DESTROY_RETURN scenario=$step101Scenario completed=$step101Completed")
|
||||
super.onDestroy()
|
||||
}
|
||||
override fun onLowMemory() { super.onLowMemory(); mapView.onLowMemory() }
|
||||
@@ -2189,6 +2315,8 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
outState.putBoolean(STATE_RETAINED_CACHE_ENABLED, retainedCacheEnabled)
|
||||
outState.putBoolean(STATE_STEP093_MODE, step093Mode)
|
||||
outState.putString(STATE_STEP097_TOPOLOGY, step097Topology?.name)
|
||||
outState.putString(STATE_STEP101_SCENARIO, step101Scenario)
|
||||
outState.putInt(STATE_STEP101_COMPLETED, step101Completed)
|
||||
outState.putString(STATE_STEP093_TOPOLOGY, step093Topology.name)
|
||||
outState.putString(STATE_STEP093_VARIANT, step093Variant.name)
|
||||
super.onSaveInstanceState(outState)
|
||||
@@ -2226,6 +2354,11 @@ class ParticleCoreLayerValidationActivity : AppCompatActivity() {
|
||||
private const val STATE_RETAINED_CACHE_ENABLED = "particle_validation.retained_cache_enabled"
|
||||
private const val STATE_STEP093_MODE = "particle_validation.step093_mode"
|
||||
private const val STATE_STEP097_TOPOLOGY = "particle_validation.step097_topology"
|
||||
private const val STATE_STEP101_SCENARIO = "particle_validation.step101_scenario"
|
||||
private const val STATE_STEP101_COMPLETED = "particle_validation.step101_completed"
|
||||
private val STEP101_SCENARIOS = listOf(
|
||||
"T1_RESET_ONCE", "T2_RESET_LOOP_20", "T3_RESET_WITH_DYNAMIC_GEOJSON", "T4_RESET_WITH_DIAGNOSTICS"
|
||||
)
|
||||
private const val STATE_STEP093_TOPOLOGY = "particle_validation.step093_topology"
|
||||
private const val STATE_STEP093_VARIANT = "particle_validation.step093_variant"
|
||||
private const val PARTICLE_LAYER_ID = "particle-raster-core"
|
||||
|
||||
Reference in New Issue
Block a user