* Fix many memory leaks (mimetype check, performance panel, render notification)
* Try to make libGDX's Pools thread safe
This commit is contained in:
Binary file not shown.
Submodule hyperlap2d-runtime-libgdx updated: bd0fc0d4b4...8457a75aab
+3
-6
@@ -9,9 +9,6 @@ import com.kotcrab.vis.ui.widget.VisLabel;
|
||||
import com.kotcrab.vis.ui.widget.VisTable;
|
||||
import games.rednblack.h2d.common.UIDraggablePanel;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.MemoryUsage;
|
||||
|
||||
public class PerformancePanel extends UIDraggablePanel {
|
||||
|
||||
private final VisTable mainTable;
|
||||
@@ -99,9 +96,9 @@ public class PerformancePanel extends UIDraggablePanel {
|
||||
entitiesCount.setText(entitySubscription.getEntities().size());
|
||||
fpsLbl.setText(Gdx.graphics.getFramesPerSecond());
|
||||
|
||||
MemoryUsage memoryUsage = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
|
||||
long usedMemory = memoryUsage.getUsed() / (1024 * 1024);
|
||||
long allocatedMemory = memoryUsage.getCommitted() / (1024 * 1024);
|
||||
long used = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
|
||||
long usedMemory = used / (1024 * 1024);
|
||||
long allocatedMemory = Runtime.getRuntime().totalMemory() / (1024 * 1024);
|
||||
memoryLabel.getText().clear();
|
||||
memoryLabel.getText().append(usedMemory);
|
||||
memoryLabel.getText().append(" of ");
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
/*******************************************************************************
|
||||
* Copyright 2011 See AUTHORS file.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
******************************************************************************/
|
||||
|
||||
package com.badlogic.gdx.utils;
|
||||
|
||||
/** A pool of objects that can be reused to avoid allocation.
|
||||
* @see Pools
|
||||
* @author Nathan Sweet */
|
||||
abstract public class Pool<T> {
|
||||
/** The maximum number of objects that will be pooled. */
|
||||
public final int max;
|
||||
/** The highest number of free objects. Can be reset any time. */
|
||||
public int peak;
|
||||
|
||||
private final Array<T> freeObjects;
|
||||
|
||||
/** Creates a pool with an initial capacity of 16 and no maximum. */
|
||||
public Pool () {
|
||||
this(16, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** Creates a pool with the specified initial capacity and no maximum. */
|
||||
public Pool (int initialCapacity) {
|
||||
this(initialCapacity, Integer.MAX_VALUE);
|
||||
}
|
||||
|
||||
/** @param initialCapacity The initial size of the array supporting the pool. No objects are created/pre-allocated. Use
|
||||
* {@link #fill(int)} after instantiation if needed.
|
||||
* @param max The maximum number of free objects to store in this pool. */
|
||||
public Pool (int initialCapacity, int max) {
|
||||
freeObjects = new Array(false, initialCapacity);
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
abstract protected T newObject ();
|
||||
|
||||
/** Returns an object from this pool. The object may be new (from {@link #newObject()}) or reused (previously
|
||||
* {@link #free(Object) freed}). */
|
||||
public T obtain () {
|
||||
synchronized (freeObjects) {
|
||||
return freeObjects.size == 0 ? newObject() : freeObjects.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/** Puts the specified object in the pool, making it eligible to be returned by {@link #obtain()}. If the pool already contains
|
||||
* {@link #max} free objects, the specified object is {@link #discard(Object) discarded}, it is not reset and not added to the
|
||||
* pool.
|
||||
* <p>
|
||||
* The pool does not check if an object is already freed, so the same object must not be freed multiple times. */
|
||||
public void free (T object) {
|
||||
synchronized (freeObjects) {
|
||||
if (object == null) throw new IllegalArgumentException("object cannot be null.");
|
||||
if (freeObjects.size < max) {
|
||||
freeObjects.add(object);
|
||||
peak = Math.max(peak, freeObjects.size);
|
||||
reset(object);
|
||||
} else
|
||||
discard(object);
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds the specified number of new free objects to the pool. Usually called early on as a pre-allocation mechanism but can be
|
||||
* used at any time.
|
||||
*
|
||||
* @param size the number of objects to be added */
|
||||
public void fill (int size) {
|
||||
synchronized (freeObjects) {
|
||||
for (int i = 0; i < size; i++)
|
||||
if (freeObjects.size < max) freeObjects.add(newObject());
|
||||
peak = Math.max(peak, freeObjects.size);
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when an object is freed to clear the state of the object for possible later reuse. The default implementation calls
|
||||
* {@link Poolable#reset()} if the object is {@link Poolable}. */
|
||||
protected void reset (T object) {
|
||||
if (object instanceof Poolable) ((Poolable)object).reset();
|
||||
}
|
||||
|
||||
/** Called when an object is discarded. This is the case when an object is freed, but the maximum capacity of the pool is
|
||||
* reached, and when the pool is {@link #clear() cleared} */
|
||||
protected void discard (T object) {
|
||||
reset(object);
|
||||
}
|
||||
|
||||
/** Puts the specified objects in the pool. Null objects within the array are silently ignored.
|
||||
* <p>
|
||||
* The pool does not check if an object is already freed, so the same object must not be freed multiple times.
|
||||
* @see #free(Object) */
|
||||
public void freeAll (Array<T> objects) {
|
||||
synchronized (freeObjects) {
|
||||
if (objects == null) throw new IllegalArgumentException("objects cannot be null.");
|
||||
Array<T> freeObjects = this.freeObjects;
|
||||
int max = this.max;
|
||||
for (int i = 0, n = objects.size; i < n; i++) {
|
||||
T object = objects.get(i);
|
||||
if (object == null) continue;
|
||||
if (freeObjects.size < max) {
|
||||
freeObjects.add(object);
|
||||
reset(object);
|
||||
} else {
|
||||
discard(object);
|
||||
}
|
||||
}
|
||||
peak = Math.max(peak, freeObjects.size);
|
||||
}
|
||||
}
|
||||
|
||||
/** Removes and discards all free objects from this pool. */
|
||||
public void clear () {
|
||||
synchronized (freeObjects) {
|
||||
Array<T> freeObjects = this.freeObjects;
|
||||
for (int i = 0, n = freeObjects.size; i < n; i++)
|
||||
discard(freeObjects.get(i));
|
||||
freeObjects.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/** The number of objects available to be obtained. */
|
||||
public int getFree () {
|
||||
synchronized (freeObjects) {
|
||||
return freeObjects.size;
|
||||
}
|
||||
}
|
||||
|
||||
/** Objects implementing this interface will have {@link #reset()} called when passed to {@link Pool#free(Object)}. */
|
||||
static public interface Poolable {
|
||||
/** Resets the object for reuse. Object references should be nulled and fields may be set to default values. */
|
||||
public void reset ();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.badlogic.gdx.utils;
|
||||
|
||||
public class Pools {
|
||||
static private final ThreadLocal<ObjectMap<Class, Pool>> typePools = new ThreadLocal<ObjectMap<Class, Pool>>() {
|
||||
protected ObjectMap<Class, Pool> initialValue () {
|
||||
return new ObjectMap<Class, Pool>();
|
||||
};
|
||||
};
|
||||
|
||||
/** Returns a new or existing pool for the specified type, stored in a Class to {@link Pool} map. Note the max size is ignored
|
||||
* if this is not the first time this pool has been requested. */
|
||||
static public <T> Pool<T> get (Class<T> type, int max) {
|
||||
Pool pool = typePools.get().get(type);
|
||||
if (pool == null) {
|
||||
pool = new ReflectionPool(type, 4, max);
|
||||
typePools.get().put(type, pool);
|
||||
}
|
||||
return pool;
|
||||
}
|
||||
|
||||
/** Returns a new or existing pool for the specified type, stored in a Class to {@link Pool} map. The max size of the pool used
|
||||
* is 100. */
|
||||
static public <T> Pool<T> get (Class<T> type) {
|
||||
return get(type, 100);
|
||||
}
|
||||
|
||||
/** Sets an existing pool for the specified type, stored in a Class to {@link Pool} map. */
|
||||
static public <T> void set (Class<T> type, Pool<T> pool) {
|
||||
typePools.get().put(type, pool);
|
||||
}
|
||||
|
||||
/** Obtains an object from the {@link #get(Class) pool}. */
|
||||
static public <T> T obtain (Class<T> type) {
|
||||
return get(type).obtain();
|
||||
}
|
||||
|
||||
/** Frees an object from the {@link #get(Class) pool}. */
|
||||
static public void free (Object object) {
|
||||
if (object == null) throw new IllegalArgumentException("object cannot be null.");
|
||||
Pool pool = typePools.get().get(object.getClass());
|
||||
if (pool == null) return; // Ignore freeing an object that was never retained.
|
||||
pool.free(object);
|
||||
}
|
||||
|
||||
/** Frees the specified objects from the {@link #get(Class) pool}. Null objects within the array are silently ignored. Objects
|
||||
* don't need to be from the same pool. */
|
||||
static public void freeAll (Array objects) {
|
||||
freeAll(objects, false);
|
||||
}
|
||||
|
||||
/** Frees the specified objects from the {@link #get(Class) pool}. Null objects within the array are silently ignored.
|
||||
* @param samePool If true, objects don't need to be from the same pool but the pool must be looked up for each object. */
|
||||
static public void freeAll (Array objects, boolean samePool) {
|
||||
if (objects == null) throw new IllegalArgumentException("objects cannot be null.");
|
||||
Pool pool = null;
|
||||
for (int i = 0, n = objects.size; i < n; i++) {
|
||||
Object object = objects.get(i);
|
||||
if (object == null) continue;
|
||||
if (pool == null) {
|
||||
pool = typePools.get().get(object.getClass());
|
||||
if (pool == null) continue; // Ignore freeing an object that was never retained.
|
||||
}
|
||||
pool.free(object);
|
||||
if (!samePool) pool = null;
|
||||
}
|
||||
}
|
||||
|
||||
public static String name() {
|
||||
return "ThreadSafe Pools";
|
||||
}
|
||||
|
||||
private Pools () {
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,11 @@ public class HyperLap2D implements IProxy, ApplicationListener, Lwjgl3WindowList
|
||||
facade.sendNotification(MsgAPI.RESUME);
|
||||
}
|
||||
|
||||
float[] delta = new float[1];
|
||||
@Override
|
||||
public void render() {
|
||||
facade.sendNotification(MsgAPI.RENDER, Math.min(Gdx.graphics.getDeltaTime(), 0.1f));
|
||||
delta[0] = Math.min(Gdx.graphics.getDeltaTime(), 0.1f);
|
||||
facade.sendNotification(MsgAPI.RENDER, delta);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,6 +2,7 @@ package games.rednblack.editor.controller;
|
||||
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.graphics.GL20;
|
||||
import com.badlogic.gdx.utils.Pools;
|
||||
import games.rednblack.editor.utils.AppConfig;
|
||||
import games.rednblack.h2d.common.HyperLog;
|
||||
import games.rednblack.puremvc.commands.SimpleCommand;
|
||||
@@ -21,5 +22,7 @@ public class BootstrapInfoCommand extends SimpleCommand {
|
||||
HyperLog.info("Shaders version " + gl20.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION));
|
||||
|
||||
HyperLog.info("JVM Version: " + System.getProperty("java.version") + " (" + System.getProperty("java.vendor") + ")");
|
||||
|
||||
HyperLog.info(Pools.name());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,18 +91,18 @@ public class ResourceManager extends Proxy implements IResourceRetriever {
|
||||
packer.setTransparentColor(Color.WHITE);
|
||||
packer.getTransparentColor().a = 0;
|
||||
|
||||
FreeTypeFontGenerator dejaVuSansGenerator = new FreeTypeFontGenerator(Gdx.files.internal("freetypefonts/DejaVuSans.ttf")) {
|
||||
FreeTypeFontGenerator dejaVuSansGenerator = new FreeTypeFontGenerator(Gdx.files.internal("freetypefonts/DejaVuSans.ttf")) /*{
|
||||
@Override
|
||||
protected BitmapFont newBitmapFont(BitmapFont.BitmapFontData data, Array<TextureRegion> pageRegions, boolean integer) {
|
||||
return new ThreadSafeBitmapFont(data, pageRegions, integer);
|
||||
}
|
||||
};
|
||||
FreeTypeFontGenerator monoGenerator = new FreeTypeFontGenerator(Gdx.files.internal("freetypefonts/FiraCode-Regular.ttf")){
|
||||
}*/;
|
||||
FreeTypeFontGenerator monoGenerator = new FreeTypeFontGenerator(Gdx.files.internal("freetypefonts/FiraCode-Regular.ttf"))/*{
|
||||
@Override
|
||||
protected BitmapFont newBitmapFont(BitmapFont.BitmapFontData data, Array<TextureRegion> pageRegions, boolean integer) {
|
||||
return new ThreadSafeBitmapFont(data, pageRegions, integer);
|
||||
}
|
||||
};
|
||||
}*/;
|
||||
|
||||
FreeTypeFontGenerator.FreeTypeFontParameter parameter = new FreeTypeFontGenerator.FreeTypeFontParameter();
|
||||
parameter.characters += "⌘⇧⌥\u25CF\u2022";
|
||||
|
||||
@@ -3,6 +3,7 @@ package games.rednblack.editor.utils;
|
||||
import com.badlogic.gdx.files.FileHandle;
|
||||
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
|
||||
import com.badlogic.gdx.utils.Array;
|
||||
import com.badlogic.gdx.utils.IntArray;
|
||||
import com.kotcrab.vis.ui.widget.file.FileTypeFilter;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
@@ -10,7 +11,6 @@ import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class AssetsUtils {
|
||||
|
||||
@@ -64,38 +64,45 @@ public class AssetsUtils {
|
||||
return fileTypeFilter;
|
||||
}
|
||||
|
||||
public static boolean isAnimationSequence(String[] names) {
|
||||
if (names.length < 2) return false;
|
||||
int[] sequenceArray = new int[names.length];
|
||||
for (int i = 0; i < names.length; i++) {
|
||||
String name = names[i];
|
||||
public static IntArray sequenceArray = new IntArray();
|
||||
|
||||
public static boolean isAnimationSequence(Array<String> files) {
|
||||
if (files.size < 2) return false;
|
||||
sequenceArray.clear();
|
||||
sequenceArray.ensureCapacity(files.size);
|
||||
sequenceArray.size = files.size;
|
||||
|
||||
for (int i = 0; i < files.size; i++) {
|
||||
String name = files.get(i);
|
||||
// try to remove extension if any
|
||||
if (name.indexOf(".") > 0) name = name.substring(0, name.indexOf("."));
|
||||
try {
|
||||
int intValue = Integer.parseInt(name.replaceAll("(.+)_", ""));
|
||||
sequenceArray[i] = intValue;
|
||||
sequenceArray.insert(i, intValue);
|
||||
} catch (Exception e) {
|
||||
sequenceArray[i] = -10;
|
||||
sequenceArray.insert(i, -1);
|
||||
}
|
||||
}
|
||||
Arrays.sort(sequenceArray);
|
||||
if (sequenceArray[0] == 0 && sequenceArray[sequenceArray.length - 1] == sequenceArray.length - 1) {
|
||||
sequenceArray.sort();
|
||||
if (sequenceArray.get(0) == 0 && sequenceArray.get(sequenceArray.size - 1) == sequenceArray.size - 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return sequenceArray[0] == 1 && sequenceArray[sequenceArray.length - 1] == sequenceArray.length;
|
||||
return sequenceArray.get(0) == 1 && sequenceArray.get(sequenceArray.size - 1) == sequenceArray.size;
|
||||
}
|
||||
|
||||
private final static Array<String> tmpNames = new Array<>();
|
||||
|
||||
public static boolean isAtlasAnimationSequence(Array<TextureAtlas.TextureAtlasData.Region> regions) {
|
||||
if (regions.size < 2) return false;
|
||||
|
||||
tmpNames.clear();
|
||||
//Check old .atlas format
|
||||
String[] regionNames = new String[regions.size];
|
||||
for (int i = 0; i < regions.size; i++) {
|
||||
regionNames[i] = regions.get(i).name;
|
||||
tmpNames.add(regions.get(i).name);
|
||||
}
|
||||
|
||||
if (isAnimationSequence(regionNames))
|
||||
if (isAnimationSequence(tmpNames))
|
||||
return true;
|
||||
|
||||
//New .atlas format
|
||||
|
||||
@@ -60,13 +60,15 @@ public abstract class Asset implements IAsset {
|
||||
|
||||
// save before importing
|
||||
SceneVO vo = Sandbox.getInstance().sceneVoFromItems();
|
||||
projectManager.saveCurrentProject(vo);
|
||||
if (!skipRepack) //Skip saving if internal resource
|
||||
projectManager.saveCurrentProject(vo);
|
||||
|
||||
ExecutorService executor = Executors.newSingleThreadExecutor();
|
||||
executor.execute(() -> importAsset(files, progressHandler, skipRepack));
|
||||
executor.execute(() -> {
|
||||
progressHandler.progressChanged(100);
|
||||
projectManager.saveCurrentProject();
|
||||
if (!skipRepack) //Skip saving if internal resource
|
||||
projectManager.saveCurrentProject();
|
||||
try {
|
||||
Thread.sleep(300);
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
@@ -134,6 +134,7 @@ public class HyperLap2DLibraryAsset extends Asset {
|
||||
|
||||
ResolutionManager resolutionManager = Facade.getInstance().retrieveProxy(ResolutionManager.NAME);
|
||||
resolutionManager.rePackProjectImagesForAllResolutionsSync();
|
||||
projectManager.saveCurrentProject();
|
||||
|
||||
progressHandler.progressChanged(100);
|
||||
|
||||
|
||||
@@ -26,11 +26,13 @@ import java.util.function.Consumer;
|
||||
|
||||
public class ImageAsset extends Asset {
|
||||
|
||||
private final Array<String> names = new Array<>();
|
||||
|
||||
@Override
|
||||
public int matchType(Array<FileHandle> files) {
|
||||
String[] names = new String[files.size];
|
||||
names.clear();
|
||||
for (int i = 0; i < files.size; i++) {
|
||||
names[i] = files.get(i).nameWithoutExtension();
|
||||
names.add(files.get(i).nameWithoutExtension());
|
||||
}
|
||||
|
||||
return AssetsUtils.isAnimationSequence(names) ? AssetsUtils.TYPE_UNKNOWN : super.matchType(files);
|
||||
|
||||
+4
-2
@@ -15,11 +15,13 @@ import java.util.Objects;
|
||||
|
||||
public class SpriteAnimationSequenceAsset extends SpriteAnimationAtlasAsset {
|
||||
|
||||
private final Array<String> names = new Array<>();
|
||||
|
||||
@Override
|
||||
public int matchType(Array<FileHandle> files) {
|
||||
String[] names = new String[files.size];
|
||||
names.clear();
|
||||
for (int i = 0; i < files.size; i++) {
|
||||
names[i] = files.get(i).nameWithoutExtension();
|
||||
names.add(files.get(i).nameWithoutExtension());
|
||||
}
|
||||
return AssetsUtils.isAnimationSequence(names) ? getType() : AssetsUtils.TYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
@@ -80,7 +80,8 @@ public class HyperLap2DScreenMediator extends Mediator<HyperLap2DScreen> {
|
||||
viewComponent.resume();
|
||||
break;
|
||||
case MsgAPI.RENDER:
|
||||
viewComponent.render(notification.getBody());
|
||||
float[] delta = notification.getBody();
|
||||
viewComponent.render(delta[0]);
|
||||
break;
|
||||
case MsgAPI.RESIZE:
|
||||
int[] data = notification.getBody();
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package games.rednblack.editor.view.ui.followers;
|
||||
|
||||
import com.artemis.ComponentMapper;
|
||||
import com.badlogic.gdx.Gdx;
|
||||
import com.badlogic.gdx.Input;
|
||||
import com.badlogic.gdx.graphics.Color;
|
||||
import com.badlogic.gdx.graphics.OrthographicCamera;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
package games.rednblack.editor.view.ui.widget.actors.polygon;
|
||||
|
||||
import com.badlogic.gdx.graphics.g2d.Batch;
|
||||
import com.badlogic.gdx.math.Intersector;
|
||||
import com.badlogic.gdx.math.MathUtils;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor;
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable;
|
||||
import com.badlogic.gdx.utils.Align;
|
||||
import com.badlogic.gdx.utils.Pool;
|
||||
import space.earlygrey.shapedrawer.ShapeDrawer;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user