Fix OutOfMemoryError when loading a game and another is already loaded (#7306)

* Fix OutOfMemoryError when loading a game and another is already loaded

* Fix merge error...
This commit is contained in:
Timo T 2022-07-01 08:34:33 +02:00 committed by GitHub
parent 119440ccec
commit 08cede4f5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 48 additions and 18 deletions

View File

@ -24,7 +24,7 @@ buildscript {
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${com.unciv.build.BuildConfig.kotlinVersion}")
classpath("de.richsource.gradle.plugins:gwt-gradle-plugin:0.6")
classpath("com.android.tools.build:gradle:7.0.4")
classpath("com.android.tools.build:gradle:7.1.3")
classpath("com.mobidevelop.robovm:robovm-gradle-plugin:2.3.1")
// This is for wrapping the .jar file into a standalone executable

View File

@ -37,6 +37,7 @@ import com.unciv.utils.concurrency.launchOnGLThread
import com.unciv.utils.concurrency.withGLContext
import com.unciv.utils.concurrency.withThreadPoolContext
import com.unciv.utils.debug
import kotlinx.coroutines.CancellationException
import java.io.PrintWriter
import java.util.*
import kotlin.collections.ArrayDeque
@ -405,6 +406,9 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
/** Handles an uncaught exception or error. First attempts a platform-specific handler, and if that didn't handle the exception or error, brings the game to a [CrashScreen]. */
fun handleUncaughtThrowable(ex: Throwable) {
if (ex is CancellationException) {
return // kotlin coroutines use this for control flow... so we can just ignore them.
}
Log.error("Uncaught throwable", ex)
try {
PrintWriter(files.fileWriter("lasterror.txt")).use {

View File

@ -24,6 +24,7 @@ class UncivStage(viewport: Viewport) : Stage(viewport) {
private set
private val events = EventBus.EventReceiver()
init {
lastKnownVisibleArea = Rectangle(0f, 0f, width, height)
events.receive(VisibleAreaChanged::class) {
@ -35,10 +36,15 @@ class UncivStage(viewport: Viewport) : Stage(viewport) {
override fun dispose() {
events.stopReceiving()
super.dispose()
/** [Stage.dispose] is supposed to clear all references it holds. But it forgets the mouse over properties:
the [Stage.mouseOverActor] and [Stage.pointerOverActors]. [Stage.act] updates those properties,
and since there aren't any children left, sets all those properties to `null`. */
super.act()
}
override fun draw() =
{ super.draw() }.wrapCrashHandlingUnit()()
{ super.draw() }.wrapCrashHandlingUnit()()
/** libGDX has no built-in way to disable/enable pointer enter/exit events. It is simply being done in [Stage.act]. So to disable this, we have
* to replicate the [Stage.act] method without the code for pointer enter/exit events. This is of course inherently brittle, but the only way. */
@ -54,31 +60,31 @@ class UncivStage(viewport: Viewport) : Stage(viewport) {
}.wrapCrashHandlingUnit()()
override fun act(delta: Float) =
{ super.act(delta) }.wrapCrashHandlingUnit()()
{ super.act(delta) }.wrapCrashHandlingUnit()()
override fun touchDown(screenX: Int, screenY: Int, pointer: Int, button: Int) =
{ super.touchDown(screenX, screenY, pointer, button) }.wrapCrashHandling()() ?: true
{ super.touchDown(screenX, screenY, pointer, button) }.wrapCrashHandling()() ?: true
override fun touchDragged(screenX: Int, screenY: Int, pointer: Int) =
{ super.touchDragged(screenX, screenY, pointer) }.wrapCrashHandling()() ?: true
{ super.touchDragged(screenX, screenY, pointer) }.wrapCrashHandling()() ?: true
override fun touchUp(screenX: Int, screenY: Int, pointer: Int, button: Int) =
{ super.touchUp(screenX, screenY, pointer, button) }.wrapCrashHandling()() ?: true
{ super.touchUp(screenX, screenY, pointer, button) }.wrapCrashHandling()() ?: true
override fun mouseMoved(screenX: Int, screenY: Int) =
{ super.mouseMoved(screenX, screenY) }.wrapCrashHandling()() ?: true
{ super.mouseMoved(screenX, screenY) }.wrapCrashHandling()() ?: true
override fun scrolled(amountX: Float, amountY: Float) =
{ super.scrolled(amountX, amountY) }.wrapCrashHandling()() ?: true
{ super.scrolled(amountX, amountY) }.wrapCrashHandling()() ?: true
override fun keyDown(keyCode: Int) =
{ super.keyDown(keyCode) }.wrapCrashHandling()() ?: true
{ super.keyDown(keyCode) }.wrapCrashHandling()() ?: true
override fun keyUp(keyCode: Int) =
{ super.keyUp(keyCode) }.wrapCrashHandling()() ?: true
{ super.keyUp(keyCode) }.wrapCrashHandling()() ?: true
override fun keyTyped(character: Char) =
{ super.keyTyped(character) }.wrapCrashHandling()() ?: true
{ super.keyTyped(character) }.wrapCrashHandling()() ?: true
class VisibleAreaChanged(
val visibleArea: Rectangle

View File

@ -212,8 +212,9 @@ class WorldScreen(
}
override fun dispose() {
super.dispose()
events.stopReceiving()
statusButtons.dispose()
super.dispose()
}
private fun addKeyboardPresses() {

View File

@ -11,6 +11,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Label
import com.badlogic.gdx.scenes.scene2d.ui.Stack
import com.badlogic.gdx.utils.Align
import com.badlogic.gdx.utils.Disposable
import com.unciv.UncivGame
import com.unciv.logic.event.EventBus
import com.unciv.logic.multiplayer.HasMultiplayerGameName
@ -26,14 +27,15 @@ import com.unciv.ui.utils.extensions.onClick
import com.unciv.ui.utils.extensions.setSize
import com.unciv.utils.concurrency.Concurrency
import com.unciv.utils.concurrency.launchOnGLThread
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import java.time.Duration
import java.time.Instant
class MultiplayerStatusButton(
/*val*/ screen: BaseScreen,
screen: BaseScreen,
curGame: OnlineMultiplayerGame?
) : Button(BaseScreen.skin) {
) : Button(BaseScreen.skin), Disposable {
private var curGameName = curGame?.name
private val multiplayerImage = createMultiplayerImage()
private val loadingImage = createLoadingImage()
@ -43,6 +45,7 @@ class MultiplayerStatusButton(
private var loadingStarted: Instant? = null
private val events = EventBus.EventReceiver()
private var loadStopJob: Job? = null
init {
turnIndicatorCell = add().padTop(10f).padBottom(10f)
@ -96,7 +99,7 @@ class MultiplayerStatusButton(
} else {
Duration.ZERO
}
Concurrency.run("Hide loading indicator") {
loadStopJob = Concurrency.run("Hide loading indicator") {
delay(waitFor.toMillis())
launchOnGLThread {
loadingImage.clearActions()
@ -147,11 +150,18 @@ class MultiplayerStatusButton(
turnIndicator.flash()
}
}
override fun dispose() {
events.stopReceiving()
turnIndicator.dispose()
loadStopJob?.cancel()
}
}
private class TurnIndicator : HorizontalGroup() {
private class TurnIndicator : HorizontalGroup(), Disposable {
val gameAmount = Label("2", BaseScreen.skin)
val image: Image
private var job: Job? = null
init {
image = ImageGetter.getImage("OtherIcons/ExclamationMark")
image.setSize(30f)
@ -175,11 +185,15 @@ private class TurnIndicator : HorizontalGroup() {
if (alternations == 0) return
gameAmount.color = nextColor
image.color = nextColor
Concurrency.run("StatusButton color flash") {
job = Concurrency.run("StatusButton color flash") {
delay(500)
launchOnGLThread {
flash(alternations - 1, nextColor, curColor)
}
}
}
override fun dispose() {
job?.cancel()
}
}

View File

@ -1,11 +1,12 @@
package com.unciv.ui.worldscreen.status
import com.badlogic.gdx.scenes.scene2d.ui.HorizontalGroup
import com.badlogic.gdx.utils.Disposable
class StatusButtons(
nextTurnButton: NextTurnButton,
multiplayerStatusButton: MultiplayerStatusButton? = null
) : HorizontalGroup() {
) : HorizontalGroup(), Disposable {
var multiplayerStatusButton: MultiplayerStatusButton? = multiplayerStatusButton
set(button) {
multiplayerStatusButton?.remove()
@ -23,4 +24,8 @@ class StatusButtons(
}
addActor(nextTurnButton)
}
override fun dispose() {
multiplayerStatusButton?.dispose()
}
}