* AssetCache is now an interface and can be extended by user. Moved to com.jme3.asset.cache package.

* Added 3 implementations of AssetCache: SimpleAssetCache, WeakRefAssetCache and WeakRefCloneAssetCache
 * Added AssetProcessor interface that handles cloning and post processing of assets after they are loaded
 * AssetKey can now configure which cache/processor to use for a particular asset type 
 * Added AssetManager unregisterLoader method
 * AssetManager now supports more than one AssetLoadListener 
 * Javadoc improvements in AssetManager 
 * Asset interface now renamed to CloneableSmartAsset (which more accurately describes its behavior and use case)
 * DesktopAssetManager now makes proper use of the new AssetProcessor/AssetCache classes when handling asset loading 
 * Added proper equals/hashCode methods to many AssetKey subclasses, which is required for the new system to work properly
 * All AssetKeys were rewritten to work with the new asset system 
 * loadAsset(AudioKey) now returns an AudioNode and not AudioData, similar to the behavior of loadAsset(TextureKey) returning a Texture and not an Image. Because of that, the key storage in AudioData has been removed. 
 * Texture, Spatial, and Material are all cloneable smart assets now and will be cleared from the cache when all instances become unreachable
 * Improved the existing TestAssetCache test to make sure the new system works 

git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@9309 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
Sha..rd 13 years ago
parent 0c37fc46e9
commit bd4214f3bd
  1. 10
      engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java
  2. 131
      engine/src/core/com/jme3/asset/AssetCache.java
  3. 69
      engine/src/core/com/jme3/asset/AssetKey.java
  4. 140
      engine/src/core/com/jme3/asset/AssetManager.java
  5. 72
      engine/src/core/com/jme3/asset/AssetProcessor.java
  6. 52
      engine/src/core/com/jme3/asset/CloneableAssetProcessor.java
  7. 24
      engine/src/core/com/jme3/asset/CloneableSmartAsset.java
  8. 253
      engine/src/core/com/jme3/asset/DesktopAssetManager.java
  9. 205
      engine/src/core/com/jme3/asset/ImplHandler.java
  10. 58
      engine/src/core/com/jme3/asset/MaterialKey.java
  11. 23
      engine/src/core/com/jme3/asset/ModelKey.java
  12. 127
      engine/src/core/com/jme3/asset/TextureKey.java
  13. 75
      engine/src/core/com/jme3/asset/cache/AssetCache.java
  14. 41
      engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java
  15. 88
      engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java
  16. 116
      engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java
  17. 3
      engine/src/core/com/jme3/audio/AudioData.java
  18. 50
      engine/src/core/com/jme3/audio/AudioKey.java
  19. 19
      engine/src/core/com/jme3/audio/AudioProcessor.java
  20. 4
      engine/src/core/com/jme3/material/Material.java
  21. 15
      engine/src/core/com/jme3/material/MaterialProcessor.java
  22. 4
      engine/src/core/com/jme3/scene/Spatial.java
  23. 4
      engine/src/core/com/jme3/texture/Texture.java
  24. 49
      engine/src/core/com/jme3/texture/TextureProcessor.java
  25. 183
      engine/src/test/jme3test/asset/TestAssetCache.java

