IMPORTANT: @Core Devs (and anyone using large Worlds in Jme) MemoryManagement issues/problems

@abies Yes, but I’m currently trieng to amke a more genral approach so the developer can choose what he wants.



So first things first:

This here makes every ByteBuffer.allocateDirect to use BufferUtils.createByteBuffer()





Index: src/networking/com/jme3/network/connection/TCPConnection.java

===================================================================

— src/networking/com/jme3/network/connection/TCPConnection.java (revision 7837)

+++ src/networking/com/jme3/network/connection/TCPConnection.java (working copy)

@@ -35,6 +35,8 @@

import com.jme3.network.message.Message;

import com.jme3.network.queue.MessageQueue;

import com.jme3.network.serializing.Serializer;

+import com.jme3.util.BufferUtils;

+

import java.io.IOException;

import java.net.InetSocketAddress;

import java.net.SocketAddress;

@@ -72,9 +74,9 @@

{

label = name;


  •    readBuffer =        ByteBuffer.allocateDirect(16228);<br />
    
  •    writeBuffer =       ByteBuffer.allocateDirect(16228);<br />
    
  •    tempWriteBuffer =   ByteBuffer.allocateDirect(16228);<br />
    
  •    readBuffer =        BufferUtils.createByteBuffer(16228);<br />
    
  •    writeBuffer =       BufferUtils.createByteBuffer(16228);<br />
    
  •    tempWriteBuffer =   BufferUtils.createByteBuffer(16228);<br />
    

}



public TCPConnection() { }

Index: src/networking/com/jme3/network/connection/SSLTCPConnection.java

===================================================================

— src/networking/com/jme3/network/connection/SSLTCPConnection.java (revision 7837)

+++ src/networking/com/jme3/network/connection/SSLTCPConnection.java (working copy)

@@ -33,6 +33,9 @@

package com.jme3.network.connection;



import javax.net.ssl.*;

+

+import com.jme3.util.BufferUtils;

+

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

@@ -83,9 +86,9 @@



SSLSession session = sslEngine.getSession();


  •    incDataDecrypted    = ByteBuffer.allocateDirect(session.getApplicationBufferSize());<br />
    
  •    incDataEncrypted    = ByteBuffer.allocateDirect(session.getPacketBufferSize());<br />
    
  •    outDataEncrypted    = ByteBuffer.allocateDirect(session.getPacketBufferSize());<br />
    
  •    incDataDecrypted    = BufferUtils.createByteBuffer(session.getApplicationBufferSize());<br />
    
  •    incDataEncrypted    = BufferUtils.createByteBuffer(session.getPacketBufferSize());<br />
    
  •    outDataEncrypted    = BufferUtils.createByteBuffer(session.getPacketBufferSize());<br />
    

incDataEncrypted.position(incDataEncrypted.limit());
outDataEncrypted.position(outDataEncrypted.limit());
Index: src/networking/com/jme3/network/connection/UDPConnection.java
===================================================================
--- src/networking/com/jme3/network/connection/UDPConnection.java (revision 7837)
+++ src/networking/com/jme3/network/connection/UDPConnection.java (working copy)
@@ -35,6 +35,8 @@
import com.jme3.network.message.DiscoverHostMessage;
import com.jme3.network.message.Message;
import com.jme3.network.serializing.Serializer;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -61,8 +63,8 @@
public UDPConnection(String label) {
this.label = label;

- readBuffer = ByteBuffer.allocateDirect(8192);
- writeBuffer = ByteBuffer.allocateDirect(8192);
+ readBuffer = BufferUtils.createByteBuffer(8192);
+ writeBuffer = BufferUtils.createByteBuffer(8192);
}

public void connect(SocketAddress address) throws IOException {
Index: src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java
===================================================================
--- src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java (revision 7837)
+++ src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java (working copy)
@@ -77,7 +77,7 @@
private final static int MAX_NUM_CHANNELS = 64;
private IntBuffer ib = BufferUtils.createIntBuffer(1);
private final FloatBuffer fb = BufferUtils.createVector3Buffer(2);
- private final ByteBuffer nativeBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
+ private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE);
private final byte[] arrayBuf = new byte[BUFFER_SIZE];

private int[] channels;
Index: src/terrain/com/jme3/terrain/GeoMap.java
===================================================================
--- src/terrain/com/jme3/terrain/GeoMap.java (revision 7837)
+++ src/terrain/com/jme3/terrain/GeoMap.java (working copy)
@@ -68,7 +68,7 @@
}

public GeoMap(int width, int height, int maxval) {
- this(ByteBuffer.allocateDirect(width*height*4).asFloatBuffer(),null,width,height,maxval);
+ this(BufferUtils.createByteBuffer(width*height*4).asFloatBuffer(),null,width,height,maxval);
}

