From 4b5ebf2575a13f1bfd6ee5f207d75d9844559fc7 Mon Sep 17 00:00:00 2001 From: Anthony Calosa Date: Sat, 9 Jul 2022 15:53:19 +0800 Subject: [PATCH] refactor libgdx static objects to be disposed -fixes memory leaks since these objects are not properly disposed by the GC -Textures, BitmapFonts and others should be disposed manually if not loaded in the assetmanager --- forge-gui-mobile/src/forge/Forge.java | 17 +++------- .../src/forge/adventure/util/Config.java | 8 ++--- forge-gui-mobile/src/forge/assets/Assets.java | 20 +++++++++-- forge-gui-mobile/src/forge/assets/FSkin.java | 4 +-- .../src/forge/assets/FSkinFont.java | 27 ++++++--------- .../src/forge/assets/FSkinTexture.java | 12 +++---- .../src/forge/assets/ImageCache.java | 34 ++++++++++++------- .../src/forge/card/CardRenderer.java | 8 ++--- 8 files changed, 70 insertions(+), 60 deletions(-) diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 7267170a25b..bcd1cc46b44 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -55,7 +55,6 @@ import java.io.File; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; -import java.util.HashMap; import java.util.List; import java.util.Map; @@ -104,8 +103,7 @@ public class Forge implements ApplicationListener { public static boolean enablePreloadExtendedArt = false; public static boolean isTabletDevice = false; public static String locale = "en-US"; - public Assets cardAssets; - public Assets otherAssets; + public Assets assets; public static boolean hdbuttons = false; public static boolean hdstart = false; public static boolean isPortraitMode = false; @@ -166,8 +164,7 @@ public class Forge implements ApplicationListener { // don't allow to read and process ForgeConstants.SPRITE_CARDBG_FILE = ""; } - cardAssets = new Assets(); - otherAssets = new Assets(); + assets = new Assets(); graphics = new Graphics(); splashScreen = new SplashScreen(); frameRate = new FrameRate(); @@ -954,8 +951,7 @@ public class Forge implements ApplicationListener { currentScreen.onClose(null); currentScreen = null; } - cardAssets.dispose(); - otherAssets.dispose(); + assets.dispose(); Dscreens.clear(); graphics.dispose(); SoundSystem.instance.dispose(); @@ -967,11 +963,8 @@ public class Forge implements ApplicationListener { /** Retrieve assets. * @param other if set to true returns otherAssets otherwise returns cardAssets */ - public static Assets getAssets(boolean other) { - if (other) - return ((Forge)Gdx.app.getApplicationListener()).otherAssets; - else - return ((Forge)Gdx.app.getApplicationListener()).cardAssets; + public static Assets getAssets() { + return ((Forge)Gdx.app.getApplicationListener()).assets; } public static boolean switchScene(Scene newScene) { if (currentScene != null) { diff --git a/forge-gui-mobile/src/forge/adventure/util/Config.java b/forge-gui-mobile/src/forge/adventure/util/Config.java index 8813e54725c..d3df18b0d22 100644 --- a/forge-gui-mobile/src/forge/adventure/util/Config.java +++ b/forge-gui-mobile/src/forge/adventure/util/Config.java @@ -120,11 +120,11 @@ public class Config { public TextureAtlas getAtlas(String spriteAtlas) { String fileName = getFile(spriteAtlas).path(); - if (!Forge.getAssets(true).manager.contains(fileName, TextureAtlas.class)) { - Forge.getAssets(true).manager.load(fileName, TextureAtlas.class); - Forge.getAssets(true).manager.finishLoadingAsset(fileName); + if (!Forge.getAssets().others.contains(fileName, TextureAtlas.class)) { + Forge.getAssets().others.load(fileName, TextureAtlas.class); + Forge.getAssets().others.finishLoadingAsset(fileName); } - return Forge.getAssets(true).manager.get(fileName); + return Forge.getAssets().others.get(fileName); } public SettingData getSettingData() { diff --git a/forge-gui-mobile/src/forge/assets/Assets.java b/forge-gui-mobile/src/forge/assets/Assets.java index 0b88a5af937..f4410bf59ac 100644 --- a/forge-gui-mobile/src/forge/assets/Assets.java +++ b/forge-gui-mobile/src/forge/assets/Assets.java @@ -2,12 +2,28 @@ package forge.assets; import com.badlogic.gdx.assets.AssetManager; import com.badlogic.gdx.assets.loaders.resolvers.AbsoluteFileHandleResolver; +import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.BitmapFont; import com.badlogic.gdx.utils.Disposable; +import com.badlogic.gdx.utils.ObjectMap; + +import java.util.HashMap; public class Assets implements Disposable { - public AssetManager manager = new AssetManager(new AbsoluteFileHandleResolver()); + public AssetManager cards = new AssetManager(new AbsoluteFileHandleResolver()); + public AssetManager others = new AssetManager(new AbsoluteFileHandleResolver()); + public HashMap fonts = new HashMap<>(); + public ObjectMap counterFonts = new ObjectMap<>(); + public ObjectMap generatedCards = new ObjectMap<>(512); @Override public void dispose() { - manager.dispose(); + cards.dispose(); + others.dispose(); + for (BitmapFont bitmapFont : counterFonts.values()) + bitmapFont.dispose(); + for (Texture texture : generatedCards.values()) + texture.dispose(); + for (FSkinFont fSkinFont : fonts.values()) + fSkinFont.font.dispose(); } } diff --git a/forge-gui-mobile/src/forge/assets/FSkin.java b/forge-gui-mobile/src/forge/assets/FSkin.java index eb5f2c8ce24..cb08205eecf 100644 --- a/forge-gui-mobile/src/forge/assets/FSkin.java +++ b/forge-gui-mobile/src/forge/assets/FSkin.java @@ -88,7 +88,7 @@ public class FSkin { * the skin name */ public static void loadLight(String skinName, final SplashScreen splashScreen) { - AssetManager manager = Forge.getAssets(true).manager; + AssetManager manager = Forge.getAssets().others; preferredName = skinName.toLowerCase().replace(' ', '_'); //reset hd buttons/icons @@ -236,7 +236,7 @@ public class FSkin { parameter.magFilter = Texture.TextureFilter.Linear; } - AssetManager manager = Forge.getAssets(true).manager; + AssetManager manager = Forge.getAssets().others; // Grab and test various sprite files. final FileHandle f1 = getDefaultSkinFile(SourceFile.ICONS.getFilename()); diff --git a/forge-gui-mobile/src/forge/assets/FSkinFont.java b/forge-gui-mobile/src/forge/assets/FSkinFont.java index 8367c054642..f00e6adb98a 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinFont.java +++ b/forge-gui-mobile/src/forge/assets/FSkinFont.java @@ -3,7 +3,6 @@ package forge.assets; import java.io.FileInputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.HashMap; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; @@ -39,7 +38,6 @@ public class FSkinFont { private static final int MAX_FONT_SIZE_MANY_GLYPHS = 36; private static final String TTF_FILE = "font1.ttf"; - private static final HashMap fonts = new HashMap<>(); private static final String commonCharacterSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm" + "nopqrstuvwxyz1234567890\"!?'.,;:()[]{}<>|/@\\^$-%+=#_&*\u2014" @@ -58,10 +56,10 @@ public class FSkinFont { return _get((int)Utils.scale(unscaledSize)); } public static FSkinFont _get(final int scaledSize) { - FSkinFont skinFont = fonts.get(scaledSize); + FSkinFont skinFont = Forge.getAssets().fonts.get(scaledSize); if (skinFont == null) { skinFont = new FSkinFont(scaledSize); - fonts.put(scaledSize, skinFont); + Forge.getAssets().fonts.put(scaledSize, skinFont); } return skinFont; } @@ -97,14 +95,14 @@ public class FSkinFont { } public static void updateAll() { - for (FSkinFont skinFont : fonts.values()) { + for (FSkinFont skinFont : Forge.getAssets().fonts.values()) { skinFont.updateFont(); } } private final int fontSize; private final float scale; - private BitmapFont font; + BitmapFont font; private FSkinFont(int fontSize0) { if (fontSize0 > MAX_FONT_SIZE) { @@ -400,16 +398,13 @@ public class FSkinFont { if (fontFile != null && fontFile.exists()) { final BitmapFontData data = new BitmapFontData(fontFile, false); String finalFontName = fontName; - FThreads.invokeInEdtNowOrLater(new Runnable() { - @Override - public void run() { //font must be initialized on UI thread - try { - font = new BitmapFont(data, (TextureRegion) null, true); - found[0] = true; - } catch (Exception e) { - e.printStackTrace(); - found[0] = false; - } + FThreads.invokeInEdtNowOrLater(() -> { //font must be initialized on UI thread + try { + font = new BitmapFont(data, (TextureRegion) null, true); + found[0] = true; + } catch (Exception e) { + e.printStackTrace(); + found[0] = false; } }); } diff --git a/forge-gui-mobile/src/forge/assets/FSkinTexture.java b/forge-gui-mobile/src/forge/assets/FSkinTexture.java index 348f51b69cb..8bee73fe9b3 100644 --- a/forge-gui-mobile/src/forge/assets/FSkinTexture.java +++ b/forge-gui-mobile/src/forge/assets/FSkinTexture.java @@ -131,9 +131,9 @@ public enum FSkinTexture implements FImage { if (preferredFile.path().contains("fallback_skin")) { texture = new Texture(preferredFile); } else { - Forge.getAssets(true).manager.load(preferredFile.path(), Texture.class); - Forge.getAssets(true).manager.finishLoadingAsset(preferredFile.path()); - texture = Forge.getAssets(true).manager.get(preferredFile.path(), Texture.class); + Forge.getAssets().others.load(preferredFile.path(), Texture.class); + Forge.getAssets().others.finishLoadingAsset(preferredFile.path()); + texture = Forge.getAssets().others.get(preferredFile.path(), Texture.class); } } catch (final Exception e) { @@ -155,9 +155,9 @@ public enum FSkinTexture implements FImage { if (defaultFile.path().contains("fallback_skin")) { texture = new Texture(defaultFile); } else { - Forge.getAssets(true).manager.load(defaultFile.path(), Texture.class); - Forge.getAssets(true).manager.finishLoadingAsset(defaultFile.path()); - texture = Forge.getAssets(true).manager.get(defaultFile.path(), Texture.class); + Forge.getAssets().others.load(defaultFile.path(), Texture.class); + Forge.getAssets().others.finishLoadingAsset(defaultFile.path()); + texture = Forge.getAssets().others.get(defaultFile.path(), Texture.class); } } catch (final Exception e) { diff --git a/forge-gui-mobile/src/forge/assets/ImageCache.java b/forge-gui-mobile/src/forge/assets/ImageCache.java index e3694817979..ff85bdec796 100644 --- a/forge-gui-mobile/src/forge/assets/ImageCache.java +++ b/forge-gui-mobile/src/forge/assets/ImageCache.java @@ -97,7 +97,6 @@ public class ImageCache { public static FImage BlackBorder = FSkinImage.IMG_BORDER_BLACK; public static FImage WhiteBorder = FSkinImage.IMG_BORDER_WHITE; private static final ObjectMap> imageBorder = new ObjectMap<>(1024); - private static final ObjectMap generatedCards = new ObjectMap<>(512); private static boolean imageLoaded, delayLoadRequested; public static void allowSingleLoad() { @@ -121,11 +120,11 @@ public class ImageCache { ImageKeys.clearMissingCards(); } public static void clearGeneratedCards() { - generatedCards.clear(); + Forge.getAssets().generatedCards.clear(); } public static void disposeTextures(){ CardRenderer.clearcardArtCache(); - Forge.getAssets(false).manager.clear(); + Forge.getAssets().cards.clear(); } public static Texture getImage(InventoryItem ii) { @@ -134,7 +133,9 @@ public class ImageCache { if(imageKey.startsWith(ImageKeys.CARD_PREFIX) || imageKey.startsWith(ImageKeys.TOKEN_PREFIX)) return getImage(ii.getImageKey(false), true, false); } - return getImage(ii.getImageKey(false), true, true); + boolean useDefaultNotFound = imageKey != null && !(imageKey.startsWith(ImageKeys.PRECON_PREFIX) || imageKey.startsWith(ImageKeys.FATPACK_PREFIX) + || imageKey.startsWith(ImageKeys.BOOSTERBOX_PREFIX) || imageKey.startsWith(ImageKeys.BOOSTER_PREFIX) || imageKey.startsWith(ImageKeys.TOURNAMENTPACK_PREFIX)); + return getImage(ii.getImageKey(false), useDefaultNotFound, true); } /** @@ -265,33 +266,40 @@ public class ImageCache { if (file == null) return null; if (!otherCache && Forge.enableUIMask.equals("Full") && isBorderless(imageKey)) - return generatedCards.get(imageKey); - return Forge.getAssets(otherCache).manager.get(file.getPath(), Texture.class, false); + return Forge.getAssets().generatedCards.get(imageKey); + if (otherCache) + return Forge.getAssets().others.get(file.getPath(), Texture.class, false); + return Forge.getAssets().cards.get(file.getPath(), Texture.class, false); } static Texture loadAsset(String imageKey, File file, boolean otherCache) { if (file == null) return null; syncQ.add(file.getPath()); - if (!otherCache && Forge.getAssets(false).manager.getLoadedAssets() > maxCardCapacity) { - unloadCardTextures(Forge.getAssets(false).manager); + if (!otherCache && Forge.getAssets().cards.getLoadedAssets() > maxCardCapacity) { + unloadCardTextures(Forge.getAssets().cards); return null; } String fileName = file.getPath(); //load to assetmanager - Forge.getAssets(otherCache).manager.load(fileName, Texture.class, Forge.isTextureFilteringEnabled() ? filtered : defaultParameter); - Forge.getAssets(otherCache).manager.finishLoadingAsset(fileName); + if (otherCache) { + Forge.getAssets().others.load(fileName, Texture.class, Forge.isTextureFilteringEnabled() ? filtered : defaultParameter); + Forge.getAssets().others.finishLoadingAsset(fileName); + } else { + Forge.getAssets().cards.load(fileName, Texture.class, Forge.isTextureFilteringEnabled() ? filtered : defaultParameter); + Forge.getAssets().cards.finishLoadingAsset(fileName); + } //return loaded assets if (otherCache) { - return Forge.getAssets(true).manager.get(fileName, Texture.class, false); + return Forge.getAssets().others.get(fileName, Texture.class, false); } else { - Texture t = Forge.getAssets(false).manager.get(fileName, Texture.class, false); + Texture t = Forge.getAssets().cards.get(fileName, Texture.class, false); //if full bordermasking is enabled, update the border color if (Forge.enableUIMask.equals("Full")) { boolean borderless = isBorderless(imageKey); updateBorders(t.toString(), borderless ? Pair.of(Color.valueOf("#171717").toString(), false): isCloserToWhite(getpixelColor(t))); //if borderless, generate new texture from the asset and store if (borderless) { - generatedCards.put(imageKey, generateTexture(new FileHandle(file), t, Forge.isTextureFilteringEnabled())); + Forge.getAssets().generatedCards.put(imageKey, generateTexture(new FileHandle(file), t, Forge.isTextureFilteringEnabled())); } } return t; diff --git a/forge-gui-mobile/src/forge/card/CardRenderer.java b/forge-gui-mobile/src/forge/card/CardRenderer.java index a9d53f9f142..b8acce7d277 100644 --- a/forge-gui-mobile/src/forge/card/CardRenderer.java +++ b/forge-gui-mobile/src/forge/card/CardRenderer.java @@ -104,8 +104,6 @@ public class CardRenderer { private static final float BORDER_THICKNESS = Utils.scale(1); public static final float PADDING_MULTIPLIER = 0.021f; public static final float CROP_MULTIPLIER = 0.96f; - - private static Map counterFonts = new HashMap<>(); private static final Color counterBackgroundColor = new Color(0f, 0f, 0f, 0.9f); private static final Map counterColorCache = new HashMap<>(); private static final GlyphLayout layout = new GlyphLayout(); @@ -1101,7 +1099,7 @@ public class CardRenderer { private static void drawCounterTabs(final CardView card, final Graphics g, final float x, final float y, final float w, final float h) { int fontSize = Math.max(11, Math.min(22, (int) (h * 0.08))); - BitmapFont font = counterFonts.get(fontSize); + BitmapFont font = Forge.getAssets().counterFonts.get(fontSize); final float additionalXOffset = 3f * ((fontSize - 11) / 11f); final float variableWidth = ((fontSize - 11) / 11f) * 44f; @@ -1218,7 +1216,7 @@ public class CardRenderer { private static void drawMarkersTabs(final List markers, final Graphics g, final float x, final float y, final float w, final float h, boolean larger) { int fontSize = larger ? Math.max(9, Math.min(22, (int) (h * 0.08))) : Math.max(8, Math.min(22, (int) (h * 0.05))); - BitmapFont font = counterFonts.get(fontSize); + BitmapFont font = Forge.getAssets().counterFonts.get(fontSize); final float additionalXOffset = 3f * ((fontSize - 8) / 8f); @@ -1410,7 +1408,7 @@ public class CardRenderer { textureRegions.add(new TextureRegion(texture)); } - counterFonts.put(fontSize, new BitmapFont(fontData, textureRegions, true)); + Forge.getAssets().counterFonts.put(fontSize, new BitmapFont(fontData, textureRegions, true)); generator.dispose(); packer.dispose();