@ -36,6 +36,7 @@ import com.jme3.asset.AssetInfo;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetLoader;
import com.jme3.asset.AssetManager;
import com.jme3.asset.cache.AssetCache;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@ -78,12 +79,15 @@ public class GLSLLoader implements AssetLoader {
}
private class GlslDependKey extends AssetKey<InputStream> {
public GlslDependKey(String name){
public GlslDependKey(String name) {
super(name);
}
@Override
public boolean shouldCache(){
return false;
public Class<? extends AssetCache> getCacheType() {
// Disallow caching here
return null;
}
}

@ -1,131 +0,0 @@
/*
* Copyright (c) 2009-2012 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.asset;
import java.lang.ref.WeakReference;
import java.util.HashMap;
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.
*/
public class AssetCache {
public static final class SmartAssetInfo {
public WeakReference<AssetKey> smartKey;
public Asset asset;
}
private final WeakHashMap<AssetKey, SmartAssetInfo> smartCache
= new WeakHashMap<AssetKey, SmartAssetInfo>();
private final HashMap<AssetKey, Object> regularCache = new HashMap<AssetKey, Object>();
/**
* 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){
if (obj instanceof Asset && key.useSmartCache()){
// put in smart cache
Asset asset = (Asset) obj;
asset.setKey(null); // no circular references
SmartAssetInfo smartInfo = new SmartAssetInfo();
smartInfo.asset = asset;
// use the original key as smart key
smartInfo.smartKey = new WeakReference<AssetKey>(key);
smartCache.put(key, smartInfo);
}else{
// put in regular cache
regularCache.put(key, obj);
}
}
}
/**
* Delete an asset from the cache, returns true if it was deleted successfuly.
* <br/><br/>
* <font color="red">Thread-safe.</font>
*/
public boolean deleteFromCache(AssetKey key){
synchronized (regularCache){
if (key.useSmartCache()){
return smartCache.remove(key) != null;
}else{
return regularCache.remove(key) != null;
}
}
}
/**
* Gets an object from the cache given an asset key.
* <br/><br/>
* <font color="red">Thread-safe.</font>
* @param key
* @return the object matching the {@link AssetKey}
*/
public Object getFromCache(AssetKey key){
synchronized (regularCache){
if (key.useSmartCache()) {
return smartCache.get(key).asset;
} else {
return regularCache.get(key);
}
}
}
/**
* Retrieves smart asset info from the cache.
* @param key
* @return
*/
public SmartAssetInfo getFromSmartCache(AssetKey key){
return smartCache.get(key);
}
/**
* Deletes all the assets in the regular cache.
*/
public void deleteAllAssets(){
synchronized (regularCache){
regularCache.clear();
smartCache.clear();
}
}
}

@ -32,6 +32,8 @@
package com.jme3.asset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.SimpleAssetCache;
import com.jme3.export.*;
import java.io.IOException;
import java.util.LinkedList;
@ -55,7 +57,7 @@ public class AssetKey<T> implements Savable {
public AssetKey(){
}
protected static String getExtension(String name){
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)
if (name.toLowerCase().endsWith(".xml")) {
@ -64,20 +66,25 @@ public class AssetKey<T> implements Savable {
idx = name.lastIndexOf('.');
}
}
if (idx <= 0 || idx == name.length() - 1)
if (idx <= 0 || idx == name.length() - 1) {
return "";
else
return name.substring(idx+1).toLowerCase();
} else {
return name.substring(idx + 1).toLowerCase();
}
}
protected static String getFolder(String name){
protected static String getFolder(String name) {
int idx = name.lastIndexOf('/');
if (idx <= 0 || idx == name.length() - 1)
if (idx <= 0 || idx == name.length() - 1) {
return "";
else
return name.substring(0, idx+1);
} else {
return name.substring(0, idx + 1);
}
}
/**
* @return The asset path
*/
public String getName() {
return name;
}
@ -90,6 +97,11 @@ public class AssetKey<T> implements Savable {
return extension;
}
/**
* @return The folder in which the asset is located in.
* E.g. if the {@link #getName() name} is "Models/MyModel/MyModel.j3o"
* then "Models/MyModel/" is returned.
*/
public String getFolder(){
if (folder == null)
folder = getFolder(name);
@ -98,41 +110,20 @@ public class AssetKey<T> implements Savable {
}
/**
* Do any post-processing on the resource after it has been loaded.
* @param asset
*/
public Object postProcess(Object asset){
return asset;
}
/**
* Create a new instance of the asset, based on a prototype that is stored
* in the cache. Implementations are allowed to return the given parameter
* as-is if it is considered that cloning is not necessary for that particular
* asset type.
*
* @param asset The asset to be cloned.
* @return The asset, possibly cloned.
*/
public Object createClonedInstance(Object asset){
return asset;
}
/**
* @return True if the asset for this key should be cached. Subclasses
* should override this method if they want to override caching behavior.
* @return The preferred cache class for this asset type. Specify "null"
* if caching is to be disabled. By default the
* {@link SimpleAssetCache} is returned.
*/
public boolean shouldCache(){
return true;
public Class<? extends AssetCache> getCacheType(){
return SimpleAssetCache.class;
}
/**
* @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.
* @return The preferred processor type for this asset type. Specify "null"
* if no processing is required.
*/
public boolean useSmartCache(){
return false;
public Class<? extends AssetProcessor> getProcessorType(){
return null;
}
/**

@ -32,65 +32,123 @@
package com.jme3.asset;
import com.jme3.audio.AudioData;
import com.jme3.asset.plugins.ClasspathLocator;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.audio.AudioKey;
import com.jme3.audio.AudioNode;
import com.jme3.font.BitmapFont;
import com.jme3.material.Material;
import com.jme3.scene.Spatial;
import com.jme3.scene.plugins.OBJLoader;
import com.jme3.shader.Shader;
import com.jme3.shader.ShaderKey;
import com.jme3.texture.Texture;
import com.jme3.texture.plugins.TGALoader;
import java.util.List;
/**
* <code>AssetManager</code> provides an interface for managing the data assets
* of a jME3 application.
* <p>
* The asset manager provides a means to register {@link AssetLocator}s,
* which are used to find asset data on disk, network, or other file system.
* The asset locators are invoked in order of addition to find the asset data.
* Use the {@link #registerLocator(java.lang.String, java.lang.Class) } method
* to add new {@link AssetLocator}s.
* Some examples of locators:
* <ul>
* <li>{@link FileLocator} - Used to find assets on the local file system.</li>
* <li>{@link ClasspathLocator} - Used to find assets in the Java classpath</li>
* </ul>
* <p>
* The asset data is represented by the {@link AssetInfo} class, this
* data is passed into the registered {@link AssetLoader}s in order to
* convert the data into a usable object. Use the
* {@link #registerLoader(java.lang.Class, java.lang.String[]) } method
* to add loaders.
* Some examples of loaders:
* <ul>
* <li>{@link OBJLoader} - Used to load Wavefront .OBJ model files</li>
* <li>{@link TGALoader} - Used to load Targa image files</li>
* </ul>
* <p>
* Once the asset has been loaded,
*/
public interface AssetManager {
/**
* Adds a ClassLoader that is used to load *Classes* that are needed for Assets like j3o models.
* This does *not* allow loading assets from that classpath, use registerLocator for that.
* @param loader A ClassLoader that Classes in asset files can be loaded from
* Adds a {@link ClassLoader} that is used to load {@link Class classes}
* that are needed for finding and loading Assets.
* This does <strong>not</strong> allow loading assets from that classpath,
* use registerLocator for that.
*
* @param loader A ClassLoader that Classes in asset files can be loaded from.
*/
public void addClassLoader(ClassLoader loader);
/**
* Remove a ClassLoader from the list of registered ClassLoaders
* Remove a {@link ClassLoader} from the list of registered ClassLoaders
*/
public void removeClassLoader(ClassLoader loader);
/**
* Retrieve the list of registered ClassLoaders that are used for loading Classes from
* asset files.
* Retrieve the list of registered ClassLoaders that are used for loading
* {@link Class classes} from asset files.
*/
public List<ClassLoader> getClassLoaders();
/**
* Registers a loader for the given extensions.
*
* @param loaderClassName
* @param extensions
*
* @deprecated Please use {@link #registerLoader(java.lang.Class, java.lang.String[]) }
* together with {@link Class#forName(java.lang.String) } to find a class
* and then register it.
*
* @deprecated Please use {@link #registerLoader(java.lang.Class, java.lang.String[]) }
* with {@link Class#forName(java.lang.String) } instead.
*/
@Deprecated
public void registerLoader(String loaderClassName, String ... extensions);
/**
* Registers an {@link AssetLocator} by using a class name, instead of
* a class instance. See the {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
* Registers an {@link AssetLocator} by using a class name.
* See the {@link AssetManager#registerLocator(java.lang.String, java.lang.Class) }
* method for more information.
*
* @param rootPath The root path from which to locate assets, implementation
* dependent.
* @param rootPath The root path from which to locate assets, this
* depends on the implementation of the asset locator.
* A URL based locator will expect a url folder such as "http://www.example.com/"
* while a File based locator will expect a file path (OS dependent).
* @param locatorClassName The full class name of the {@link AssetLocator}
* implementation.
*
* @deprecated Please use {@link #registerLocator(java.lang.String, java.lang.Class) }
* together with {@link Class#forName(java.lang.String) } to find a class
* and then register it.
*/
@Deprecated
public void registerLocator(String rootPath, String locatorClassName);
/**
*
* Register an {@link AssetLoader} by using a class object.
*
* @param loaderClass
* @param extensions
*/
public void registerLoader(Class<? extends AssetLoader> loaderClass, String ... extensions);
/**
* Unregister a {@link AssetLoader} from loading its assigned extensions.
* This undoes the effect of calling
* {@link #registerLoader(java.lang.Class, java.lang.String[]) }.
*
* @param loaderClass The loader class to unregister.
* @see #registerLoader(java.lang.Class, java.lang.String[])
*/
public void unregisterLoader(Class<? extends AssetLoader> loaderClass);
/**
* Registers the given locator class for locating assets with this
@ -119,16 +177,42 @@ public interface AssetManager {
* @param rootPath Should be the same as the root path specified in {@link
* #registerLocator(java.lang.String, java.lang.Class) }.
* @param locatorClass The locator class to unregister
*
* @see #registerLocator(java.lang.String, java.lang.Class)
*/
public void unregisterLocator(String rootPath, Class<? extends AssetLocator> locatorClass);
/**
* Add an {@link AssetEventListener} to receive events from this
* <code>AssetManager</code>.
* @param listener The asset event listener to add
*/
public void addAssetEventListener(AssetEventListener listener);
/**
* Remove an {@link AssetEventListener} from receiving events from this
* <code>AssetManager</code>
* @param listener The asset event listener to remove
*/
public void removeAssetEventListener(AssetEventListener listener);
/**
* Removes all asset event listeners.
*
* @see #addAssetEventListener(com.jme3.asset.AssetEventListener)
*/
public void clearAssetEventListeners();
/**
* Set an {@link AssetEventListener} to receive events from this
* <code>AssetManager</code>. There can only be one {@link AssetEventListener}
* associated with an <code>AssetManager</code>
* <code>AssetManager</code>. Any currently added listeners are
* cleared and then the given listener is added.
*
* @param listener
* @param listener The listener to set
* @deprecated Please use {@link #addAssetEventListener(com.jme3.asset.AssetEventListener) }
* to listen for asset events.
*/
@Deprecated
public void setAssetEventListener(AssetEventListener listener);
/**
@ -162,7 +246,7 @@ public interface AssetManager {
public <T> T loadAsset(AssetKey<T> key);
/**
* Load a named asset by name, calling this method
* Load an asset by name, calling this method
* is the same as calling
* <code>
* loadAsset(new AssetKey(name)).
@ -199,27 +283,27 @@ public interface AssetManager {
/**
* Load audio file, supported types are WAV or OGG.
* @param key
* @param key Asset key of the audio file to load
* @return The audio data loaded
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
*/
public AudioData loadAudio(AudioKey key);
public AudioNode loadAudio(AudioKey key);
/**
* Load audio file, supported types are WAV or OGG.
* The file is loaded without stream-mode.
* @param name
* @param name Asset name of the audio file to load
* @return The audio data loaded
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
*/
public AudioData loadAudio(String name);
public AudioNode loadAudio(String name);
/**
* Loads a named model. Models can be jME3 object files (J3O) or
* OgreXML/OBJ files.
* @param key
* Loads a 3D model with a ModelKey.
* Models can be jME3 object files (J3O) or OgreXML/OBJ files.
* @param key Asset key of the model to load
* @return The model that was loaded
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
@ -227,9 +311,9 @@ public interface AssetManager {
public Spatial loadModel(ModelKey key);
/**
* Loads a named model. Models can be jME3 object files (J3O) or
* Loads a 3D model. Models can be jME3 object files (J3O) or
* OgreXML/OBJ files.
* @param name
* @param name Asset name of the model to load
* @return The model that was loaded
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
@ -237,8 +321,8 @@ public interface AssetManager {
public Spatial loadModel(String name);
/**
* Load a material (J3M) file.
* @param name
* Load a material instance (J3M) file.
* @param name Asset name of the material to load
* @return The material that was loaded
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)
@ -256,7 +340,7 @@ public interface AssetManager {
* Load a font file. Font files are in AngelCode text format,
* and are with the extension "fnt".
*
* @param name
* @param name Asset name of the font to load
* @return The font loaded
*
* @see AssetManager#loadAsset(com.jme3.asset.AssetKey)

@ -0,0 +1,72 @@
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.asset;
import com.jme3.material.Material;
import com.jme3.shader.Shader;
/**
* <code>AssetProcessor</code> is used to apply processing to assets
* after they have been loaded. They are assigned to a particular
* asset type (which is represented by a {@link Class} and any assets
* loaded that are of that class will be processed by the assigned
* processor.
*
* @author Kirill Vainer
*/
public interface AssetProcessor {
/**
* Applies post processing to an asset.
* The method may return an object that is not the same
* instance as the parameter object, and it could be from a different class.
*
* @param obj The asset that was loaded from an {@link AssetLoader}.
* @return Either the same object with processing applied, or an instance
* of a new object.
*/
public Object postProcess(AssetKey key, Object obj);
/**
* Creates a clone of the given asset.
* If no clone is desired, then the same instance can be returned,
* otherwise, a clone should be created.
* For example, a clone of a {@link Material} should have its own set
* of unique parameters that can be changed just for that instance,
* but it may share certain other data if it sees fit (like the {@link Shader}).
*
* @param obj The asset to clone
* @return The cloned asset, or the same as the given argument if no
* clone is needed.
*/
public Object createClone(Object obj);
}

@ -0,0 +1,52 @@
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.asset;
/**
* <code>CloneableAssetProcessor</code> simply calls {@link Object#clone() }
* on assets to clone them. No processing is applied.
*
* @author Kirill Vainer
*/
public class CloneableAssetProcessor implements AssetProcessor {
public Object postProcess(AssetKey key, Object obj) {
return obj;
}
public Object createClone(Object obj) {
CloneableSmartAsset asset = (CloneableSmartAsset) obj;
return asset.clone();
}
}

@ -32,14 +32,17 @@
package com.jme3.asset;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
/**
* Implementing the asset interface allows use of smart asset management.
* Implementing the <code>CloneableSmartAsset</code> interface allows use
* of cloneable smart asset management.
* <p>
* Smart asset management requires cooperation from the {@link AssetKey}.
* In particular, the AssetKey should return true in its
* {@link AssetKey#useSmartCache() } method. Also smart assets MUST
* In particular, the AssetKey should return {@link WeakRefCloneAssetCache} in its
* {@link AssetKey#getCacheType()} method. Also smart assets MUST
* create a clone of the asset and cannot return the same reference,
* e.g. {@link AssetKey#createClonedInstance(java.lang.Object) createCloneInstance(someAsset)} <code>!= someAsset</code>.
* e.g. {@link AssetProcessor#createClone(java.lang.Object) createClone(someAsset)} <code>!= someAsset</code>.
* <p>
* If the {@link AssetManager#loadAsset(com.jme3.asset.AssetKey) } method
* is called twice with the same asset key (equals() wise, not necessarily reference wise)
@ -49,7 +52,18 @@ package com.jme3.asset;
* are garbage collected, the shared asset key becomes unreachable and at that
* point it is removed from the smart asset cache.
*/
public interface Asset {
public interface CloneableSmartAsset extends Cloneable {
/**
* Creates a clone of the asset.
*
* Please see {@link Object#clone() } for more info on how this method
* should be implemented.
*
* @return A clone of this asset.
* The cloned asset cannot reference equal this asset.
*/
public Object clone();
/**
* Set by the {@link AssetManager} to track this asset.

@ -32,9 +32,10 @@
package com.jme3.asset;
import com.jme3.asset.AssetCache.SmartAssetInfo;
import com.jme3.audio.AudioData;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.SimpleAssetCache;
import com.jme3.audio.AudioKey;
import com.jme3.audio.AudioNode;
import com.jme3.font.BitmapFont;
import com.jme3.material.Material;
import com.jme3.scene.Spatial;
@ -48,6 +49,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -60,15 +62,14 @@ import java.util.logging.Logger;
public class DesktopAssetManager implements AssetManager {
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;
private List<ClassLoader> classLoaders;
// private final ThreadingManager threadingMan = new ThreadingManager(this);
// private final Set<AssetKey> alreadyLoadingSet = new HashSet<AssetKey>();
private CopyOnWriteArrayList<AssetEventListener> eventListeners =
new CopyOnWriteArrayList<AssetEventListener>();
private List<ClassLoader> classLoaders =
Collections.synchronizedList(new ArrayList<ClassLoader>());
public DesktopAssetManager(){
this(null);
@ -81,44 +82,55 @@ public class DesktopAssetManager implements AssetManager {
public DesktopAssetManager(URL configFile){
if (configFile != null){
InputStream stream = null;
try{
AssetConfig cfg = new AssetConfig(this);
stream = configFile.openStream();
cfg.loadText(stream);
}catch (IOException ex){
logger.log(Level.SEVERE, "Failed to load asset config", ex);
}finally{
if (stream != null)
try{
stream.close();
}catch (IOException ex){
}
}
loadConfigFile(configFile);
}
logger.info("DesktopAssetManager created.");
}
public void addClassLoader(ClassLoader loader){
if(classLoaders == null)
classLoaders = Collections.synchronizedList(new ArrayList<ClassLoader>());
synchronized(classLoaders) {
classLoaders.add(loader);
private void loadConfigFile(URL configFile){
InputStream stream = null;
try{
AssetConfig cfg = new AssetConfig(this);
stream = configFile.openStream();
cfg.loadText(stream);
}catch (IOException ex){
logger.log(Level.SEVERE, "Failed to load asset config", ex);
}finally{
if (stream != null)
try{
stream.close();
}catch (IOException ex){
}
}
}
public void removeClassLoader(ClassLoader loader){
if(classLoaders != null) synchronized(classLoaders) {
classLoaders.remove(loader);
}
public void addClassLoader(ClassLoader loader) {
classLoaders.add(loader);
}
public void removeClassLoader(ClassLoader loader) {
classLoaders.remove(loader);
}
public List<ClassLoader> getClassLoaders(){
return classLoaders;
return Collections.unmodifiableList(classLoaders);
}
public void addAssetEventListener(AssetEventListener listener) {
eventListeners.add(listener);
}
public void removeAssetEventListener(AssetEventListener listener) {
eventListeners.remove(listener);
}
public void clearAssetEventListeners() {
eventListeners.clear();
}
public void setAssetEventListener(AssetEventListener listener){
eventListener = listener;
eventListeners.clear();
eventListeners.add(listener);
}
public void registerLoader(Class<? extends AssetLoader> loader, String ... extensions){
@ -142,6 +154,14 @@ public class DesktopAssetManager implements AssetManager {
registerLoader(clazz, extensions);
}
}
public void unregisterLoader(Class<? extends AssetLoader> loaderClass) {
handler.removeLoader(loaderClass);
if (logger.isLoggable(Level.FINER)){
logger.log(Level.FINER, "Unregistered loader: {0}",
loaderClass.getSimpleName());
}
}
public void registerLocator(String rootPath, Class<? extends AssetLocator> locatorClass){
handler.addLocator(locatorClass, rootPath);
@ -172,39 +192,15 @@ public class DesktopAssetManager implements AssetManager {
clazz.getSimpleName());
}
}
public void clearCache(){
cache.deleteAllAssets();
}
/**
* Delete an asset from the cache, returns true if it was deleted
* successfully.
* <br/><br/>
* <font color="red">Thread-safe.</font>
*/
public boolean deleteFromCache(AssetKey key){
return cache.deleteFromCache(key);
}
/**
* Adds a resource to the cache.
* <br/><br/>
* <font color="red">Thread-safe.</font>
*/
public void addToCache(AssetKey key, Object asset){
cache.addToCache(key, asset);
handler.clearCache();
if (logger.isLoggable(Level.FINER)){
logger.log(Level.FINER, "All asset caches cleared.");
}
}
public AssetInfo locateAsset(AssetKey<?> key){
if (handler.getLocatorCount() == 0){
logger.warning("There are no locators currently"+
" registered. Use AssetManager."+
"registerLocator() to register a"+
" locator.");
return null;
}
AssetInfo info = handler.tryLocate(key);
if (info == null){
logger.log(Level.WARNING, "Cannot locate resource: {0}", key);
@ -224,91 +220,83 @@ public class DesktopAssetManager implements AssetManager {
if (key == null)
throw new IllegalArgumentException("key cannot be null");
if (eventListener != null)
eventListener.assetRequested(key);
AssetKey smartKey = null;
Object o = null;
if (key.shouldCache()){
if (key.useSmartCache()){
SmartAssetInfo smartInfo = cache.getFromSmartCache(key);
if (smartInfo != null){
smartKey = smartInfo.smartKey.get();
if (smartKey != null){
o = smartInfo.asset;
}
}
}else{
o = cache.getFromCache(key);
}
for (AssetEventListener listener : eventListeners){
listener.assetRequested(key);
}
if (o == null){
AssetCache cache = handler.getCache(key.getCacheType());
AssetProcessor proc = handler.getProcessor(key.getProcessorType());
Object obj = cache != null ? cache.getFromCache(key) : null;
if (obj == null){
// Asset not in cache, load it from file system.
AssetLoader loader = handler.aquireLoader(key);
if (loader == null){
throw new IllegalStateException("No loader registered for type \"" +
key.getExtension() + "\"");
}
if (handler.getLocatorCount() == 0){
throw new IllegalStateException("There are no locators currently"+
" registered. Use AssetManager."+
"registerLocator() to register a"+
" locator.");
}
AssetInfo info = handler.tryLocate(key);
if (info == null){
if (handler.getParentKey() != null && eventListener != null){
if (handler.getParentKey() != null){
// Inform event listener that an asset has failed to load.
// If the parent AssetLoader chooses not to propagate
// the exception, this is the only means of finding
// that something went wrong.
eventListener.assetDependencyNotFound(handler.getParentKey(), key);
for (AssetEventListener listener : eventListeners){
listener.assetDependencyNotFound(handler.getParentKey(), key);
}
}
throw new AssetNotFoundException(key.toString());
}
try {
handler.establishParentKey(key);
o = loader.load(info);
obj = loader.load(info);
} catch (IOException ex) {
throw new AssetLoadException("An exception has occured while loading asset: " + key, ex);
} finally {
handler.releaseParentKey(key);
}
if (o == null){
throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using" + loader.getClass().getSimpleName());
if (obj == null){
throw new AssetLoadException("Error occured while loading asset \"" + key + "\" using " + loader.getClass().getSimpleName());
}else{
if (logger.isLoggable(Level.FINER)){
logger.log(Level.FINER, "Loaded {0} with {1}",
new Object[]{key, loader.getClass().getSimpleName()});
}
// do processing on asset before caching
o = key.postProcess(o);
if (key.shouldCache())
cache.addToCache(key, o);
if (eventListener != null)
eventListener.assetLoaded(key);
if (proc != null){
// do processing on asset before caching
obj = proc.postProcess(key, obj);
}
if (cache != null){
// At this point, obj should be of type T
cache.addToCache(key, (T) obj);
}
for (AssetEventListener listener : eventListeners){
listener.assetLoaded(key);
}
}
}
// object o is the asset
// object obj is the original 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);
T clone = (T) obj;
if (clone instanceof CloneableSmartAsset){
if (proc == null){
throw new IllegalStateException("Asset implements "
+ "CloneableSmartAsset but doesn't "
+ "have processor to handle cloning");
}else{
// smart asset was cached on this call, use our key
((Asset)clone).setKey(key);
clone = (T) proc.createClone(obj);
if (cache != null && clone != obj){
cache.registerAssetClone(key, clone);
} else{
throw new IllegalStateException("Asset implements "
+ "CloneableSmartAsset but doesn't have cache or "
+ "was not cloned");
}
}
}
return clone;
}
@ -329,38 +317,15 @@ public class DesktopAssetManager implements AssetManager {
return (Material) loadAsset(new MaterialKey(name));
}
/**
* Loads a texture.
*
* @param name
* @param generateMipmaps Enable if applying texture to 3D objects, disable
* for GUI/HUD elements.
* @return the loaded texture
*/
public Texture loadTexture(String name, boolean generateMipmaps){
TextureKey key = new TextureKey(name, true);
key.setGenerateMips(generateMipmaps);
key.setAsCube(false);
return loadTexture(key);
}
public Texture loadTexture(String name, boolean generateMipmaps, boolean flipY, boolean asCube, int aniso){
TextureKey key = new TextureKey(name, flipY);
key.setGenerateMips(generateMipmaps);
key.setAsCube(asCube);
key.setAnisotropy(aniso);
return loadTexture(key);
}
public Texture loadTexture(String name){
return loadTexture(name, true);
return loadTexture(new TextureKey(name, false));
}
public AudioData loadAudio(AudioKey key){
return (AudioData) loadAsset(key);
public AudioNode loadAudio(AudioKey key){
return (AudioNode) loadAsset(key);
}
public AudioData loadAudio(String name){
public AudioNode loadAudio(String name){
return loadAudio(new AudioKey(name, false));
}
@ -387,6 +352,7 @@ public class DesktopAssetManager implements AssetManager {
public Shader loadShader(ShaderKey key){
// cache abuse in method
// that doesn't use loaders/locators
AssetCache cache = handler.getCache(SimpleAssetCache.class);
Shader s = (Shader) cache.getFromCache(key);
if (s == null){
String vertName = key.getVertName();
@ -417,5 +383,4 @@ public class DesktopAssetManager implements AssetManager {
public Spatial loadModel(String name){
return loadModel(new ModelKey(name));
}
}

@ -32,9 +32,12 @@
package com.jme3.asset;
import com.jme3.asset.cache.AssetCache;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -49,40 +52,62 @@ public class ImplHandler {
private static final Logger logger = Logger.getLogger(ImplHandler.class.getName());
private final AssetManager owner;
private final AssetManager assetManager;
private final ThreadLocal<AssetKey> parentAssetKey
= new ThreadLocal<AssetKey>();
private final ArrayList<ImplThreadLocal> genericLocators =
new ArrayList<ImplThreadLocal>();
private final CopyOnWriteArrayList<ImplThreadLocal<AssetLocator>> locatorsList =
new CopyOnWriteArrayList<ImplThreadLocal<AssetLocator>>();
private final HashMap<Class<?>, ImplThreadLocal<AssetLoader>> classToLoaderMap =
new HashMap<Class<?>, ImplThreadLocal<AssetLoader>>();
private final HashMap<String, ImplThreadLocal> loaders =
new HashMap<String, ImplThreadLocal>();
private final ConcurrentHashMap<String, ImplThreadLocal<AssetLoader>> extensionToLoaderMap =
new ConcurrentHashMap<String, ImplThreadLocal<AssetLoader>>();
private final ConcurrentHashMap<Class<? extends AssetProcessor>, AssetProcessor> classToProcMap =
new ConcurrentHashMap<Class<? extends AssetProcessor>, AssetProcessor>();
private final ConcurrentHashMap<Class<? extends AssetCache>, AssetCache> classToCacheMap =
new ConcurrentHashMap<Class<? extends AssetCache>, AssetCache>();
public ImplHandler(AssetManager owner){
this.owner = owner;
public ImplHandler(AssetManager assetManager){
this.assetManager = assetManager;
}
protected class ImplThreadLocal extends ThreadLocal {
protected class ImplThreadLocal<T> extends ThreadLocal {
private final Class<?> type;
private final Class<T> type;
private final String path;
private final String[] extensions;
public ImplThreadLocal(Class<?> type){
public ImplThreadLocal(Class<T> type, String[] extensions){
this.type = type;
path = null;
this.extensions = extensions;
this.path = null;
}
public ImplThreadLocal(Class<?> type, String path){
public ImplThreadLocal(Class<T> type, String path){
this.type = type;
this.path = path;
this.extensions = null;
}
public ImplThreadLocal(Class<T> type){
this.type = type;
this.path = null;
this.extensions = null;
}
public String getPath() {
return path;
}
public String[] getExtensions(){
return extensions;
}
public Class<?> getTypeClass(){
return type;
}
@ -137,27 +162,29 @@ public class ImplHandler {
* access, or null if not found.
*/
public AssetInfo tryLocate(AssetKey key){
synchronized (genericLocators){
if (genericLocators.isEmpty())
return null;
for (ImplThreadLocal local : genericLocators){
AssetLocator locator = (AssetLocator) local.get();
if (local.getPath() != null){
locator.setRootPath((String) local.getPath());
}
AssetInfo info = locator.locate(owner, key);
if (info != null)
return info;
if (locatorsList.isEmpty()){
logger.warning("There are no locators currently"+
" registered. Use AssetManager."+
"registerLocator() to register a"+
" locator.");
return null;
}
for (ImplThreadLocal local : locatorsList){
AssetLocator locator = (AssetLocator) local.get();
if (local.getPath() != null){
locator.setRootPath((String) local.getPath());
}
AssetInfo info = locator.locate(assetManager, key);
if (info != null)
return info;
}
return null;
}
public int getLocatorCount(){
synchronized (genericLocators){
return genericLocators.size();
}
return locatorsList.size();
}
/**
@ -166,44 +193,120 @@ public class ImplHandler {
* @return AssetLoader registered with addLoader.
*/
public AssetLoader aquireLoader(AssetKey key){
synchronized (loaders){
ImplThreadLocal local = loaders.get(key.getExtension());
if (local != null){
AssetLoader loader = (AssetLoader) local.get();
return loader;
// No need to synchronize() against map, its concurrent
ImplThreadLocal local = extensionToLoaderMap.get(key.getExtension());
if (local == null){
throw new IllegalStateException("No loader registered for type \"" +
key.getExtension() + "\"");
}
return (AssetLoader) local.get();
}
public void clearCache(){
// The iterator of the values collection is thread safe
for (AssetCache cache : classToCacheMap.values()){
cache.clearCache();
}
}
public <T extends AssetCache> T getCache(Class<T> cacheClass) {
if (cacheClass == null) {
return null;
}
T cache = (T) classToCacheMap.get(cacheClass);
if (cache == null) {
synchronized (classToCacheMap) {
cache = (T) classToCacheMap.get(cacheClass);
if (cache == null) {
try {
cache = cacheClass.newInstance();
classToCacheMap.put(cacheClass, cache);
} catch (InstantiationException ex) {
throw new IllegalArgumentException("The cache class cannot"
+ " be created, ensure it has empty constructor", ex);
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException("The cache class cannot "
+ "be accessed", ex);
}
}
}
}
return cache;
}
public <T extends AssetProcessor> T getProcessor(Class<T> procClass){
if (procClass == null)
return null;
T proc = (T) classToProcMap.get(procClass);
if (proc == null){
synchronized(classToProcMap){
proc = (T) classToProcMap.get(procClass);
if (proc == null) {
try {
proc = procClass.newInstance();
classToProcMap.put(procClass, proc);
} catch (InstantiationException ex) {
throw new IllegalArgumentException("The processor class cannot"
+ " be created, ensure it has empty constructor", ex);
} catch (IllegalAccessException ex) {
throw new IllegalArgumentException("The processor class cannot "
+ "be accessed", ex);
}
}
}
}
return proc;
}
public void addLoader(final Class<?> loaderType, String ... extensions){
ImplThreadLocal local = new ImplThreadLocal(loaderType);
public void addLoader(final Class<? extends AssetLoader> loaderType, String ... extensions){
// Synchronized access must be used for any ops on classToLoaderMap
ImplThreadLocal local = new ImplThreadLocal(loaderType, extensions);
for (String extension : extensions){
extension = extension.toLowerCase();
synchronized (loaders){
loaders.put(extension, local);
synchronized (classToLoaderMap){
classToLoaderMap.put(loaderType, local);
extensionToLoaderMap.put(extension, local);
}
}
}
public void addLocator(final Class<?> locatorType, String rootPath){
ImplThreadLocal local = new ImplThreadLocal(locatorType, rootPath);
synchronized (genericLocators){
genericLocators.add(local);
public void removeLoader(final Class<? extends AssetLoader> loaderType){
// Synchronized access must be used for any ops on classToLoaderMap
// Find the loader ImplThreadLocal for this class
synchronized (classToLoaderMap){
ImplThreadLocal local = classToLoaderMap.get(loaderType);
// Remove it from the class->loader map
classToLoaderMap.remove(loaderType);
// Remove it from the extension->loader map
for (String extension : local.getExtensions()){
extensionToLoaderMap.remove(extension);
}
}
}
public void addLocator(final Class<? extends AssetLocator> locatorType, String rootPath){
locatorsList.add(new ImplThreadLocal(locatorType, rootPath));
}
public void removeLocator(final Class<?> locatorType, String rootPath){
synchronized (genericLocators){
Iterator<ImplThreadLocal> it = genericLocators.iterator();
while (it.hasNext()){
ImplThreadLocal locator = it.next();
if (locator.getPath().equals(rootPath) &&
locator.getTypeClass().equals(locatorType)){
it.remove();
}
public void removeLocator(final Class<? extends AssetLocator> locatorType, String rootPath){
ArrayList<ImplThreadLocal<AssetLocator>> locatorsToRemove = new ArrayList<ImplThreadLocal<AssetLocator>>();
Iterator<ImplThreadLocal<AssetLocator>> it = locatorsList.iterator();
while (it.hasNext()){
ImplThreadLocal locator = it.next();
if (locator.getPath().equals(rootPath) &&
locator.getTypeClass().equals(locatorType)){
//it.remove();
// copy on write list doesn't support iterator remove,
// must use temporary list
locatorsToRemove.add(locator);
}
}
locatorsList.removeAll(locatorsToRemove);
}
}

@ -1,29 +1,65 @@
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.asset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.material.Material;
/**
* Used for loading {@link Material materials} only (not material definitions).
*
* Used for loading {@link Material materials} only (not material definitions!).
* Material instances use cloneable smart asset management so that they and any
* referenced textures will be collected when all instances of the material
* become unreachable.
*
* @author Kirill Vainer
*/
public class MaterialKey extends AssetKey {
public MaterialKey(String name){
public class MaterialKey extends AssetKey<Material> {
public MaterialKey(String name) {
super(name);
}
public MaterialKey(){
public MaterialKey() {
super();
}
@Override
public boolean useSmartCache(){
return true;
public Class<? extends AssetCache> getCacheType() {
return WeakRefCloneAssetCache.class;
}
@Override
public Object createClonedInstance(Object asset){
Material mat = (Material) asset;
return mat.clone();
public Class<? extends AssetProcessor> getProcessorType() {
return CloneableAssetProcessor.class;
}
}

@ -29,33 +29,38 @@
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.asset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.scene.Spatial;
/**
* Used to load model files, such as OBJ or Blender models.
* This uses cloneable smart asset management, so that when all clones of
* this model become unreachable, the original asset is purged from the cache,
* allowing textures, materials, shaders, etc referenced by the model to
* become collected.
*
* @author Kirill Vainer
*/
public class ModelKey extends AssetKey<Spatial> {
public ModelKey(String name){
public ModelKey(String name) {
super(name);
}
public ModelKey(){
public ModelKey() {
super();
}
@Override
public boolean useSmartCache(){
return true;
public Class<? extends AssetCache> getCacheType(){
return WeakRefCloneAssetCache.class;
}
@Override
public Object createClonedInstance(Object asset){
Spatial model = (Spatial) asset;
return model.clone();
public Class<? extends AssetProcessor> getProcessorType(){
return CloneableAssetProcessor.class;
}
}

@ -31,15 +31,29 @@
*/
package com.jme3.asset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.export.OutputCapsule;
import com.jme3.texture.Image;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.Type;
import com.jme3.texture.*;
import com.jme3.texture.TextureProcessor;
import java.io.IOException;
import java.nio.ByteBuffer;
/**
* Used to load textures from image files such as JPG or PNG.
* Note that texture loaders actually load the asset as an {@link Image}
* object, which is then converted to a {@link Texture} in the
* {@link TextureProcessor#postProcess(com.jme3.asset.AssetKey, java.lang.Object) }
* method. Since textures are cloneable smart assets, the texture stored
* in the cache will be collected when all clones of the texture become
* unreachable.
*
* @author Kirill Vainer
*/
public class TextureKey extends AssetKey<Texture> {
private boolean generateMips;
@ -47,7 +61,7 @@ public class TextureKey extends AssetKey<Texture> {
private boolean asCube;
private boolean asTexture3D;
private int anisotropy;
private Texture.Type textureTypeHint=Texture.Type.TwoDimensional;
private Texture.Type textureTypeHint = Texture.Type.TwoDimensional;
public TextureKey(String name, boolean flipY) {
super(name);
@ -64,58 +78,19 @@ public class TextureKey extends AssetKey<Texture> {
@Override
public String toString() {
return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmaped)" : "");
return name + (flipY ? " (Flipped)" : "") + (asCube ? " (Cube)" : "") + (generateMips ? " (Mipmapped)" : "");
}
/**
* Enable smart caching for textures
* @return true to enable smart cache
*/
@Override
public boolean useSmartCache() {
return true;
public Class<? extends AssetCache> getCacheType(){
return WeakRefCloneAssetCache.class;
}
@Override
public Object createClonedInstance(Object asset) {
Texture tex = (Texture) asset;
return tex.createSimpleClone();
public Class<? extends AssetProcessor> getProcessorType(){
return TextureProcessor.class;
}
@Override
public Object postProcess(Object asset) {
Image img = (Image) asset;
if (img == null) {
return null;
}
Texture tex;
if (isAsCube()) {
if (isFlipY()) {
// also flip -y and +y image in cubemap
ByteBuffer pos_y = img.getData(2);
img.setData(2, img.getData(3));
img.setData(3, pos_y);
}
tex = new TextureCubeMap();
} else if (isAsTexture3D()) {
tex = new Texture3D();
} else {
tex = new Texture2D();
}
// enable mipmaps if image has them
// or generate them if requested by user
if (img.hasMipmaps() || isGenerateMips()) {
tex.setMinFilter(Texture.MinFilter.Trilinear);
}
tex.setAnisotropicFilter(getAnisotropy());
tex.setName(getName());
tex.setImage(img);
return tex;
}
public boolean isFlipY() {
return flipY;
}
@ -152,14 +127,6 @@ public class TextureKey extends AssetKey<Texture> {
this.asTexture3D = asTexture3D;
}
@Override
public boolean equals(Object other) {
if (!(other instanceof TextureKey)) {
return false;
}
return super.equals(other) && isFlipY() == ((TextureKey) other).isFlipY();
}
public Type getTextureTypeHint() {
return textureTypeHint;
}
@ -168,7 +135,53 @@ public class TextureKey extends AssetKey<Texture> {
this.textureTypeHint = textureTypeHint;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final TextureKey other = (TextureKey) obj;
if (!super.equals(obj)) {
return false;
}
if (this.generateMips != other.generateMips) {
return false;
}
if (this.flipY != other.flipY) {
return false;
}
if (this.asCube != other.asCube) {
return false;
}
if (this.asTexture3D != other.asTexture3D) {
return false;
}
if (this.anisotropy != other.anisotropy) {
return false;
}
if (this.textureTypeHint != other.textureTypeHint) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 17 * hash + (super.hashCode());
hash = 17 * hash + (this.generateMips ? 1 : 0);
hash = 17 * hash + (this.flipY ? 1 : 0);
hash = 17 * hash + (this.asCube ? 1 : 0);
hash = 17 * hash + (this.asTexture3D ? 1 : 0);
hash = 17 * hash + this.anisotropy;
hash = 17 * hash + (this.textureTypeHint != null ? this.textureTypeHint.hashCode() : 0);
return hash;
}
@Override
public void write(JmeExporter ex) throws IOException {
super.write(ex);
OutputCapsule oc = ex.getCapsule(this);

@ -0,0 +1,75 @@
package com.jme3.asset.cache;
import com.jme3.asset.AssetKey;
/**
* <code>AssetCache</code> is an interface for asset caches.
* Allowing storage of loaded resources in order to improve their access time
* if they are requested again in a short period of time.
* Depending on the asset type and how it is used, a specialized
* caching method can be selected that is most appropriate for that asset type.
* The asset cache must be thread safe.
* <p>
*
*
* @author Kirill Vainer
*/
public interface AssetCache {
/**
* Adds an asset to the cache.
* Once added, it should be possible to retrieve the asset
* by using the {@link #getFromCache(com.jme3.asset.AssetKey) } method.
* However the caching criteria may at some point choose that the asset
* should be removed from the cache to save memory, in that case,
* {@link #getFromCache(com.jme3.asset.AssetKey) } will return null.
* <p><font color="red">Thread-Safe</font>
*
* @param <T> The type of the asset to cache.
* @param key The asset key that can be used to look up the asset.
* @param obj The asset data to cache.
*/
public <T> void addToCache(AssetKey<T> key, T obj);
/**
* This should be called by the asset manager when it has successfully
* acquired a cached asset (with {@link #getFromCache(com.jme3.asset.AssetKey) })
* and cloned it for use.
* <p><font color="red">Thread-Safe</font>
*
* @param <T> The type of the asset to register.
* @param key The asset key of the loaded asset (used to retrieve from cache)
* @param clone The <strong>clone</strong> of the asset retrieved from
* the cache.
*/
public <T> void registerAssetClone(AssetKey<T> key, T clone);
/**
* Retrieves an asset from the cache.
* It is possible to add an asset to the cache using
* {@link #addToCache(com.jme3.asset.AssetKey, java.lang.Object) }.
* The asset may be removed from the cache automatically even if
* it was added previously, in that case, this method will return null.
* <p><font color="red">Thread-Safe</font>
*
* @param <T> The type of the asset to retrieve
* @param key The key used to lookup the asset.
* @return The asset that was previously cached, or null if not found.
*/
public <T> T getFromCache(AssetKey<T> key);
/**
* Deletes an asset from the cache.
* <p><font color="red">Thread-Safe</font>
*
* @param key The asset key to find the asset to delete.
* @return True if the asset was successfully found in the cache
* and removed.
*/
public boolean deleteFromCache(AssetKey key);
/**
* Deletes all assets from the cache.
* <p><font color="red">Thread-Safe</font>
*/
public void clearCache();
}

@ -0,0 +1,41 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.jme3.asset.cache;
import com.jme3.asset.AssetKey;
import java.util.concurrent.ConcurrentHashMap;
/**
* <code>SimpleAssetCache</code> is an asset cache
* that caches assets without any automatic removal policy. The user
* is expected to manually call {@link #deleteFromCache(com.jme3.asset.AssetKey) }
* to delete any assets.
*
* @author Kirill Vainer
*/
public class SimpleAssetCache implements AssetCache {
private final ConcurrentHashMap<AssetKey, Object> keyToAssetMap = new ConcurrentHashMap<AssetKey, Object>();
public <T> void addToCache(AssetKey<T> key, T obj) {
keyToAssetMap.put(key, obj);
}
public <T> void registerAssetClone(AssetKey<T> key, T clone) {
}
public <T> T getFromCache(AssetKey<T> key) {
return (T) keyToAssetMap.get(key);
}
public boolean deleteFromCache(AssetKey key) {
return keyToAssetMap.remove(key) != null;
}
public void clearCache() {
keyToAssetMap.clear();
}
}

@ -0,0 +1,88 @@
package com.jme3.asset.cache;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.concurrent.ConcurrentHashMap;
/**
* A garbage collector bound asset cache that handles non-clonable objects.
* This cache assumes that the asset given to the user is the same asset
* that has been stored in the cache, in other words,
* {@link AssetProcessor#createClone(java.lang.Object) } for that asset
* returns the same object as the argument.
* This implementation will remove the asset from the cache
* once the asset is no longer referenced in user code and memory is low,
* e.g. the VM feels like purging the weak references for that asset.
*
* @author Kirill Vainer
*/
public class WeakRefAssetCache implements AssetCache {
private final ReferenceQueue<Object> refQueue = new ReferenceQueue<Object>();
private final ConcurrentHashMap<AssetKey, AssetRef> assetCache
= new ConcurrentHashMap<AssetKey, AssetRef>();
private static class AssetRef extends WeakReference<Object> {
private final AssetKey assetKey;
public AssetRef(AssetKey assetKey, Object originalAsset, ReferenceQueue<Object> refQueue){
super(originalAsset, refQueue);
this.assetKey = assetKey;
}
}
private void removeCollectedAssets(){
int removedAssets = 0;
for (AssetRef ref; (ref = (AssetRef)refQueue.poll()) != null;){
// Asset was collected, note that at this point the asset cache
// might not even have this asset anymore, it is OK.
if (assetCache.remove(ref.assetKey) != null){
removedAssets ++;
//System.out.println("WeakRefAssetCache: The asset " + ref.assetKey + " was purged from the cache");
}
}
if (removedAssets >= 1) {
// System.out.println("WeakRefAssetCache: " + removedAssets + " assets were purged from the cache.");
}
}
public <T> void addToCache(AssetKey<T> key, T obj) {
removeCollectedAssets();
// NOTE: Some thread issues can hapen if another
// thread is loading an asset with the same key ..
AssetRef ref = new AssetRef(key, obj, refQueue);
assetCache.put(key, ref);
// Texture t = (Texture) obj;
// Image i = t.getImage();
// System.out.println("add to cache " + System.identityHashCode(i));
}
public <T> T getFromCache(AssetKey<T> key) {
AssetRef ref = assetCache.get(key);
if (ref != null){
return (T) ref.get();
}else{
return null;
}
}
public boolean deleteFromCache(AssetKey key) {
return assetCache.remove(key) != null;
}
public void clearCache() {
assetCache.clear();
}
public <T> void registerAssetClone(AssetKey<T> key, T clone) {
// Texture t = (Texture) clone;
// System.out.println("clonable asset " + System.identityHashCode(t.getImage()));
//throw new UnsupportedOperationException("Cannot use this cache for cloneable assets");
}
}

@ -0,0 +1,116 @@
package com.jme3.asset.cache;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.asset.AssetKey;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.WeakHashMap;
/**
* <codeWeakRefCloneAssetCache</code> caches cloneable assets in a weak-key
* cache, allowing them to be collected when memory is low.
* The cache stores weak references to the asset keys, so that
* when all clones of the original asset are collected, will cause the
* asset to be automatically removed from the cache.
*
* @author Kirill Vainer
*/
public class WeakRefCloneAssetCache implements AssetCache {
private static final class SmartCachedAsset {
WeakReference<AssetKey> key;
CloneableSmartAsset asset;
public SmartCachedAsset(CloneableSmartAsset originalAsset, AssetKey originalKey) {
this.key = new WeakReference<AssetKey>(originalKey);
this.asset = originalAsset;
}
}
private final WeakHashMap<AssetKey, SmartCachedAsset> smartCache
= new WeakHashMap<AssetKey, SmartCachedAsset>();
private final ThreadLocal<ArrayDeque<AssetKey>> assetLoadStack
= new ThreadLocal<ArrayDeque<AssetKey>>() {
@Override
protected ArrayDeque<AssetKey> initialValue() {
return new ArrayDeque<AssetKey>();
}
};
public <T> void addToCache(AssetKey<T> key, T obj) {
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));
}
// 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);
}
public <T> void registerAssetClone(AssetKey<T> key, T clone) {
ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
((CloneableSmartAsset)clone).setKey(loadStack.pop());
}
public <T> T getFromCache(AssetKey<T> key) {
SmartCachedAsset smartInfo;
synchronized (smartCache){
smartInfo = smartCache.get(key);
}
if (smartInfo == null) {
return null;
} else {
// 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();
if (keyForTheClone == null){
// The asset was JUST collected by GC
// (between here and smartCache.get)
return null;
}
// Prevent original key from getting collected
// while an asset is loaded for it.
ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
loadStack.push(keyForTheClone);
return (T) smartInfo.asset;
}
}
public boolean deleteFromCache(AssetKey key) {
ArrayDeque<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;
}
}
public void clearCache() {
ArrayDeque<AssetKey> loadStack = assetLoadStack.get();
if (!loadStack.isEmpty()){
throw new UnsupportedOperationException("Cache cannot be modified"
+ "while assets are being loaded");
}
synchronized (smartCache) {
smartCache.clear();
}
}
}

@ -70,7 +70,7 @@ public abstract class AudioData extends NativeObject {
* @return the duration in seconds of the audio clip.
*/
public abstract float getDuration();
/**
* @return Bits per single sample from a channel.
*/
@ -106,5 +106,4 @@ public abstract class AudioData extends NativeObject {
this.bitsPerSample = bitsPerSample;
this.sampleRate = sampleRate;
}
}

@ -33,6 +33,9 @@
package com.jme3.audio;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.WeakRefAssetCache;
import com.jme3.export.InputCapsule;
import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
@ -44,7 +47,7 @@ import java.io.IOException;
*
* @author Kirill Vainer
*/
public class AudioKey extends AssetKey<AudioData> {
public class AudioKey extends AssetKey<AudioNode> {
private boolean stream;
private boolean streamCache;
@ -114,10 +117,51 @@ public class AudioKey extends AssetKey<AudioData> {
}
@Override
public boolean shouldCache(){
return !stream && !streamCache;
public Class<? extends AssetCache> getCacheType() {
if ((stream && streamCache) || !stream) {
// Use non-cloning cache
return WeakRefAssetCache.class;
} else {
// Disable caching for streaming audio
return null;
}
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final AudioKey other = (AudioKey) obj;
if (!super.equals(other)) {
return false;
}
if (this.stream != other.stream) {
return false;
}
if (this.streamCache != other.streamCache) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + (super.hashCode());
hash = 67 * hash + (this.stream ? 1 : 0);
hash = 67 * hash + (this.streamCache ? 1 : 0);
return hash;
}
@Override
public Class<? extends AssetProcessor> getProcessorType() {
return AudioProcessor.class;
}
@Override
public void write(JmeExporter ex) throws IOException{
super.write(ex);

@ -0,0 +1,19 @@
package com.jme3.audio;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
public class AudioProcessor implements AssetProcessor{
public Object postProcess(AssetKey key, Object obj) {
AudioKey audioKey = (AudioKey) key;
AudioData audioData = (AudioData) obj;
return new AudioNode(audioData, audioKey);
}
public Object createClone(Object obj) {
AudioNode node = (AudioNode) obj;
return node.clone();
}
}

@ -29,7 +29,7 @@
*/
package com.jme3.material;
import com.jme3.asset.Asset;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetManager;
import com.jme3.export.*;
@ -66,7 +66,7 @@ import java.util.logging.Logger;
*
* @author Kirill Vainer
*/
public class Material implements Asset, Cloneable, Savable {
public class Material implements CloneableSmartAsset, Cloneable, Savable {
// Version #2: Fixed issue with RenderState.apply*** flags not getting exported
public static final int SAVABLE_VERSION = 2;

@ -0,0 +1,15 @@
package com.jme3.material;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
public class MaterialProcessor implements AssetProcessor {
public Object postProcess(AssetKey key, Object obj) {
return null;
}
public Object createClone(Object obj) {
return ((Material) obj).clone();
}
}

@ -31,8 +31,8 @@
*/
package com.jme3.scene;
import com.jme3.asset.Asset;
import com.jme3.asset.AssetKey;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.bounding.BoundingVolume;
import com.jme3.collision.Collidable;
import com.jme3.export.*;
@ -63,7 +63,7 @@ import java.util.logging.Logger;
* @author Joshua Slack
* @version $Revision: 4075 $, $Data$
*/
public abstract class Spatial implements Savable, Cloneable, Collidable, Asset {
public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset {
private static final Logger logger = Logger.getLogger(Spatial.class.getName());

@ -34,7 +34,7 @@ package com.jme3.texture;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetNotFoundException;
import com.jme3.asset.Asset;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.asset.TextureKey;
import com.jme3.export.*;
import com.jme3.util.PlaceholderAssets;
@ -57,7 +57,7 @@ import java.util.logging.Logger;
* @author Joshua Slack
* @version $Id: Texture.java 4131 2009-03-19 20:15:28Z blaine.dev $
*/
public abstract class Texture implements Asset, Savable, Cloneable {
public abstract class Texture implements CloneableSmartAsset, Savable, Cloneable {
public enum Type {

@ -0,0 +1,49 @@
package com.jme3.texture;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
import com.jme3.asset.TextureKey;
import java.nio.ByteBuffer;
public class TextureProcessor implements AssetProcessor {
public Object postProcess(AssetKey key, Object obj) {
TextureKey texKey = (TextureKey) key;
Image img = (Image) obj;
if (img == null) {
return null;
}
Texture tex;
if (texKey.isAsCube()) {
if (texKey.isFlipY()) {
// also flip -y and +y image in cubemap
ByteBuffer pos_y = img.getData(2);
img.setData(2, img.getData(3));
img.setData(3, pos_y);
}
tex = new TextureCubeMap();
} else if (texKey.isAsTexture3D()) {
tex = new Texture3D();
} else {
tex = new Texture2D();
}
// enable mipmaps if image has them
// or generate them if requested by user
if (img.hasMipmaps() || texKey.isGenerateMips()) {
tex.setMinFilter(Texture.MinFilter.Trilinear);
}
tex.setAnisotropicFilter(texKey.getAnisotropy());
tex.setName(texKey.getName());
tex.setImage(img);
return tex;
}
public Object createClone(Object obj) {
Texture tex = (Texture) obj;
return tex.clone();
}
}

@ -32,36 +32,43 @@
package jme3test.asset;
import com.jme3.asset.Asset;
import com.jme3.asset.AssetCache;
import com.jme3.asset.AssetKey;
import com.jme3.asset.AssetProcessor;
import com.jme3.asset.CloneableAssetProcessor;
import com.jme3.asset.CloneableSmartAsset;
import com.jme3.asset.cache.AssetCache;
import com.jme3.asset.cache.SimpleAssetCache;
import com.jme3.asset.cache.WeakRefAssetCache;
import com.jme3.asset.cache.WeakRefCloneAssetCache;
import java.util.ArrayList;
import java.util.List;
public class TestAssetCache {
/**
* Keep references to loaded assets
*/
private final static boolean KEEP_REFERENCES = false;
/**
* Enable smart cache use
* Counter for asset keys
*/
private final static boolean USE_SMART_CACHE = true;
private static int counter = 0;
/**
* Enable cloneable asset use
* Dummy data is an asset having 10 KB to put a dent in the garbage collector
*/
private final static boolean CLONEABLE_ASSET = true;
private static int counter = 0;
private static class DummyData implements Asset {
private static class DummyData implements CloneableSmartAsset {
private AssetKey key;
private byte[] data = new byte[10000];
private byte[] data = new byte[10 * 1024];
@Override
public Object clone(){
try {
DummyData clone = (DummyData) super.clone();
clone.data = data.clone();
return clone;
} catch (CloneNotSupportedException ex) {
throw new AssertionError();
}
}
public byte[] getData(){
return data;
}
@ -75,67 +82,64 @@ public class TestAssetCache {
}
}
private static class SmartKey extends AssetKey {
/**
* Dummy key is indexed by a generated ID
*/
private static class DummyKey extends AssetKey<DummyData> implements Cloneable {
private int id = 0;
public DummyKey(){
super(".");
id = counter++;
}
public SmartKey(){
public DummyKey(int id){
super(".");
counter++;
this.id = id;
}
@Override
public int hashCode(){
return 0;
return id;
}
@Override
public boolean equals(Object other){
return false;
return ((DummyKey)other).id == id;
}
@Override
public boolean useSmartCache(){
return true;
public DummyKey clone(){
return new DummyKey(id);
}
@Override
public Object createClonedInstance(Object asset){
DummyData data = new DummyData();
return data;
public String toString() {
return "ID=" + id;
}
}
private static class DumbKey extends AssetKey {
public DumbKey(){
super(".");
counter++;
}
private static void runTest(boolean cloneAssets, boolean smartCache, boolean keepRefs, int limit) {
counter = 0;
List<Object> refs = new ArrayList<Object>(limit);
@Override
public int hashCode(){
return 0;
}
AssetCache cache;
AssetProcessor proc = null;
@Override
public boolean equals(Object other){
return false;
if (cloneAssets) {
proc = new CloneableAssetProcessor();
}
@Override
public Object createClonedInstance(Object asset){
if (CLONEABLE_ASSET){
DummyData data = new DummyData();
return data;
}else{
return asset;
if (smartCache) {
if (cloneAssets) {
cache = new WeakRefCloneAssetCache();
} else {
cache = new WeakRefAssetCache();
}
} else {
cache = new SimpleAssetCache();
}
}
public static void main(String[] args){
List<Object> refs = new ArrayList<Object>(5000);
AssetCache cache = new AssetCache();
System.gc();
System.gc();
@ -144,28 +148,81 @@ public class TestAssetCache {
long memory = Runtime.getRuntime().freeMemory();
while (true){
AssetKey key;
while (counter < limit){
// Create a key
DummyKey key = new DummyKey();
// Create some data
DummyData data = new DummyData();
if (USE_SMART_CACHE){
key = new SmartKey();
}else{
key = new DumbKey();
// Post process the data before placing it in the cache
if (proc != null){
data = (DummyData) proc.postProcess(key, data);
}
if (data.key != null){
// Keeping a hard reference to the key in the cache
// means the asset will never be collected => bug
throw new AssertionError();
}
DummyData data = new DummyData();
cache.addToCache(key, data);
if (KEEP_REFERENCES){
// Get the asset from the cache
AssetKey<DummyData> keyToGet = key.clone();
// NOTE: Commented out because getFromCache leaks the original key
// DummyData someLoaded = (DummyData) cache.getFromCache(keyToGet);
// if (someLoaded != data){
// // Failed to get the same asset from the cache => bug
// // Since a hard reference to the key is kept,
// // it cannot be collected at this point.
// throw new AssertionError();
// }
// Clone the asset
if (proc != null){
// Data is now the clone!
data = (DummyData) proc.createClone(data);
if (smartCache) {
// Registering a clone is only needed
// if smart cache is used.
cache.registerAssetClone(keyToGet, data);
// The clone of the asset must have the same key as the original
// otherwise => bug
if (data.key != key){
throw new AssertionError();
}
}
}
// Keep references to the asset => *should* prevent
// collections of the asset in the cache thus causing
// an out of memory error.
if (keepRefs){
// Prevent the saved references from taking too much memory ..
if (cloneAssets) {
data.data = null;
}
refs.add(data);
}
if ((counter % 100) == 0){
if ((counter % 1000) == 0){
long newMem = Runtime.getRuntime().freeMemory();
System.out.println("Allocated objects: " + counter);
System.out.println("Allocated memory: " + ((memory - newMem)/1024) + "K" );
System.out.println("Allocated memory: " + ((memory - newMem)/(1024*1024)) + " MB" );
memory = newMem;
}
}
}
public static void main(String[] args){
// Test cloneable smart asset
System.out.println("====== Running Cloneable Smart Asset Test ======");
runTest(true, true, false, 100000);
// Test non-cloneable smart asset
System.out.println("====== Running Non-cloneable Smart Asset Test ======");
runTest(false, true, false, 100000);
}
}

Loading…
Cancel
Save