diff --git a/engine/src/core/com/jme3/asset/AssetKey.java b/engine/src/core/com/jme3/asset/AssetKey.java index ff767b2a6..8d3dc5c18 100644 --- a/engine/src/core/com/jme3/asset/AssetKey.java +++ b/engine/src/core/com/jme3/asset/AssetKey.java @@ -43,7 +43,7 @@ import java.util.LinkedList; * look up a resource from a cache. * This class should be immutable. */ -public class AssetKey implements Savable { +public class AssetKey implements Savable, Cloneable { protected String name; protected transient String folder; @@ -57,6 +57,15 @@ public class AssetKey implements Savable { public AssetKey(){ } + @Override + public AssetKey clone() { + try { + return (AssetKey) super.clone(); + } catch (CloneNotSupportedException ex) { + throw new AssertionError(); + } + } + protected static String getExtension(String name) { int idx = name.lastIndexOf('.'); //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml) diff --git a/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java b/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java index 673ae73c7..63982d2ad 100644 --- a/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java +++ b/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java @@ -2,9 +2,13 @@ package com.jme3.asset.cache; import com.jme3.asset.AssetKey; import com.jme3.asset.CloneableSmartAsset; +import java.lang.ref.PhantomReference; +import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; -import java.util.ArrayDeque; -import java.util.WeakHashMap; +import java.util.ArrayList; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; /** * caches cloneable assets in a weak-key @@ -17,58 +21,110 @@ import java.util.WeakHashMap; */ public class WeakRefCloneAssetCache implements AssetCache { - private static final class SmartCachedAsset { + private static final Logger logger = Logger.getLogger(WeakRefAssetCache.class.getName()); + + private final ReferenceQueue refQueue = new ReferenceQueue(); + + /** + * Maps cloned key to AssetRef which has a weak ref to the original + * key and a strong ref to the original asset. + */ + private final ConcurrentHashMap smartCache + = new ConcurrentHashMap(); + + /** + * Stored in the ReferenceQueue to find out when originalKey is collected + * by GC. Once collected, the clonedKey is used to remove the asset + * from the cache. + */ + private static final class KeyRef extends PhantomReference { + + AssetKey clonedKey; + + public KeyRef(AssetKey originalKey, ReferenceQueue refQueue) { + super(originalKey, refQueue); + clonedKey = originalKey.clone(); + } + } + + /** + * Stores the original key and original asset. + * The asset info contains a cloneable asset (e.g. the original, from + * which all clones are made). Also a weak reference to the + * original key which is used when the clones are produced. + */ + private static final class AssetRef extends WeakReference { - WeakReference key; CloneableSmartAsset asset; - public SmartCachedAsset(CloneableSmartAsset originalAsset, AssetKey originalKey) { - this.key = new WeakReference(originalKey); + public AssetRef(CloneableSmartAsset originalAsset, AssetKey originalKey) { + super(originalKey); this.asset = originalAsset; } } - private final WeakHashMap smartCache - = new WeakHashMap(); - - private final ThreadLocal> assetLoadStack - = new ThreadLocal>() { + private final ThreadLocal> assetLoadStack + = new ThreadLocal>() { @Override - protected ArrayDeque initialValue() { - return new ArrayDeque(); + protected ArrayList initialValue() { + return new ArrayList(); } }; - public void addToCache(AssetKey key, T obj) { + private void removeCollectedAssets(){ + int removedAssets = 0; + for (KeyRef ref; (ref = (KeyRef)refQueue.poll()) != null;){ + // (Cannot use ref.get() since it was just collected by GC!) + AssetKey key = ref.clonedKey; + + // Asset was collected, note that at this point the asset cache + // might not even have this asset anymore, it is OK. + if (smartCache.remove(key) != null){ + removedAssets ++; + //System.out.println("WeakRefAssetCache: The asset " + ref.assetKey + " was purged from the cache"); + } + } + if (removedAssets >= 1) { + logger.log(Level.INFO, "WeakRefAssetCache: {0} assets were purged from the cache.", removedAssets); + } + } + + public void addToCache(AssetKey originalKey, T obj) { + // Make room for new asset + removeCollectedAssets(); + CloneableSmartAsset asset = (CloneableSmartAsset) obj; // No circular references, since the original asset is // strongly referenced, we don't want the key strongly referenced. asset.setKey(null); - // Place the asset in the cache with a weak ref to the key. - synchronized (smartCache) { - smartCache.put(key, new SmartCachedAsset(asset, key)); - } + // Start tracking the collection of originalKey + // (this adds the KeyRef to the ReferenceQueue) + KeyRef ref = new KeyRef(originalKey, refQueue); + + // Place the asset in the cache, but use a clone of + // the original key. + smartCache.put(ref.clonedKey, new AssetRef(asset, originalKey)); // Push the original key used to load the asset // so that it can be set on the clone later - ArrayDeque loadStack = assetLoadStack.get(); - loadStack.push(key); + ArrayList loadStack = assetLoadStack.get(); + loadStack.add(originalKey); } public void registerAssetClone(AssetKey key, T clone) { - ArrayDeque loadStack = assetLoadStack.get(); - ((CloneableSmartAsset)clone).setKey(loadStack.pop()); + ArrayList loadStack = assetLoadStack.get(); + ((CloneableSmartAsset)clone).setKey(loadStack.remove(loadStack.size() - 1)); } public void notifyNoAssetClone() { - ArrayDeque loadStack = assetLoadStack.get(); - loadStack.pop(); + ArrayList loadStack = assetLoadStack.get(); + loadStack.remove(loadStack.size() - 1); } public T getFromCache(AssetKey key) { - SmartCachedAsset smartInfo; + AssetRef smartInfo; synchronized (smartCache){ smartInfo = smartCache.get(key); } @@ -79,7 +135,7 @@ public class WeakRefCloneAssetCache implements AssetCache { // NOTE: Optimization so that registerAssetClone() // can check this and determine that the asset clone // belongs to the asset retrieved here. - AssetKey keyForTheClone = smartInfo.key.get(); + AssetKey keyForTheClone = smartInfo.get(); if (keyForTheClone == null){ // The asset was JUST collected by GC // (between here and smartCache.get) @@ -88,34 +144,32 @@ public class WeakRefCloneAssetCache implements AssetCache { // Prevent original key from getting collected // while an asset is loaded for it. - ArrayDeque loadStack = assetLoadStack.get(); - loadStack.push(keyForTheClone); + ArrayList loadStack = assetLoadStack.get(); + loadStack.add(keyForTheClone); return (T) smartInfo.asset; } } public boolean deleteFromCache(AssetKey key) { - ArrayDeque loadStack = assetLoadStack.get(); + ArrayList loadStack = assetLoadStack.get(); if (!loadStack.isEmpty()){ throw new UnsupportedOperationException("Cache cannot be modified" + "while assets are being loaded"); } - synchronized (smartCache) { - return smartCache.remove(key) != null; - } + + return smartCache.remove(key) != null; } public void clearCache() { - ArrayDeque loadStack = assetLoadStack.get(); + ArrayList loadStack = assetLoadStack.get(); if (!loadStack.isEmpty()){ throw new UnsupportedOperationException("Cache cannot be modified" + "while assets are being loaded"); } - synchronized (smartCache) { - smartCache.clear(); - } + + smartCache.clear(); } }