From bd4214f3bd07188cda13e2644ec0377b73acd03e Mon Sep 17 00:00:00 2001 From: "Sha..rd" Date: Sat, 14 Apr 2012 19:58:17 +0000 Subject: [PATCH] * 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 --- .../com/jme3/shader/plugins/GLSLLoader.java | 10 +- .../src/core/com/jme3/asset/AssetCache.java | 131 --------- engine/src/core/com/jme3/asset/AssetKey.java | 69 +++-- .../src/core/com/jme3/asset/AssetManager.java | 140 ++++++++-- .../core/com/jme3/asset/AssetProcessor.java | 72 +++++ .../jme3/asset/CloneableAssetProcessor.java | 52 ++++ .../{Asset.java => CloneableSmartAsset.java} | 24 +- .../com/jme3/asset/DesktopAssetManager.java | 253 ++++++++---------- .../src/core/com/jme3/asset/ImplHandler.java | 205 ++++++++++---- .../src/core/com/jme3/asset/MaterialKey.java | 58 +++- engine/src/core/com/jme3/asset/ModelKey.java | 23 +- .../src/core/com/jme3/asset/TextureKey.java | 127 +++++---- .../core/com/jme3/asset/cache/AssetCache.java | 75 ++++++ .../jme3/asset/cache/SimpleAssetCache.java | 41 +++ .../jme3/asset/cache/WeakRefAssetCache.java | 88 ++++++ .../asset/cache/WeakRefCloneAssetCache.java | 116 ++++++++ engine/src/core/com/jme3/audio/AudioData.java | 3 +- engine/src/core/com/jme3/audio/AudioKey.java | 50 +++- .../core/com/jme3/audio/AudioProcessor.java | 19 ++ .../src/core/com/jme3/material/Material.java | 4 +- .../com/jme3/material/MaterialProcessor.java | 15 ++ engine/src/core/com/jme3/scene/Spatial.java | 4 +- engine/src/core/com/jme3/texture/Texture.java | 4 +- .../com/jme3/texture/TextureProcessor.java | 49 ++++ .../test/jme3test/asset/TestAssetCache.java | 183 ++++++++----- 25 files changed, 1263 insertions(+), 552 deletions(-) delete mode 100644 engine/src/core/com/jme3/asset/AssetCache.java create mode 100644 engine/src/core/com/jme3/asset/AssetProcessor.java create mode 100644 engine/src/core/com/jme3/asset/CloneableAssetProcessor.java rename engine/src/core/com/jme3/asset/{Asset.java => CloneableSmartAsset.java} (78%) create mode 100644 engine/src/core/com/jme3/asset/cache/AssetCache.java create mode 100644 engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java create mode 100644 engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java create mode 100644 engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java create mode 100644 engine/src/core/com/jme3/audio/AudioProcessor.java create mode 100644 engine/src/core/com/jme3/material/MaterialProcessor.java create mode 100644 engine/src/core/com/jme3/texture/TextureProcessor.java diff --git a/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java b/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java index a4b063538..536e55f39 100644 --- a/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.java +++ b/engine/src/core-plugins/com/jme3/shader/plugins/GLSLLoader.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 { - public GlslDependKey(String name){ + + public GlslDependKey(String name) { super(name); } + @Override - public boolean shouldCache(){ - return false; + public Class getCacheType() { + // Disallow caching here + return null; } } diff --git a/engine/src/core/com/jme3/asset/AssetCache.java b/engine/src/core/com/jme3/asset/AssetCache.java deleted file mode 100644 index 99381e083..000000000 --- a/engine/src/core/com/jme3/asset/AssetCache.java +++ /dev/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 AssetCache 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 smartKey; - public Asset asset; - } - - private final WeakHashMap smartCache - = new WeakHashMap(); - private final HashMap regularCache = new HashMap(); - - /** - * Adds a resource to the cache. - *

- * Thread-safe. - * @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(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. - *

- * Thread-safe. - */ - 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. - *

- * Thread-safe. - * @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(); - } - } -} diff --git a/engine/src/core/com/jme3/asset/AssetKey.java b/engine/src/core/com/jme3/asset/AssetKey.java index 9b6f0cd68..ff767b2a6 100644 --- a/engine/src/core/com/jme3/asset/AssetKey.java +++ b/engine/src/core/com/jme3/asset/AssetKey.java @@ -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 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 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 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 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 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 getProcessorType(){ + return null; } /** diff --git a/engine/src/core/com/jme3/asset/AssetManager.java b/engine/src/core/com/jme3/asset/AssetManager.java index a863ebf09..eb064059c 100644 --- a/engine/src/core/com/jme3/asset/AssetManager.java +++ b/engine/src/core/com/jme3/asset/AssetManager.java @@ -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; /** * AssetManager provides an interface for managing the data assets * of a jME3 application. + *

+ * 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: + *

    + *
  • {@link FileLocator} - Used to find assets on the local file system.
  • + *
  • {@link ClasspathLocator} - Used to find assets in the Java classpath
  • + *
+ *

+ * 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: + *

    + *
  • {@link OBJLoader} - Used to load Wavefront .OBJ model files
  • + *
  • {@link TGALoader} - Used to load Targa image files
  • + *
+ *

+ * 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 not 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 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 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 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 locatorClass); + /** + * Add an {@link AssetEventListener} to receive events from this + * AssetManager. + * @param listener The asset event listener to add + */ + public void addAssetEventListener(AssetEventListener listener); + + /** + * Remove an {@link AssetEventListener} from receiving events from this + * AssetManager + * @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 - * AssetManager. There can only be one {@link AssetEventListener} - * associated with an AssetManager + * AssetManager. 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 loadAsset(AssetKey key); /** - * Load a named asset by name, calling this method + * Load an asset by name, calling this method * is the same as calling * * 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) diff --git a/engine/src/core/com/jme3/asset/AssetProcessor.java b/engine/src/core/com/jme3/asset/AssetProcessor.java new file mode 100644 index 000000000..862e14bb1 --- /dev/null +++ b/engine/src/core/com/jme3/asset/AssetProcessor.java @@ -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; + +/** + * AssetProcessor 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); +} diff --git a/engine/src/core/com/jme3/asset/CloneableAssetProcessor.java b/engine/src/core/com/jme3/asset/CloneableAssetProcessor.java new file mode 100644 index 000000000..0c546f596 --- /dev/null +++ b/engine/src/core/com/jme3/asset/CloneableAssetProcessor.java @@ -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; + +/** + * CloneableAssetProcessor 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(); + } + +} diff --git a/engine/src/core/com/jme3/asset/Asset.java b/engine/src/core/com/jme3/asset/CloneableSmartAsset.java similarity index 78% rename from engine/src/core/com/jme3/asset/Asset.java rename to engine/src/core/com/jme3/asset/CloneableSmartAsset.java index f36f96395..affaab19a 100644 --- a/engine/src/core/com/jme3/asset/Asset.java +++ b/engine/src/core/com/jme3/asset/CloneableSmartAsset.java @@ -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 CloneableSmartAsset interface allows use + * of cloneable smart asset management. *

* 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)} != someAsset. + * e.g. {@link AssetProcessor#createClone(java.lang.Object) createClone(someAsset)} != someAsset. *

* 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. diff --git a/engine/src/core/com/jme3/asset/DesktopAssetManager.java b/engine/src/core/com/jme3/asset/DesktopAssetManager.java index 4ec475124..1b31bd75d 100644 --- a/engine/src/core/com/jme3/asset/DesktopAssetManager.java +++ b/engine/src/core/com/jme3/asset/DesktopAssetManager.java @@ -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 classLoaders; - -// private final ThreadingManager threadingMan = new ThreadingManager(this); -// private final Set alreadyLoadingSet = new HashSet(); + private CopyOnWriteArrayList eventListeners = + new CopyOnWriteArrayList(); + + private List classLoaders = + Collections.synchronizedList(new ArrayList()); 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()); - 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 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 loader, String ... extensions){ @@ -142,6 +154,14 @@ public class DesktopAssetManager implements AssetManager { registerLoader(clazz, extensions); } } + + public void unregisterLoader(Class loaderClass) { + handler.removeLoader(loaderClass); + if (logger.isLoggable(Level.FINER)){ + logger.log(Level.FINER, "Unregistered loader: {0}", + loaderClass.getSimpleName()); + } + } public void registerLocator(String rootPath, Class 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. - *

- * Thread-safe. - */ - public boolean deleteFromCache(AssetKey key){ - return cache.deleteFromCache(key); - } - - /** - * Adds a resource to the cache. - *

- * Thread-safe. - */ - 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)); } - } diff --git a/engine/src/core/com/jme3/asset/ImplHandler.java b/engine/src/core/com/jme3/asset/ImplHandler.java index 9b0b50a88..9913192ee 100644 --- a/engine/src/core/com/jme3/asset/ImplHandler.java +++ b/engine/src/core/com/jme3/asset/ImplHandler.java @@ -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 parentAssetKey = new ThreadLocal(); - private final ArrayList genericLocators = - new ArrayList(); + private final CopyOnWriteArrayList> locatorsList = + new CopyOnWriteArrayList>(); + + private final HashMap, ImplThreadLocal> classToLoaderMap = + new HashMap, ImplThreadLocal>(); - private final HashMap loaders = - new HashMap(); + private final ConcurrentHashMap> extensionToLoaderMap = + new ConcurrentHashMap>(); + + private final ConcurrentHashMap, AssetProcessor> classToProcMap = + new ConcurrentHashMap, AssetProcessor>(); + + private final ConcurrentHashMap, AssetCache> classToCacheMap = + new ConcurrentHashMap, AssetCache>(); - public ImplHandler(AssetManager owner){ - this.owner = owner; + public ImplHandler(AssetManager assetManager){ + this.assetManager = assetManager; } - protected class ImplThreadLocal extends ThreadLocal { + protected class ImplThreadLocal extends ThreadLocal { - private final Class type; + private final Class type; private final String path; + private final String[] extensions; - public ImplThreadLocal(Class type){ + public ImplThreadLocal(Class type, String[] extensions){ this.type = type; - path = null; + this.extensions = extensions; + this.path = null; } - public ImplThreadLocal(Class type, String path){ + public ImplThreadLocal(Class type, String path){ this.type = type; this.path = path; + this.extensions = null; } + public ImplThreadLocal(Class 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 getCache(Class 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 getProcessor(Class 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 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 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 locatorType, String rootPath){ + locatorsList.add(new ImplThreadLocal(locatorType, rootPath)); + } - public void removeLocator(final Class locatorType, String rootPath){ - synchronized (genericLocators){ - Iterator 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 locatorType, String rootPath){ + ArrayList> locatorsToRemove = new ArrayList>(); + Iterator> 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); } } diff --git a/engine/src/core/com/jme3/asset/MaterialKey.java b/engine/src/core/com/jme3/asset/MaterialKey.java index cc74fbc7e..b204ea43c 100644 --- a/engine/src/core/com/jme3/asset/MaterialKey.java +++ b/engine/src/core/com/jme3/asset/MaterialKey.java @@ -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 { + + public MaterialKey(String name) { super(name); } - public MaterialKey(){ + public MaterialKey() { super(); } @Override - public boolean useSmartCache(){ - return true; + public Class getCacheType() { + return WeakRefCloneAssetCache.class; } - + @Override - public Object createClonedInstance(Object asset){ - Material mat = (Material) asset; - return mat.clone(); + public Class getProcessorType() { + return CloneableAssetProcessor.class; } } diff --git a/engine/src/core/com/jme3/asset/ModelKey.java b/engine/src/core/com/jme3/asset/ModelKey.java index fcf5c5399..eed6b3072 100644 --- a/engine/src/core/com/jme3/asset/ModelKey.java +++ b/engine/src/core/com/jme3/asset/ModelKey.java @@ -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 { - public ModelKey(String name){ + public ModelKey(String name) { super(name); } - public ModelKey(){ + public ModelKey() { super(); } + @Override - public boolean useSmartCache(){ - return true; + public Class getCacheType(){ + return WeakRefCloneAssetCache.class; } @Override - public Object createClonedInstance(Object asset){ - Spatial model = (Spatial) asset; - return model.clone(); + public Class getProcessorType(){ + return CloneableAssetProcessor.class; } - } diff --git a/engine/src/core/com/jme3/asset/TextureKey.java b/engine/src/core/com/jme3/asset/TextureKey.java index 118c84ef4..c4ee9f099 100644 --- a/engine/src/core/com/jme3/asset/TextureKey.java +++ b/engine/src/core/com/jme3/asset/TextureKey.java @@ -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 { private boolean generateMips; @@ -47,7 +61,7 @@ public class TextureKey extends AssetKey { 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 { @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 getCacheType(){ + return WeakRefCloneAssetCache.class; } @Override - public Object createClonedInstance(Object asset) { - Texture tex = (Texture) asset; - return tex.createSimpleClone(); + public Class 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 { 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 { 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); diff --git a/engine/src/core/com/jme3/asset/cache/AssetCache.java b/engine/src/core/com/jme3/asset/cache/AssetCache.java new file mode 100644 index 000000000..916ab33c7 --- /dev/null +++ b/engine/src/core/com/jme3/asset/cache/AssetCache.java @@ -0,0 +1,75 @@ +package com.jme3.asset.cache; + +import com.jme3.asset.AssetKey; + +/** + * AssetCache 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. + *

+ * + * + * @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. + *

Thread-Safe + * + * @param 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 void addToCache(AssetKey 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. + *

Thread-Safe + * + * @param The type of the asset to register. + * @param key The asset key of the loaded asset (used to retrieve from cache) + * @param clone The clone of the asset retrieved from + * the cache. + */ + public void registerAssetClone(AssetKey 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. + *

Thread-Safe + * + * @param 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 getFromCache(AssetKey key); + + /** + * Deletes an asset from the cache. + *

Thread-Safe + * + * @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. + *

Thread-Safe + */ + public void clearCache(); +} diff --git a/engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java b/engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java new file mode 100644 index 000000000..0935704cf --- /dev/null +++ b/engine/src/core/com/jme3/asset/cache/SimpleAssetCache.java @@ -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; + +/** + * SimpleAssetCache 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 keyToAssetMap = new ConcurrentHashMap(); + + public void addToCache(AssetKey key, T obj) { + keyToAssetMap.put(key, obj); + } + + public void registerAssetClone(AssetKey key, T clone) { + } + + public T getFromCache(AssetKey key) { + return (T) keyToAssetMap.get(key); + } + + public boolean deleteFromCache(AssetKey key) { + return keyToAssetMap.remove(key) != null; + } + + public void clearCache() { + keyToAssetMap.clear(); + } + +} diff --git a/engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java b/engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java new file mode 100644 index 000000000..53b63a720 --- /dev/null +++ b/engine/src/core/com/jme3/asset/cache/WeakRefAssetCache.java @@ -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 refQueue = new ReferenceQueue(); + + private final ConcurrentHashMap assetCache + = new ConcurrentHashMap(); + + private static class AssetRef extends WeakReference { + + private final AssetKey assetKey; + + public AssetRef(AssetKey assetKey, Object originalAsset, ReferenceQueue 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 void addToCache(AssetKey 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 getFromCache(AssetKey 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 void registerAssetClone(AssetKey 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"); + } +} diff --git a/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java b/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java new file mode 100644 index 000000000..d99d2ff9c --- /dev/null +++ b/engine/src/core/com/jme3/asset/cache/WeakRefCloneAssetCache.java @@ -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; + +/** + * 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 key; + CloneableSmartAsset asset; + + public SmartCachedAsset(CloneableSmartAsset originalAsset, AssetKey originalKey) { + this.key = new WeakReference(originalKey); + this.asset = originalAsset; + } + } + + private final WeakHashMap smartCache + = new WeakHashMap(); + + private final ThreadLocal> assetLoadStack + = new ThreadLocal>() { + @Override + protected ArrayDeque initialValue() { + return new ArrayDeque(); + } + }; + + public void addToCache(AssetKey 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 loadStack = assetLoadStack.get(); + loadStack.push(key); + } + + public void registerAssetClone(AssetKey key, T clone) { + ArrayDeque loadStack = assetLoadStack.get(); + ((CloneableSmartAsset)clone).setKey(loadStack.pop()); + } + + public T getFromCache(AssetKey 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 loadStack = assetLoadStack.get(); + loadStack.push(keyForTheClone); + + return (T) smartInfo.asset; + } + } + + public boolean deleteFromCache(AssetKey key) { + ArrayDeque 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 loadStack = assetLoadStack.get(); + + if (!loadStack.isEmpty()){ + throw new UnsupportedOperationException("Cache cannot be modified" + + "while assets are being loaded"); + } + synchronized (smartCache) { + smartCache.clear(); + } + } +} diff --git a/engine/src/core/com/jme3/audio/AudioData.java b/engine/src/core/com/jme3/audio/AudioData.java index 186a7346b..8470015cd 100644 --- a/engine/src/core/com/jme3/audio/AudioData.java +++ b/engine/src/core/com/jme3/audio/AudioData.java @@ -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; } - } diff --git a/engine/src/core/com/jme3/audio/AudioKey.java b/engine/src/core/com/jme3/audio/AudioKey.java index e9492bb9b..95d17c98c 100644 --- a/engine/src/core/com/jme3/audio/AudioKey.java +++ b/engine/src/core/com/jme3/audio/AudioKey.java @@ -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 { +public class AudioKey extends AssetKey { private boolean stream; private boolean streamCache; @@ -114,10 +117,51 @@ public class AudioKey extends AssetKey { } @Override - public boolean shouldCache(){ - return !stream && !streamCache; + public Class 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 getProcessorType() { + return AudioProcessor.class; + } + @Override public void write(JmeExporter ex) throws IOException{ super.write(ex); diff --git a/engine/src/core/com/jme3/audio/AudioProcessor.java b/engine/src/core/com/jme3/audio/AudioProcessor.java new file mode 100644 index 000000000..3fd09bff2 --- /dev/null +++ b/engine/src/core/com/jme3/audio/AudioProcessor.java @@ -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(); + } + +} diff --git a/engine/src/core/com/jme3/material/Material.java b/engine/src/core/com/jme3/material/Material.java index 6e5872a9c..0989a0aaf 100644 --- a/engine/src/core/com/jme3/material/Material.java +++ b/engine/src/core/com/jme3/material/Material.java @@ -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; diff --git a/engine/src/core/com/jme3/material/MaterialProcessor.java b/engine/src/core/com/jme3/material/MaterialProcessor.java new file mode 100644 index 000000000..c4cfb6996 --- /dev/null +++ b/engine/src/core/com/jme3/material/MaterialProcessor.java @@ -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(); + } +} diff --git a/engine/src/core/com/jme3/scene/Spatial.java b/engine/src/core/com/jme3/scene/Spatial.java index 14880607d..a5e72ebfd 100644 --- a/engine/src/core/com/jme3/scene/Spatial.java +++ b/engine/src/core/com/jme3/scene/Spatial.java @@ -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()); diff --git a/engine/src/core/com/jme3/texture/Texture.java b/engine/src/core/com/jme3/texture/Texture.java index e61dc962c..78ecc72eb 100644 --- a/engine/src/core/com/jme3/texture/Texture.java +++ b/engine/src/core/com/jme3/texture/Texture.java @@ -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 { diff --git a/engine/src/core/com/jme3/texture/TextureProcessor.java b/engine/src/core/com/jme3/texture/TextureProcessor.java new file mode 100644 index 000000000..e6e52f99f --- /dev/null +++ b/engine/src/core/com/jme3/texture/TextureProcessor.java @@ -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(); + } + +} diff --git a/engine/src/test/jme3test/asset/TestAssetCache.java b/engine/src/test/jme3test/asset/TestAssetCache.java index 066fe211d..ad219f73d 100644 --- a/engine/src/test/jme3test/asset/TestAssetCache.java +++ b/engine/src/test/jme3test/asset/TestAssetCache.java @@ -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 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 refs = new ArrayList(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 refs = new ArrayList(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 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); + } }