Просмотр исходного кода

* Added AssetKey.clone()
* WeakRefCloneAssetCache will attempt to collect GCd assets more often by not relying WeakHashMap to do so

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9492 75d07b2b-3a1a-0410-a2c5-0572b91ccdca

Sha..rd 13 лет назад
Родитель
Сommit
fd03d28d81

+ 10 - 1
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<T> implements Savable {
+public class AssetKey<T> implements Savable, Cloneable {
 
     protected String name;
     protected transient String folder;
@@ -57,6 +57,15 @@ public class AssetKey<T> implements Savable {
     public AssetKey(){
     }
 
+    @Override
+    public AssetKey<T> clone() {
+        try {
+            return (AssetKey<T>) 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)

+ 90 - 36
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;
 
 /**
  * <codeWeakRefCloneAssetCache</code> 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<AssetKey> refQueue = new ReferenceQueue<AssetKey>();
+    
+    /**
+     * 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<AssetKey, AssetRef> smartCache 
+            = new ConcurrentHashMap<AssetKey, AssetRef>();
+    
+    /**
+     * 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> {
+        
+        AssetKey clonedKey;
+        
+        public KeyRef(AssetKey originalKey, ReferenceQueue<AssetKey> 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<AssetKey> {
 
-        WeakReference<AssetKey> key;
         CloneableSmartAsset asset;
 
-        public SmartCachedAsset(CloneableSmartAsset originalAsset, AssetKey originalKey) {
-            this.key = new WeakReference<AssetKey>(originalKey);
+        public AssetRef(CloneableSmartAsset originalAsset, AssetKey originalKey) {
+            super(originalKey);
             this.asset = originalAsset;
         }
     }
 
-    private final WeakHashMap<AssetKey, SmartCachedAsset> smartCache
-            = new WeakHashMap<AssetKey, SmartCachedAsset>();
-    
-    private final ThreadLocal<ArrayDeque<AssetKey>> assetLoadStack 
-            = new ThreadLocal<ArrayDeque<AssetKey>>() {
+    private final ThreadLocal<ArrayList<AssetKey>> assetLoadStack 
+            = new ThreadLocal<ArrayList<AssetKey>>() {
         @Override
-        protected ArrayDeque<AssetKey> initialValue() {
-            return new ArrayDeque<AssetKey>();
+        protected ArrayList<AssetKey> initialValue() {
+            return new ArrayList<AssetKey>();
         }
     };
     
-    public <T> void addToCache(AssetKey<T> 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 <T> void addToCache(AssetKey<T> 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<AssetKey> loadStack = assetLoadStack.get();
-        loadStack.push(key);
+        ArrayList<AssetKey> loadStack = assetLoadStack.get();
+        loadStack.add(originalKey);
     }
 
     public <T> void registerAssetClone(AssetKey<T> key, T clone) {
-        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
-        ((CloneableSmartAsset)clone).setKey(loadStack.pop());
+        ArrayList<AssetKey> loadStack = assetLoadStack.get();
+        ((CloneableSmartAsset)clone).setKey(loadStack.remove(loadStack.size() - 1));
     }
     
     public void notifyNoAssetClone() {
-        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
-        loadStack.pop();
+        ArrayList<AssetKey> loadStack = assetLoadStack.get();
+        loadStack.remove(loadStack.size() - 1);
     }
 
     public <T> T getFromCache(AssetKey<T> 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<AssetKey> loadStack = assetLoadStack.get();
-            loadStack.push(keyForTheClone);
+            ArrayList<AssetKey> loadStack = assetLoadStack.get();
+            loadStack.add(keyForTheClone);
             
             return (T) smartInfo.asset;
         }
     }
 
     public boolean deleteFromCache(AssetKey key) {
-        ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
+        ArrayList<AssetKey> 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<AssetKey> loadStack = assetLoadStack.get();
+        ArrayList<AssetKey> loadStack = assetLoadStack.get();
         
         if (!loadStack.isEmpty()){
             throw new UnsupportedOperationException("Cache cannot be modified"
                                                   + "while assets are being loaded");
         }
-        synchronized (smartCache) {
-            smartCache.clear();
-        }
+        
+        smartCache.clear();
     }
 }