Fix memory leak per turn - dispose of individual pixmaps immediately, dispose of atlases after new screen is available (#12865)

This commit is contained in:
Yair Morgenstern 2025-01-27 09:56:11 +02:00 committed by GitHub
parent a7dca5c7e6
commit 56ab32ea1b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 25 additions and 4 deletions

View File

@ -42,7 +42,9 @@ import com.unciv.ui.components.extensions.toGroup
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.fonts.FontRulesetIcons
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.utils.Concurrency
import com.unciv.utils.debug
import kotlinx.coroutines.delay
import kotlin.math.atan2
import kotlin.math.max
import kotlin.math.min
@ -56,6 +58,8 @@ object ImageGetter {
// We use texture atlases to minimize texture swapping - see https://yairm210.medium.com/the-libgdx-performance-guide-1d068a84e181
lateinit var atlas: TextureAtlas
private val atlases = HashMap<String, TextureAtlas>()
/** These are atlases that we generate on-the-fly per ruleset */
private val tempAtlases = ArrayList<TextureAtlas>()
var ruleset = Ruleset()
fun getStatWithBackground(statName: String): TextureRegionDrawable? = textureRegionDrawables[statName]
@ -95,10 +99,24 @@ object ImageGetter {
BaseScreen.setSkin()
FontRulesetIcons.addRulesetImages(ruleset)
disposeTempAtlases()
setupStatImages()
setupResourcePortraits()
setupImprovementPortraits()
}
// We want this with a delay of a few seconds because *the current screen* might still be using this image
private fun disposeTempAtlases(){
val toDispose = tempAtlases.toList()
tempAtlases.clear()
Concurrency.run {
delay(3000)
Concurrency.runOnGLThread {
toDispose.forEach { it.dispose() }
}
}
}
private fun setupStatImages() {
// Performance improvement - "pack" the stat images together with the circle to the same texture
@ -127,7 +145,9 @@ object ImageGetter {
val pixmapPacker = PixmapPacker(2048, 2048, Pixmap.Format.RGBA8888, 2, false).apply { packToTexture = true }
for ((name, actor) in nameToActorList) {
actor.apply { isTransform = true; setScale(1f, -1f); setPosition(0f, height) } // flip Y axis
pixmapPacker.pack(name, FontRulesetIcons.getPixmapFromActorBase(actor, size, size))
val pixmap = FontRulesetIcons.getPixmapFromActorBase(actor, size, size)
pixmapPacker.pack(name, pixmap)
pixmap.dispose()
}
val yieldAtlas = pixmapPacker.generateTextureAtlas(
@ -135,6 +155,7 @@ object ImageGetter {
TextureFilter.MipMapLinearLinear,
true
)
tempAtlases.add(yieldAtlas)
for (region in yieldAtlas.regions) {
val drawable = TextureRegionDrawable(region)
textureRegionDrawables[region.name] = drawable

View File

@ -87,7 +87,7 @@ class WorldScreen(
val viewingCiv: Civilization,
restoreState: RestoreState? = null
) : BaseScreen() {
/** When set, causes the screen to update in the next [render][BaseScreen.render] event */
/** When set, causes the screen to update in the next [render][render] event */
var shouldUpdate = false
/** Indicates it's the player's ([viewingCiv]) turn */
@ -445,8 +445,8 @@ class WorldScreen(
updateGameplayButtons()
val coveredNotificationsTop = stage.height - statusButtons.y
val coveredNotificationsBottom = bottomTileInfoTable.height +
(if (game.settings.showMinimap) minimapWrapper.height else 0f)
val coveredNotificationsBottom = (bottomTileInfoTable.height + bottomTileInfoTable.y)
// (if (game.settings.showMinimap) minimapWrapper.height else 0f)
notificationsScroll.update(viewingCiv.notifications, coveredNotificationsTop, coveredNotificationsBottom)
val posZoomFromRight = if (game.settings.showMinimap) minimapWrapper.width