public FloatBuffer getHeightData(){
@@ -187,7 +187,7 @@
* Copies a section of this geomap as a new geomap
*/
public GeoMap copySubGeomap(int x, int y, int w, int h){
- FloatBuffer nhdata = ByteBuffer.allocateDirect(w*h*4).asFloatBuffer();
+ FloatBuffer nhdata = BufferUtils.createByteBuffer(w*h*4).asFloatBuffer();
hdata.position(y*width+x);
for (int cy = 0; cy < height; cy++){
hdata.limit(hdata.position()+w);
@@ -199,7 +199,7 @@

ByteBuffer nndata = null;
if (ndata!=null){
- nndata = ByteBuffer.allocateDirect(w*h*3);
+ nndata = BufferUtils.createByteBuffer(w*h*3);
ndata.position( (y*width+x)*3 );
for (int cy = 0; cy < height; cy++){
ndata.limit(ndata.position()+w*3);
Index: src/bullet/com/jme3/bullet/util/NativeMeshUtil.java
===================================================================
--- src/bullet/com/jme3/bullet/util/NativeMeshUtil.java (revision 7837)
+++ src/bullet/com/jme3/bullet/util/NativeMeshUtil.java (working copy)
@@ -34,6 +34,8 @@
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@@ -45,8 +47,8 @@
public class NativeMeshUtil {

public static long getTriangleIndexVertexArray(Mesh mesh){
- ByteBuffer triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
- ByteBuffer vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ ByteBuffer triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ ByteBuffer vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
int numVertices = mesh.getVertexCount();
int vertexStride = 12; //3 verts * 4 bytes per.
int numTriangles = mesh.getTriangleCount();
Index: src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java
===================================================================
--- src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java (revision 7837)
+++ src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java (working copy)
@@ -11,6 +11,8 @@
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -94,7 +96,7 @@
}

protected void createShape() {
- bbuf = ByteBuffer.allocateDirect(heightfieldData.length * 4).order(ByteOrder.nativeOrder());
+ bbuf = BufferUtils.createByteBuffer(heightfieldData.length * 4).order(ByteOrder.nativeOrder());
// fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData);
// fbuf.rewind();
// fbuf.put(heightfieldData);
Index: src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java
===================================================================
--- src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java (revision 7837)
+++ src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java (working copy)
@@ -39,6 +39,8 @@
import com.jme3.export.OutputCapsule;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -70,8 +72,8 @@
}

private void createCollisionMesh(Mesh mesh) {
- triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
- vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
// triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4);
// vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4);
numVertices = mesh.getVertexCount();
Index: src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java
===================================================================
--- src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java (revision 7837)
+++ src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java (working copy)
@@ -7,6 +7,8 @@
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -66,7 +68,7 @@
// objectId = new ConvexHullShape(pointList);
// objectId.setLocalScaling(Converter.convert(getScale()));
// objectId.setMargin(margin);
- ByteBuffer bbuf=ByteBuffer.allocateDirect(points.length * 4).order(ByteOrder.nativeOrder());
+ ByteBuffer bbuf=BufferUtils.createByteBuffer(points.length * 4).order(ByteOrder.nativeOrder());
// fbuf = bbuf.asFloatBuffer();
// fbuf.rewind();
// fbuf.put(points);
Index: src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java
===================================================================
--- src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java (revision 7837)
+++ src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java (working copy)
@@ -45,6 +45,7 @@
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -177,7 +178,7 @@
// The number of bytes needed is: (floats in a vertex) * (vertices in a triangle) * (# of triangles) * (size of float in bytes)
final int numberOfFloats = 3 * 3 * numberOfTriangles;
final int byteBufferSize = numberOfFloats * Float.SIZE;
- FloatBuffer vertices = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ FloatBuffer vertices = BufferUtils.createByteBuffer(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();

// Force the limit, set the cap - most number of floats we will use the buffer for
vertices.limit(numberOfFloats);
@@ -236,7 +237,7 @@
// There are 3 floats needed for each vertex (x,y,z)
final int numberOfFloats = vertices.size() * 3;
final int byteBufferSize = numberOfFloats * Float.SIZE;
- FloatBuffer verticesBuffer = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ FloatBuffer verticesBuffer = BufferUtils.createByteBuffer(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();

// Force the limit, set the cap - most number of floats we will use the buffer for
verticesBuffer.limit(numberOfFloats);

And here a better idea for the AssetCache, this one does not define anything itself but is abstract implementation, Assetkey is changed to give a hint to the usedAssetcache what it should do

KeepAlways,SmartCache,KeepNever, however the implementation is free to do something else.

Also there is a defaultimplementation that SimpleApplication now uses that simply caches everything in a HashMap.

The Implementation can be changed by useing prior starting the application

AssetCache.setImplementation(new AllCachingAssetCache());







Index: src/desktop/com/jme3/asset/AllCachingAssetCache.java

===================================================================

— src/desktop/com/jme3/asset/AllCachingAssetCache.java (revision 0)

+++ src/desktop/com/jme3/asset/AllCachingAssetCache.java (revision 0)

@@ -0,0 +1,39 @@

+package com.jme3.asset;

+

+import java.util.HashMap;

+

+/**

    • A dumb assetcache, it is not able to cleanup itself.
    • @author Empire-Phoenix
  • *
  • */

    +public class AllCachingAssetCache extends AssetCache {
  • HashMap<AssetKey,Object> cache = new HashMap<AssetKey,Object>();

    +
  • @Override
  • public void addToCache(AssetKey key, Object obj) {
  •   synchronized(cache){<br />
    
  •   	cache.put(key, obj);<br />
    
  •   }<br />
    
  • }

    +
  • @Override
  • public boolean deleteFromCache(AssetKey key) {
  •   synchronized(cache){<br />
    
  •   	return cache.remove(key)!= null;<br />
    
  •   }<br />
    
  • }

    +
  • @Override
  • public Object getFromCache(AssetKey key) {
  •   synchronized(cache){<br />
    
  •   	return cache.get(key);<br />
    
  •   }<br />
    
  • }

    +
  • @Override
  • public void clearCache() {
  •   cache.clear();<br />
    
  • }

    +

    +}

    Index: src/desktop/com/jme3/asset/AssetCache.java

    ===================================================================

    — src/desktop/com/jme3/asset/AssetCache.java (revision 7837)

    +++ src/desktop/com/jme3/asset/AssetCache.java (working copy)

    @@ -37,94 +37,50 @@

    import java.util.WeakHashMap;



    /**
    • An <code>AssetCache</code> allows storage of loaded resources in order
    • to improve their access time if they are requested again in a short period
    • of time. The AssetCache stores weak references to the resources, allowing
    • Java’s garbage collector to request deletion of rarely used resources
    • when heap memory is low.

      +* This Class is a abstract to define own AssetCaches.

      +* The default implementation is the AllCachingAssetCache

      +* The implementations have to make sure they are Threadsave to not break this contract!

      */

      -public class AssetCache {

      +public abstract class AssetCache {
  • private static AssetCache currentimplementation;


  • public static final class SmartAssetInfo {
  •    public WeakReference&lt;AssetKey&gt; smartKey;<br />
    
  •    public Asset asset;<br />
    
  • public static AssetCache getImplementation(){
  •   return currentimplementation;<br />
    

}

-

  • private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache
  •        = new WeakHashMap&lt;AssetKey, SmartAssetInfo&gt;();<br />
    
  • private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();

    -
  • /**

    +
  • public static void setImplementation(AssetCache cache){
  •   if(currentimplementation != null){<br />
    
  •   	throw new RuntimeException(&quot;Only one AssetCache can be set, it must be prior to starting the application&quot;);<br />
    
  •   }<br />
    
  •   currentimplementation = cache;<br />
    
  • }

    +
  • /**
  • Adds a resource to the cache.
  • <br/><br/>
  • <font color="red">Thread-safe.</font>
  • @see #getFromCache(java.lang.String)

    */
  • public void addToCache(AssetKey key, Object obj){
  •    synchronized (regularCache){<br />
    
  •        if (obj instanceof Asset &amp;&amp; key.useSmartCache()){<br />
    
  •            // put in smart cache<br />
    
  •            Asset asset = (Asset) obj;<br />
    
  •            asset.setKey(null); // no circular references<br />
    
  •            SmartAssetInfo smartInfo = new SmartAssetInfo();<br />
    
  •            smartInfo.asset = asset;<br />
    
  •            // use the original key as smart key<br />
    
  •            smartInfo.smartKey = new WeakReference&lt;AssetKey&gt;(key);<br />
    
  •            smartCache.put(key, smartInfo);<br />
    
  •        }else{<br />
    
  •            // put in regular cache<br />
    
  •            regularCache.put(key, obj);<br />
    
  •        }<br />
    
  •    }<br />
    
  • }
  • public abstract void addToCache(AssetKey key, Object obj);



    /**
  • * Delete an asset from the cache, returns true if it was deleted successfuly.<br />
    
  • * Delete an asset from the cache, returns true if it was deleted successfuly or the implementation cannot determine if it was deleted<br />
    
  • <br/><br/>
  • <font color="red">Thread-safe.</font>

    */
  • public boolean deleteFromCache(AssetKey key){
  •    if (key.useSmartCache()){<br />
    
  •        throw new UnsupportedOperationException(&quot;You cannot delete from the smart cache&quot;);<br />
    
  •    }<br />
    

-

  •    synchronized (regularCache){<br />
    
  •        return regularCache.remove(key) != null;<br />
    
  •    }<br />
    
  • }
  • public abstract boolean deleteFromCache(AssetKey key);



    /**
  • * Gets an object from the cache given an asset key.<br />
    
  • * Gets an object from the cache given an asset key, the implementation is free to return null if it was unloaded<br />
    
  • <br/><br/>
  • <font color="red">Thread-safe.</font>
  • @param key
  • @return

    */
  • public Object getFromCache(AssetKey key){
  •    synchronized (regularCache){<br />
    
  •        if (key.useSmartCache()) {<br />
    
  •            return smartCache.get(key).asset;<br />
    
  •        } else {<br />
    
  •            return regularCache.get(key);<br />
    
  •        }<br />
    
  •    }<br />
    
  • }
  • public abstract Object getFromCache(AssetKey key);



    /**
  • * Retrieves smart asset info from the cache.<br />
    
  • * @param key<br />
    
  • * @return<br />
    
  • * Deletes all the assets in the cache<br />
    

*/

  • public SmartAssetInfo getFromSmartCache(AssetKey key){
  •    return smartCache.get(key);<br />
    
  • }

    -
  • /**
  • * Deletes all the assets in the regular cache.<br />
    
  • */<br />
    
  • public void deleteAllAssets(){
  •    synchronized (regularCache){<br />
    
  •        regularCache.clear();<br />
    
  •    }<br />
    
  • }
  • public abstract void clearCache();

    }

    Index: src/desktop/com/jme3/asset/DesktopAssetManager.java

    ===================================================================

    — src/desktop/com/jme3/asset/DesktopAssetManager.java (revision 7837)

    +++ src/desktop/com/jme3/asset/DesktopAssetManager.java (working copy)

    @@ -32,7 +32,6 @@



    package com.jme3.asset;



    -import com.jme3.asset.AssetCache.SmartAssetInfo;

    import com.jme3.audio.AudioKey;

    import com.jme3.audio.AudioData;

    import com.jme3.font.BitmapFont;

    @@ -58,7 +57,6 @@



    private static final Logger logger = Logger.getLogger(AssetManager.class.getName());


  • private final AssetCache cache = new AssetCache();

    private final ImplHandler handler = new ImplHandler(this);



    private AssetEventListener eventListener = null;

    @@ -152,7 +150,7 @@

    }



    public void clearCache(){
  •    cache.deleteAllAssets();<br />
    
  •    AssetCache.getImplementation().clearCache();<br />
    

}



/**

@@ -162,7 +160,7 @@

  • <font color="red">Thread-safe.</font>

    */

    public boolean deleteFromCache(AssetKey key){
  •    return cache.deleteFromCache(key);<br />
    
  •    return  AssetCache.getImplementation().deleteFromCache(key);<br />
    

}



/**

@@ -171,7 +169,7 @@

  • <font color="red">Thread-safe.</font>

    */

    public void addToCache(AssetKey key, Object asset){
  •    cache.addToCache(key, asset);<br />
    
  •    AssetCache.getImplementation().addToCache(key, asset);<br />
    

}



public AssetInfo locateAsset(AssetKey<?> key){

@@ -202,21 +200,7 @@

if (eventListener != null)

eventListener.assetRequested(key);


  •    AssetKey smartKey = null;<br />
    
  •    Object o = null;<br />
    
  •    if (key.shouldCache()){<br />
    
  •        if (key.useSmartCache()){<br />
    
  •            SmartAssetInfo smartInfo = cache.getFromSmartCache(key);<br />
    
  •            if (smartInfo != null){<br />
    
  •                smartKey = smartInfo.smartKey.get();<br />
    
  •                if (smartKey != null){<br />
    
  •                    o = smartInfo.asset;<br />
    
  •                }<br />
    
  •            }<br />
    
  •        }else{<br />
    
  •            o = cache.getFromCache(key);<br />
    
  •        }<br />
    
  •    }<br />
    
  •    Object o = AssetCache.getImplementation().getFromCache(key);<br />
    

if (o == null){

AssetLoader loader = handler.aquireLoader(key);

if (loader == null){

@@ -252,8 +236,7 @@

// do processing on asset before caching

o = key.postProcess(o);


  •            if (key.shouldCache())<br />
    
  •                cache.addToCache(key, o);<br />
    
  •            AssetCache.getImplementation().addToCache(key, o);<br />
    

if (eventListener != null)
eventListener.assetLoaded(key);
@@ -263,17 +246,7 @@
// object o is the asset
// create an instance for user
T clone = (T) key.createClonedInstance(o);
-
- if (key.useSmartCache()){
- if (smartKey != null){
- // smart asset was already cached, use original key
- ((Asset)clone).setKey(smartKey);
- }else{
- // smart asset was cached on this call, use our key
- ((Asset)clone).setKey(key);
- }
- }
-
+
return clone;
}

@@ -350,9 +323,8 @@
* @return
*/
public Shader loadShader(ShaderKey key){
- // cache abuse in method
// that doesn't use loaders/locators
- Shader s = (Shader) cache.getFromCache(key);
+ Shader s = (Shader) AssetCache.getImplementation().getFromCache(key);
if (s == null){
String vertName = key.getVertName();
String fragName = key.getFragName();
@@ -364,7 +336,7 @@
s.addSource(Shader.ShaderType.Vertex, vertName, vertSource, key.getDefines().getCompiled());
s.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled());

- cache.addToCache(key, s);
+ AssetCache.getImplementation().addToCache(key, s);
}
return s;
}
Index: src/test/jme3test/asset/TestAssetCache.java
===================================================================
--- src/test/jme3test/asset/TestAssetCache.java (revision 7837)
+++ src/test/jme3test/asset/TestAssetCache.java (working copy)
@@ -34,6 +34,7 @@

import com.jme3.asset.AssetCache;
import com.jme3.asset.AssetKey;
+import com.jme3.asset.AllCachingAssetCache;

public class TestAssetCache {

@@ -57,7 +58,7 @@
// }
//
public static void main(String[] args){
- AssetCache cache = new AssetCache();
+ AssetCache cache = new AllCachingAssetCache();

System.gc();
System.gc();
Index: src/core/com/jme3/app/SimpleApplication.java
===================================================================
--- src/core/com/jme3/app/SimpleApplication.java (revision 7837)
+++ src/core/com/jme3/app/SimpleApplication.java (working copy)
@@ -31,6 +31,8 @@
*/
package com.jme3.app;

+import com.jme3.asset.AssetCache;
+import com.jme3.asset.AllCachingAssetCache;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.FlyByCamera;
@@ -201,7 +203,8 @@
@Override
public void initialize() {
super.initialize();
-
+ AssetCache.setImplementation(new AllCachingAssetCache());
+
guiNode.setQueueBucket(Bucket.Gui);
guiNode.setCullHint(CullHint.Never);
loadFPSText();
@@ -226,6 +229,7 @@

}

+
// call user code
simpleInitApp();
}
Index: src/core/com/jme3/audio/AudioKey.java
===================================================================
--- src/core/com/jme3/audio/AudioKey.java (revision 7837)
+++ src/core/com/jme3/audio/AudioKey.java (working copy)
@@ -33,6 +33,7 @@
package com.jme3.audio;

import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetKey.CacheMode;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
@@ -112,10 +113,13 @@
public boolean useStreamCache(){
return streamCache;
}
-
+
@Override
- public boolean shouldCache(){
- return !stream && !streamCache;
+ public CacheMode cachePriority(){
+ if(!stream && !streamCache){
+ return CacheMode.SmartCache;
+ }
+ return CacheMode.KeepNever;
}

@Override
Index: src/core/com/jme3/shader/plugins/GLSLLoader.java
===================================================================
--- src/core/com/jme3/shader/plugins/GLSLLoader.java (revision 7837)
+++ src/core/com/jme3/shader/plugins/GLSLLoader.java (working copy)
@@ -36,6 +36,8 @@
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetKey.CacheMode;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -88,10 +90,12 @@
public GlslDependKey(String name){
super(name);
}
+
@Override
- public boolean shouldCache(){
- return false;
+ public CacheMode cachePriority(){
+ return CacheMode.KeepAlways;
}
+
}

private DependencyNode loadNode(InputStream in, String nodeName) throws IOException{
Index: src/core/com/jme3/asset/TextureKey.java
===================================================================
--- src/core/com/jme3/asset/TextureKey.java (revision 7837)
+++ src/core/com/jme3/asset/TextureKey.java (working copy)
@@ -32,6 +32,7 @@

package com.jme3.asset;

+import com.jme3.asset.AssetKey.CacheMode;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
@@ -68,13 +69,10 @@
return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
}

- /**
- * Enable smart caching for textures
- * @return true to enable smart cache
- */
+
@Override
- public boolean useSmartCache(){
- return true;
+ public CacheMode cachePriority(){
+ return CacheMode.SmartCache;
}

@Override
Index: src/core/com/jme3/asset/AssetKey.java
===================================================================
--- src/core/com/jme3/asset/AssetKey.java (revision 7837)
+++ src/core/com/jme3/asset/AssetKey.java (working copy)
@@ -45,7 +45,10 @@
* This class should be immutable.
*/
public class AssetKey<T> implements Savable {
-
+ /**
+ * This is to determine how the selected AssetCache behaves for this type of ressource, note that it is up to the implementation to decide this it merly a hint
+ */
+ public enum CacheMode{KeepAlways,SmartCache,KeepNever};
protected String name;
protected transient String folder;
protected transient String extension;
@@ -125,17 +128,8 @@
* @return True if the asset for this key should be cached. Subclasses
* should override this method if they want to override caching behavior.
*/
- public boolean shouldCache(){
- return true;
- }
-
- /**
- * @return Should return true, if the asset objects implement the "Asset"
- * interface and want to be removed from the cache when no longer
- * referenced in user-code.
- */
- public boolean useSmartCache(){
- return false;
+ public CacheMode cachePriority(){
+ return CacheMode.SmartCache;
}

@Override

EmpirePhoenix said:
Actually this is the the second thing I want to make, I want to give each Assettype a rating how much memory it usualy takes (aka Shader < Models < Textures) or similar, also I want to get how often a specific asset is used (could be counted over the get method, and how long the last use is away) out of this I then want to determine wich ones should be freed first. (Other approach would be to let the programmer determine with a additional load flag if the asset should stay in memory as long as possible or if it could be freed anytime)

At this point we are somewhat going away from the garbage collection based cache and instead move to a more custom one. E.g. a least recently used cache that prioritizes on asset size.

Well take a look at the second commit, it is a AssetCache suggestion that allows anyone to write their own prefered way of caching assets. I plan something similar for the MemoryManager, so that everyone can choose if they want a FullGC on out of memory or a background thread or whatever.

Ok, more on my issues since I could not talk about them before as I was in a rush.



In general, I think the point of the current AssetManager behavior is a good one. It is doing something that we cannot otherwise do in our own code.



If I want to load fifty different models that all use the same texture then I have the nice feature that all of the textures will be the same instance. Because asset manager is managing the loading, I could not do this myself without a lot of trickery.



If I stop using those fifty models and the texture is no longer used then it goes away. All is right with the world. If I want to keep it around, I can hold a reference to it in some other cache that uses whatever smarts I want.



Smart caches are rarely one size fits all and some applications will not need them at all. They’ll either be doing their own (like me) or just not care.



When AssetManager uses weak references then it does not need to handle smart caching at all as any traditional smart cache will work just fine. Hold a reference to an asset and it never goes away. If some other model tries to load it then it will get the pre-existing instance. Everyone wins whether you’ve rolled your own cache or used ehcache or something else. JME could provide a smart cache for you but I think it feels wrong to include it in asset manager.



If you embed a smart cache into AssetManager you increase the number of default assumptions you have to make and potentially greatly increase the API… for a class that didn’t need it. Moreover you have to worry about the different AssetManager implementations and whether they all interact with the cache in an appropriate way.



Regarding direct memory, the direct memory managed by JME is not a good metric for how much JVM direct memory is available. There could be any of a half-dozen common reasons that lots of direct memory is allocated and managed outside of JME control. The only good and reliable way to implement a smart cache is to give the caller parameters allowing them to set the optimal pool size, maximum pool size, etc.

That direct buffer stuff is way out of line and is too hard for us to handle.

However we can implement the LRU cache and control exactly when an asset is freed. I was thinking something like 60 seconds after its garbage collected, remove it from the cache

So my final basic suggestion:



→ abstract MemoryManager that can be extended dependign on own needs

→ abstract Assetcache that can be extended depending on needs

→ For simpleapplication default assetcache that just caches everything

→ For simpleapplication default MemoryManager that makes full gc on outofdirectmemory and then retries one more time before throwing a out of memory.

→ AssetKeys now use a abstrac enumeration where they can request what they prefer regarding caching (Don’t Cache, Smartcache,Always cache) The AssetCache then can take care of these hints or ignore them depending on implementation.



Note this patch contains all of the above adjustments, and at least under asun jvm it will not crash with out of memory if it can be avoided by freeing directmemory.





Index: src/desktop/com/jme3/asset/AllCachingAssetCache.java

===================================================================

— src/desktop/com/jme3/asset/AllCachingAssetCache.java (revision 0)

+++ src/desktop/com/jme3/asset/AllCachingAssetCache.java (revision 0)

@@ -0,0 +1,40 @@

+package com.jme3.asset;

+

+import java.util.HashMap;

+

+/**

    • A dumb assetcache, it is not able to cleanup itself.
    • @author Empire-Phoenix
  • *
  • */

    +public class AllCachingAssetCache extends AssetCache {
  • HashMap<AssetKey,Object> cache = new HashMap<AssetKey,Object>();

    +
  • @Override
  • public void addToCache(AssetKey key, Object obj) {
  •   System.out.println(&quot;Caching &quot; + key);<br />
    
  •   synchronized(cache){<br />
    
  •   	cache.put(key, obj);<br />
    
  •   }<br />
    
  • }

    +
  • @Override
  • public boolean deleteFromCache(AssetKey key) {
  •   synchronized(cache){<br />
    
  •   	return cache.remove(key)!= null;<br />
    
  •   }<br />
    
  • }

    +
  • @Override
  • public Object getFromCache(AssetKey key) {
  •   synchronized(cache){<br />
    
  •   	return cache.get(key);<br />
    
  •   }<br />
    
  • }

    +
  • @Override
  • public void clearCache() {
  •   cache.clear();<br />
    
  • }

    +

    +}

    Index: src/desktop/com/jme3/asset/AssetCache.java

    ===================================================================

    — src/desktop/com/jme3/asset/AssetCache.java (revision 7837)

    +++ src/desktop/com/jme3/asset/AssetCache.java (working copy)

    @@ -37,94 +37,50 @@

    import java.util.WeakHashMap;



    /**
    • An <code>AssetCache</code> allows storage of loaded resources in order
    • to improve their access time if they are requested again in a short period
    • of time. The AssetCache stores weak references to the resources, allowing
    • Java’s garbage collector to request deletion of rarely used resources
    • when heap memory is low.

      +* This Class is a abstract to define own AssetCaches.

      +* The default implementation is the AllCachingAssetCache

      +* The implementations have to make sure they are Threadsave to not break this contract!

      */

      -public class AssetCache {

      +public abstract class AssetCache {
  • private static AssetCache currentimplementation;


  • public static final class SmartAssetInfo {
  •    public WeakReference&lt;AssetKey&gt; smartKey;<br />
    
  •    public Asset asset;<br />
    
  • public static AssetCache getImplementation(){
  •   return currentimplementation;<br />
    

}

-

  • private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache
  •        = new WeakHashMap&lt;AssetKey, SmartAssetInfo&gt;();<br />
    
  • private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();

    -
  • /**

    +
  • public static void setImplementation(AssetCache cache){
  •   if(currentimplementation != null){<br />
    
  •   	throw new RuntimeException(&quot;Only one AssetCache can be set, it must be prior to starting the application&quot;);<br />
    
  •   }<br />
    
  •   currentimplementation = cache;<br />
    
  • }

    +
  • /**
  • Adds a resource to the cache.
  • <br/><br/>
  • <font color="red">Thread-safe.</font>
  • @see #getFromCache(java.lang.String)

    */
  • public void addToCache(AssetKey key, Object obj){
  •    synchronized (regularCache){<br />
    
  •        if (obj instanceof Asset &amp;&amp; key.useSmartCache()){<br />
    
  •            // put in smart cache<br />
    
  •            Asset asset = (Asset) obj;<br />
    
  •            asset.setKey(null); // no circular references<br />
    
  •            SmartAssetInfo smartInfo = new SmartAssetInfo();<br />
    
  •            smartInfo.asset = asset;<br />
    
  •            // use the original key as smart key<br />
    
  •            smartInfo.smartKey = new WeakReference&lt;AssetKey&gt;(key);<br />
    
  •            smartCache.put(key, smartInfo);<br />
    
  •        }else{<br />
    
  •            // put in regular cache<br />
    
  •            regularCache.put(key, obj);<br />
    
  •        }<br />
    
  •    }<br />
    
  • }
  • public abstract void addToCache(AssetKey key, Object obj);



    /**
  • * Delete an asset from the cache, returns true if it was deleted successfuly.<br />
    
  • * Delete an asset from the cache, returns true if it was deleted successfuly or the implementation cannot determine if it was deleted<br />
    
  • <br/><br/>
  • <font color="red">Thread-safe.</font>

    */
  • public boolean deleteFromCache(AssetKey key){
  •    if (key.useSmartCache()){<br />
    
  •        throw new UnsupportedOperationException(&quot;You cannot delete from the smart cache&quot;);<br />
    
  •    }<br />
    

-

  •    synchronized (regularCache){<br />
    
  •        return regularCache.remove(key) != null;<br />
    
  •    }<br />
    
  • }
  • public abstract boolean deleteFromCache(AssetKey key);



    /**
  • * Gets an object from the cache given an asset key.<br />
    
  • * Gets an object from the cache given an asset key, the implementation is free to return null if it was unloaded<br />
    
  • <br/><br/>
  • <font color="red">Thread-safe.</font>
  • @param key
  • @return

    */
  • public Object getFromCache(AssetKey key){
  •    synchronized (regularCache){<br />
    
  •        if (key.useSmartCache()) {<br />
    
  •            return smartCache.get(key).asset;<br />
    
  •        } else {<br />
    
  •            return regularCache.get(key);<br />
    
  •        }<br />
    
  •    }<br />
    
  • }
  • public abstract Object getFromCache(AssetKey key);



    /**
  • * Retrieves smart asset info from the cache.<br />
    
  • * @param key<br />
    
  • * @return<br />
    
  • * Deletes all the assets in the cache<br />
    

*/

  • public SmartAssetInfo getFromSmartCache(AssetKey key){
  •    return smartCache.get(key);<br />
    
  • }

    -
  • /**
  • * Deletes all the assets in the regular cache.<br />
    
  • */<br />
    
  • public void deleteAllAssets(){
  •    synchronized (regularCache){<br />
    
  •        regularCache.clear();<br />
    
  •    }<br />
    
  • }
  • public abstract void clearCache();

    }

    Index: src/desktop/com/jme3/asset/DesktopAssetManager.java

    ===================================================================

    — src/desktop/com/jme3/asset/DesktopAssetManager.java (revision 7837)

    +++ src/desktop/com/jme3/asset/DesktopAssetManager.java (working copy)

    @@ -32,7 +32,6 @@



    package com.jme3.asset;



    -import com.jme3.asset.AssetCache.SmartAssetInfo;

    import com.jme3.audio.AudioKey;

    import com.jme3.audio.AudioData;

    import com.jme3.font.BitmapFont;

    @@ -58,7 +57,6 @@



    private static final Logger logger = Logger.getLogger(AssetManager.class.getName());


  • private final AssetCache cache = new AssetCache();

    private final ImplHandler handler = new ImplHandler(this);



    private AssetEventListener eventListener = null;

    @@ -152,7 +150,7 @@

    }



    public void clearCache(){
  •    cache.deleteAllAssets();<br />
    
  •    AssetCache.getImplementation().clearCache();<br />
    

}



/**

@@ -162,7 +160,7 @@

  • <font color="red">Thread-safe.</font>

    */

    public boolean deleteFromCache(AssetKey key){
  •    return cache.deleteFromCache(key);<br />
    
  •    return  AssetCache.getImplementation().deleteFromCache(key);<br />
    

}



/**

@@ -171,7 +169,7 @@

  • <font color="red">Thread-safe.</font>

    */

    public void addToCache(AssetKey key, Object asset){
  •    cache.addToCache(key, asset);<br />
    
  •    AssetCache.getImplementation().addToCache(key, asset);<br />
    

}



public AssetInfo locateAsset(AssetKey<?> key){

@@ -202,21 +200,7 @@

if (eventListener != null)

eventListener.assetRequested(key);


  •    AssetKey smartKey = null;<br />
    
  •    Object o = null;<br />
    
  •    if (key.shouldCache()){<br />
    
  •        if (key.useSmartCache()){<br />
    
  •            SmartAssetInfo smartInfo = cache.getFromSmartCache(key);<br />
    
  •            if (smartInfo != null){<br />
    
  •                smartKey = smartInfo.smartKey.get();<br />
    
  •                if (smartKey != null){<br />
    
  •                    o = smartInfo.asset;<br />
    
  •                }<br />
    
  •            }<br />
    
  •        }else{<br />
    
  •            o = cache.getFromCache(key);<br />
    
  •        }<br />
    
  •    }<br />
    
  •    Object o = AssetCache.getImplementation().getFromCache(key);<br />
    

if (o == null){

AssetLoader loader = handler.aquireLoader(key);

if (loader == null){

@@ -252,8 +236,7 @@

// do processing on asset before caching

o = key.postProcess(o);


  •            if (key.shouldCache())<br />
    
  •                cache.addToCache(key, o);<br />
    
  •           addToCache(key, o);<br />
    

if (eventListener != null)
eventListener.assetLoaded(key);
@@ -263,17 +246,7 @@
// object o is the asset
// create an instance for user
T clone = (T) key.createClonedInstance(o);
-
- if (key.useSmartCache()){
- if (smartKey != null){
- // smart asset was already cached, use original key
- ((Asset)clone).setKey(smartKey);
- }else{
- // smart asset was cached on this call, use our key
- ((Asset)clone).setKey(key);
- }
- }
-
+
return clone;
}

@@ -350,9 +323,8 @@
* @return
*/
public Shader loadShader(ShaderKey key){
- // cache abuse in method
// that doesn't use loaders/locators
- Shader s = (Shader) cache.getFromCache(key);
+ Shader s = (Shader) AssetCache.getImplementation().getFromCache(key);
if (s == null){
String vertName = key.getVertName();
String fragName = key.getFragName();
@@ -364,7 +336,7 @@
s.addSource(Shader.ShaderType.Vertex, vertName, vertSource, key.getDefines().getCompiled());
s.addSource(Shader.ShaderType.Fragment, fragName, fragSource, key.getDefines().getCompiled());

- cache.addToCache(key, s);
+ addToCache(key, s);
}
return s;
}
Index: src/networking/com/jme3/network/connection/TCPConnection.java
===================================================================
--- src/networking/com/jme3/network/connection/TCPConnection.java (revision 7837)
+++ src/networking/com/jme3/network/connection/TCPConnection.java (working copy)
@@ -35,6 +35,8 @@
import com.jme3.network.message.Message;
import com.jme3.network.queue.MessageQueue;
import com.jme3.network.serializing.Serializer;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -72,9 +74,9 @@
{
label = name;

- readBuffer = ByteBuffer.allocateDirect(16228);
- writeBuffer = ByteBuffer.allocateDirect(16228);
- tempWriteBuffer = ByteBuffer.allocateDirect(16228);
+ readBuffer = BufferUtils.createByteBuffer(16228);
+ writeBuffer = BufferUtils.createByteBuffer(16228);
+ tempWriteBuffer = BufferUtils.createByteBuffer(16228);
}

public TCPConnection() { }
Index: src/networking/com/jme3/network/connection/SSLTCPConnection.java
===================================================================
--- src/networking/com/jme3/network/connection/SSLTCPConnection.java (revision 7837)
+++ src/networking/com/jme3/network/connection/SSLTCPConnection.java (working copy)
@@ -33,6 +33,9 @@
package com.jme3.network.connection;

import javax.net.ssl.*;
+
+import com.jme3.util.BufferUtils;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
@@ -83,9 +86,9 @@

SSLSession session = sslEngine.getSession();

- incDataDecrypted = ByteBuffer.allocateDirect(session.getApplicationBufferSize());
- incDataEncrypted = ByteBuffer.allocateDirect(session.getPacketBufferSize());
- outDataEncrypted = ByteBuffer.allocateDirect(session.getPacketBufferSize());
+ incDataDecrypted = BufferUtils.createByteBuffer(session.getApplicationBufferSize());
+ incDataEncrypted = BufferUtils.createByteBuffer(session.getPacketBufferSize());
+ outDataEncrypted = BufferUtils.createByteBuffer(session.getPacketBufferSize());

incDataEncrypted.position(incDataEncrypted.limit());
outDataEncrypted.position(outDataEncrypted.limit());
Index: src/networking/com/jme3/network/connection/UDPConnection.java
===================================================================
--- src/networking/com/jme3/network/connection/UDPConnection.java (revision 7837)
+++ src/networking/com/jme3/network/connection/UDPConnection.java (working copy)
@@ -35,6 +35,8 @@
import com.jme3.network.message.DiscoverHostMessage;
import com.jme3.network.message.Message;
import com.jme3.network.serializing.Serializer;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -61,8 +63,8 @@
public UDPConnection(String label) {
this.label = label;

- readBuffer = ByteBuffer.allocateDirect(8192);
- writeBuffer = ByteBuffer.allocateDirect(8192);
+ readBuffer = BufferUtils.createByteBuffer(8192);
+ writeBuffer = BufferUtils.createByteBuffer(8192);
}

public void connect(SocketAddress address) throws IOException {
Index: src/test/jme3test/asset/TestAssetCache.java
===================================================================
--- src/test/jme3test/asset/TestAssetCache.java (revision 7837)
+++ src/test/jme3test/asset/TestAssetCache.java (working copy)
@@ -34,6 +34,7 @@

import com.jme3.asset.AssetCache;
import com.jme3.asset.AssetKey;
+import com.jme3.asset.AllCachingAssetCache;

public class TestAssetCache {

@@ -57,7 +58,7 @@
// }
//
public static void main(String[] args){
- AssetCache cache = new AssetCache();
+ AssetCache cache = new AllCachingAssetCache();

System.gc();
System.gc();
Index: src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java
===================================================================
--- src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java (revision 7837)
+++ src/lwjgl-oal/com/jme3/audio/lwjgl/LwjglAudioRenderer.java (working copy)
@@ -77,7 +77,7 @@
private final static int MAX_NUM_CHANNELS = 64;
private IntBuffer ib = BufferUtils.createIntBuffer(1);
private final FloatBuffer fb = BufferUtils.createVector3Buffer(2);
- private final ByteBuffer nativeBuf = ByteBuffer.allocateDirect(BUFFER_SIZE);
+ private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE);
private final byte[] arrayBuf = new byte[BUFFER_SIZE];

private int[] channels;
Index: src/terrain/com/jme3/terrain/GeoMap.java
===================================================================
--- src/terrain/com/jme3/terrain/GeoMap.java (revision 7837)
+++ src/terrain/com/jme3/terrain/GeoMap.java (working copy)
@@ -68,7 +68,7 @@
}

public GeoMap(int width, int height, int maxval) {
- this(ByteBuffer.allocateDirect(width*height*4).asFloatBuffer(),null,width,height,maxval);
+ this(BufferUtils.createByteBuffer(width*height*4).asFloatBuffer(),null,width,height,maxval);
}

public FloatBuffer getHeightData(){
@@ -187,7 +187,7 @@
* Copies a section of this geomap as a new geomap
*/
public GeoMap copySubGeomap(int x, int y, int w, int h){
- FloatBuffer nhdata = ByteBuffer.allocateDirect(w*h*4).asFloatBuffer();
+ FloatBuffer nhdata = BufferUtils.createByteBuffer(w*h*4).asFloatBuffer();
hdata.position(y*width+x);
for (int cy = 0; cy < height; cy++){
hdata.limit(hdata.position()+w);
@@ -199,7 +199,7 @@

ByteBuffer nndata = null;
if (ndata!=null){
- nndata = ByteBuffer.allocateDirect(w*h*3);
+ nndata = BufferUtils.createByteBuffer(w*h*3);
ndata.position( (y*width+x)*3 );
for (int cy = 0; cy < height; cy++){
ndata.limit(ndata.position()+w*3);
Index: src/bullet/com/jme3/bullet/util/NativeMeshUtil.java
===================================================================
--- src/bullet/com/jme3/bullet/util/NativeMeshUtil.java (revision 7837)
+++ src/bullet/com/jme3/bullet/util/NativeMeshUtil.java (working copy)
@@ -34,6 +34,8 @@
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
@@ -45,8 +47,8 @@
public class NativeMeshUtil {

public static long getTriangleIndexVertexArray(Mesh mesh){
- ByteBuffer triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
- ByteBuffer vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ ByteBuffer triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ ByteBuffer vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
int numVertices = mesh.getVertexCount();
int vertexStride = 12; //3 verts * 4 bytes per.
int numTriangles = mesh.getTriangleCount();
Index: src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java
===================================================================
--- src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java (revision 7837)
+++ src/bullet/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java (working copy)
@@ -11,6 +11,8 @@
import com.jme3.math.FastMath;
import com.jme3.math.Vector3f;
import com.jme3.scene.Mesh;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -94,7 +96,7 @@
}

protected void createShape() {
- bbuf = ByteBuffer.allocateDirect(heightfieldData.length * 4).order(ByteOrder.nativeOrder());
+ bbuf = BufferUtils.createByteBuffer(heightfieldData.length * 4).order(ByteOrder.nativeOrder());
// fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData);
// fbuf.rewind();
// fbuf.put(heightfieldData);
Index: src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java
===================================================================
--- src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java (revision 7837)
+++ src/bullet/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java (working copy)
@@ -39,6 +39,8 @@
import com.jme3.export.OutputCapsule;
import com.jme3.scene.VertexBuffer.Type;
import com.jme3.scene.mesh.IndexBuffer;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -70,8 +72,8 @@
}

private void createCollisionMesh(Mesh mesh) {
- triangleIndexBase = ByteBuffer.allocateDirect(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
- vertexBase = ByteBuffer.allocateDirect(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4).order(ByteOrder.nativeOrder());
+ vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4).order(ByteOrder.nativeOrder());
// triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4);
// vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4);
numVertices = mesh.getVertexCount();
Index: src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java
===================================================================
--- src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java (revision 7837)
+++ src/bullet/com/jme3/bullet/collision/shapes/HullCollisionShape.java (working copy)
@@ -7,6 +7,8 @@
import com.jme3.export.OutputCapsule;
import com.jme3.scene.Mesh;
import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
+
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -66,7 +68,7 @@
// objectId = new ConvexHullShape(pointList);
// objectId.setLocalScaling(Converter.convert(getScale()));
// objectId.setMargin(margin);
- ByteBuffer bbuf=ByteBuffer.allocateDirect(points.length * 4).order(ByteOrder.nativeOrder());
+ ByteBuffer bbuf=BufferUtils.createByteBuffer(points.length * 4).order(ByteOrder.nativeOrder());
// fbuf = bbuf.asFloatBuffer();
// fbuf.rewind();
// fbuf.put(points);
Index: src/core/com/jme3/util/BasicMemoryManager.java
===================================================================
--- src/core/com/jme3/util/BasicMemoryManager.java (revision 0)
+++ src/core/com/jme3/util/BasicMemoryManager.java (revision 0)
@@ -0,0 +1,17 @@
+package com.jme3.util;
+
+import java.nio.ByteBuffer;
+
+public class BasicMemoryManager extends MemoryManager {
+
+ @Override
+ public ByteBuffer allocateDirect(int i) {
+ try{
+ return ByteBuffer.allocateDirect(i);
+ }catch(Throwable e){
+ System.gc();
+ return ByteBuffer.allocateDirect(i);
+ }
+ }
+
+}
Index: src/core/com/jme3/util/MemoryManager.java
===================================================================
--- src/core/com/jme3/util/MemoryManager.java (revision 0)
+++ src/core/com/jme3/util/MemoryManager.java (revision 0)
@@ -0,0 +1,25 @@
+package com.jme3.util;
+
+import java.nio.ByteBuffer;
+
+import com.jme3.asset.AssetCache;
+
+public abstract class MemoryManager {
+
+ private static MemoryManager currentimplementation;
+
+ public static MemoryManager getImplementation(){
+ return currentimplementation;
+ }
+
+ public static void setImplementation(MemoryManager cache){
+ if(currentimplementation != null){
+ throw new RuntimeException("Only one AssetCache can be set, it must be prior to starting the application");
+ }
+ currentimplementation = cache;
+ }
+
+
+ public abstract ByteBuffer allocateDirect(int i);
+
+}
Index: src/core/com/jme3/util/BufferUtils.java
===================================================================
--- src/core/com/jme3/util/BufferUtils.java (revision 7837)
+++ src/core/com/jme3/util/BufferUtils.java (working copy)
@@ -700,7 +700,7 @@
* @return the new DoubleBuffer
*/
public static DoubleBuffer createDoubleBuffer(int size) {
- DoubleBuffer buf = ByteBuffer.allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();
+ DoubleBuffer buf = MemoryManager.getImplementation().allocateDirect(8 * size).order(ByteOrder.nativeOrder()).asDoubleBuffer();
buf.clear();
onBufferAllocated(buf);
return buf;
@@ -763,7 +763,7 @@
* @return the new FloatBuffer
*/
public static FloatBuffer createFloatBuffer(int size) {
- FloatBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ FloatBuffer buf = MemoryManager.getImplementation().allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asFloatBuffer();
buf.clear();
onBufferAllocated(buf);
return buf;
@@ -825,7 +825,7 @@
* @return the new IntBuffer
*/
public static IntBuffer createIntBuffer(int size) {
- IntBuffer buf = ByteBuffer.allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();
+ IntBuffer buf = MemoryManager.getImplementation().allocateDirect(4 * size).order(ByteOrder.nativeOrder()).asIntBuffer();
buf.clear();
onBufferAllocated(buf);
return buf;
@@ -888,7 +888,7 @@
* @return the new IntBuffer
*/
public static ByteBuffer createByteBuffer(int size) {
- ByteBuffer buf = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
+ ByteBuffer buf = MemoryManager.getImplementation().allocateDirect(size).order(ByteOrder.nativeOrder());
buf.clear();
onBufferAllocated(buf);
return buf;
@@ -966,7 +966,7 @@
* @return the new ShortBuffer
*/
public static ShortBuffer createShortBuffer(int size) {
- ShortBuffer buf = ByteBuffer.allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();
+ ShortBuffer buf = MemoryManager.getImplementation().allocateDirect(2 * size).order(ByteOrder.nativeOrder()).asShortBuffer();
buf.clear();
onBufferAllocated(buf);
return buf;
Index: src/core/com/jme3/app/SimpleApplication.java
===================================================================
--- src/core/com/jme3/app/SimpleApplication.java (revision 7837)
+++ src/core/com/jme3/app/SimpleApplication.java (working copy)
@@ -31,6 +31,8 @@
*/
package com.jme3.app;

+import com.jme3.asset.AssetCache;
+import com.jme3.asset.AllCachingAssetCache;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.input.FlyByCamera;
@@ -46,7 +48,9 @@
import com.jme3.system.AppSettings;
import com.jme3.system.JmeContext.Type;
import com.jme3.system.JmeSystem;
+import com.jme3.util.BasicMemoryManager;
import com.jme3.util.BufferUtils;
+import com.jme3.util.MemoryManager;

/**
* <code>SimpleApplication</code> extends the {@link com.jme3.app.Application}
@@ -111,6 +115,9 @@

public SimpleApplication() {
super();
+ AssetCache.setImplementation(new AllCachingAssetCache());
+ MemoryManager.setImplementation(new BasicMemoryManager());
+
}

@Override
@@ -200,8 +207,7 @@

@Override
public void initialize() {
- super.initialize();
-
+ super.initialize();
guiNode.setQueueBucket(Bucket.Gui);
guiNode.setCullHint(CullHint.Never);
loadFPSText();
@@ -226,6 +232,7 @@

}

+
// call user code
simpleInitApp();
}
Index: src/core/com/jme3/audio/AudioKey.java
===================================================================
--- src/core/com/jme3/audio/AudioKey.java (revision 7837)
+++ src/core/com/jme3/audio/AudioKey.java (working copy)
@@ -33,6 +33,7 @@
package com.jme3.audio;

import com.jme3.asset.AssetKey;
+import com.jme3.asset.AssetKey.CacheMode;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
@@ -112,10 +113,13 @@
public boolean useStreamCache(){
return streamCache;
}
-
+
@Override
- public boolean shouldCache(){
- return !stream && !streamCache;
+ public CacheMode cachePriority(){
+ if(!stream && !streamCache){
+ return CacheMode.SmartCache;
+ }
+ return CacheMode.KeepNever;
}

@Override
Index: src/core/com/jme3/shader/plugins/GLSLLoader.java
===================================================================
--- src/core/com/jme3/shader/plugins/GLSLLoader.java (revision 7837)
+++ src/core/com/jme3/shader/plugins/GLSLLoader.java (working copy)
@@ -36,6 +36,8 @@
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.AssetManager;
+import com.jme3.asset.AssetKey.CacheMode;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -88,10 +90,12 @@
public GlslDependKey(String name){
super(name);
}
+
@Override
- public boolean shouldCache(){
- return false;
+ public CacheMode cachePriority(){
+ return CacheMode.KeepAlways;
}
+
}

private DependencyNode loadNode(InputStream in, String nodeName) throws IOException{
Index: src/core/com/jme3/asset/TextureKey.java
===================================================================
--- src/core/com/jme3/asset/TextureKey.java (revision 7837)
+++ src/core/com/jme3/asset/TextureKey.java (working copy)
@@ -32,6 +32,7 @@

package com.jme3.asset;

+import com.jme3.asset.AssetKey.CacheMode;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.InputCapsule;
@@ -68,13 +69,10 @@
return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
}

- /**
- * Enable smart caching for textures
- * @return true to enable smart cache
- */
+
@Override
- public boolean useSmartCache(){
- return true;
+ public CacheMode cachePriority(){
+ return CacheMode.SmartCache;
}

@Override
Index: src/core/com/jme3/asset/AssetKey.java
===================================================================
--- src/core/com/jme3/asset/AssetKey.java (revision 7837)
+++ src/core/com/jme3/asset/AssetKey.java (working copy)
@@ -45,7 +45,10 @@
* This class should be immutable.
*/
public class AssetKey<T> implements Savable {
-
+ /**
+ * This is to determine how the selected AssetCache behaves for this type of ressource, note that it is up to the implementation to decide this it merly a hint
+ */
+ public enum CacheMode{KeepAlways,SmartCache,KeepNever};
protected String name;
protected transient String folder;
protected transient String extension;
@@ -125,17 +128,8 @@
* @return True if the asset for this key should be cached. Subclasses
* should override this method if they want to override caching behavior.
*/
- public boolean shouldCache(){
- return true;
- }
-
- /**
- * @return Should return true, if the asset objects implement the "Asset"
- * interface and want to be removed from the cache when no longer
- * referenced in user-code.
- */
- public boolean useSmartCache(){
- return false;
+ public CacheMode cachePriority(){
+ return CacheMode.SmartCache;
}

@Override
Index: src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java
===================================================================
--- src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java (revision 7837)
+++ src/jbullet/com/jme3/bullet/util/DebugShapeFactory.java (working copy)
@@ -45,6 +45,7 @@
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.VertexBuffer.Type;
+import com.jme3.util.BufferUtils;
import com.jme3.util.TempVars;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -177,7 +178,7 @@
// The number of bytes needed is: (floats in a vertex) * (vertices in a triangle) * (# of triangles) * (size of float in bytes)
final int numberOfFloats = 3 * 3 * numberOfTriangles;
final int byteBufferSize = numberOfFloats * Float.SIZE;
- FloatBuffer vertices = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ FloatBuffer vertices = BufferUtils.createByteBuffer(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();

// Force the limit, set the cap - most number of floats we will use the buffer for
vertices.limit(numberOfFloats);
@@ -236,7 +237,7 @@
// There are 3 floats needed for each vertex (x,y,z)
final int numberOfFloats = vertices.size() * 3;
final int byteBufferSize = numberOfFloats * Float.SIZE;
- FloatBuffer verticesBuffer = ByteBuffer.allocateDirect(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();
+ FloatBuffer verticesBuffer = BufferUtils.createByteBuffer(byteBufferSize).order(ByteOrder.nativeOrder()).asFloatBuffer();

// Force the limit, set the cap - most number of floats we will use the buffer for
verticesBuffer.limit(numberOfFloats);

EmpirePhoenix said:
Note this patch contains all of the above adjustments, and at least under asun jvm it will not crash with out of memory if it can be avoided by freeing directmemory.


For the record, it is a sun VM that I use that hard-crashes when attempting to GC in a loop when out of memory errors occur. But only sometimes. Heap dump to disk and everything.

It's still better to try but I'm just saying.

Well I guess tat in that case you either really useing more directMemroy than the limit is, or that you have a memory leak somewhere.

So what now will we use the patch I suggested (or a similar interface) or not? I want a decision as these problem is far to important to just ignore it.!!

No we wont use the patch and yes we will fix this problem as it was planned all the time.

http://code.google.com/p/jmonkeyengine/issues/detail?id=273

A few things:

  1. We will enable smart caching for models and materials
  2. We may implement a smart cache that is based on time of use rather than weak references, which will fix the direct memory issue
  3. Or you can do what paul did which is set heap size to low and direct memory size to high

Well ok, I would still like however to have a way to use a own implementation, so if it is not to complex it would be nice to have a interface for that.

Well, one thing that’s nice is that if AssetManager is keeping weak references properly… then you can easily implement your own cache. If asset manager is doing something smarter, it’s potentially stupid for some class of app… since smart for one app may not be smart for another. Having asset manager do smarter caching is convenient but is is possible to do it outside of it as well until something like that is in place.



I’ve been pushing to fix the weak reference problems first and then put a cache in once those issues are sorted. Since as I understand it right now the heap grows and grows and grows… which is bad. Better to get that under control first, in my opinion.

Well the heap never was a problem for me actually,a s it crashes with out of directmemory way sooner.



Still I think best would be to have some kind of interface for a AssetCache, and allow each developer to set the one they think fit best. The behaviour described above could be used for a default one when no custom is set, as it will probably work good enough for most application types.

First, I agree with you about a pluggable component.



But, there are two separate issues to me.


  1. make sure that assets are only loaded once if they are still around. If I load fifty objects with the same texture I should make sure that the texture is only loaded once. AssetManager is the only thing that can do this and weak referencing is a reliable way to make that happen.


  2. make sure assets stay around for some period based on cache policy. This can and should be implemented in parallel to the above because even if your cache says it’s ok for an asset to go, if it’s still strongly referenced then it can still be reused when loading new assets. A naive implementation potentially ignores this and accidentally reloads assets because the expiration routine is too aggressive.



    If (1) is working then (2) is easier. It can be informed by the weak references but otherwise is a separate component that can be more easily plugged in and out and use whatever expiration policy it chooses without destroying asset sharing.

Actualy 1) should work already, as the assetManager first tries to get the requestet AssetKey from the AssetCache, before loading it.

And today’s asset cache is based on weak references (when it works that way).



If you based another one on a straight LRU cache, you could be needless reloading assets that are still strongly referenced somewhere. My point is leave the existing weak referenced cache because it is the weakest cache possible and makes sure things are 100% shared if loaded. A smart cache operates next to this one to keep things longer based on some implemented expiration policy.

Yeah basically my #2 point is based on a hybrid LRU + garbage collector based cache. Essentially when there are no strong references to an asset from user code, it will be removed from memory after X seconds or some other criteria.

As long as we have ainterface I don’t really care how the default implementation is, (But I would kinda dislike being forced to LRU as this would give hugh problems for me when changing levels)