From b34649d399770af7f48cac4fab8836b41c076b26 Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Tue, 27 Oct 2015 21:05:32 +0100 Subject: [PATCH 01/37] Bugfix: fix to importing blend files with linked content. --- .../main/java/com/jme3/asset/BlenderKey.java | 4 + .../blender/AbstractBlenderHelper.java | 87 ++--------- .../scene/plugins/blender/BlenderContext.java | 137 ++++++++++++++++-- .../scene/plugins/blender/BlenderLoader.java | 86 ++++++++--- .../blender/landscape/LandscapeHelper.java | 1 + 5 files changed, 205 insertions(+), 110 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java index c412c07b3..3b72088ab 100644 --- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java +++ b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java @@ -230,18 +230,22 @@ public class BlenderKey extends ModelKey { } /** + * Not used any more. * This method sets the asset root path. * @param assetRootPath * the assets root path */ + @Deprecated public void setAssetRootPath(String assetRootPath) { this.assetRootPath = assetRootPath; } /** + * Not used any more. * This method returns the asset root path. * @return the asset root path */ + @Deprecated public String getAssetRootPath() { return assetRootPath; } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java index e11101c14..142f757e2 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java @@ -31,8 +31,6 @@ */ package com.jme3.scene.plugins.blender; -import java.io.File; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -40,25 +38,17 @@ import java.util.Map.Entry; import java.util.logging.Level; import java.util.logging.Logger; -import com.jme3.animation.Animation; import com.jme3.asset.AssetNotFoundException; import com.jme3.asset.BlenderKey; import com.jme3.export.Savable; -import com.jme3.light.Light; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; -import com.jme3.post.Filter; -import com.jme3.renderer.Camera; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; import com.jme3.scene.plugins.blender.file.BlenderFileException; import com.jme3.scene.plugins.blender.file.Pointer; import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; import com.jme3.scene.plugins.blender.objects.Properties; -import com.jme3.texture.Texture; /** * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can @@ -157,7 +147,6 @@ public abstract class AbstractBlenderHelper { * @throws BlenderFileException * and exception is throw when problems with reading a blend file occur */ - @SuppressWarnings("unchecked") protected Object loadLibrary(Structure id) throws BlenderFileException { Pointer pLib = (Pointer) id.getFieldValue("lib"); if (pLib.isNotNull()) { @@ -167,79 +156,21 @@ public abstract class AbstractBlenderHelper { String path = library.getFieldValue("filepath").toString(); if (!blenderContext.getLinkedFeatures().keySet().contains(path)) { - File file = new File(path); - List pathsToCheck = new ArrayList(); - String currentPath = file.getName(); - do { - pathsToCheck.add(currentPath); - file = file.getParentFile(); - if (file != null) { - currentPath = file.getName() + '/' + currentPath; - } - } while (file != null); - Spatial loadedAsset = null; - BlenderKey blenderKey = null; - for (String p : pathsToCheck) { - blenderKey = new BlenderKey(p); - blenderKey.setLoadUnlinkedAssets(true); - try { - loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey); - break;// break if no exception was thrown - } catch (AssetNotFoundException e) { - LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", p); - } + BlenderKey blenderKey = new BlenderKey(path); + blenderKey.setLoadUnlinkedAssets(true); + try { + loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey); + } catch (AssetNotFoundException e) { + LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", path); } - + if (loadedAsset != null) { Map> linkedData = loadedAsset.getUserData("linkedData"); + for (Entry> entry : linkedData.entrySet()) { String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey(); - - List scenes = (List) entry.getValue().get("scenes"); - for (Node scene : scenes) { - blenderContext.addLinkedFeature(linkedDataFilePath, "SC" + scene.getName(), scene); - } - List objects = (List) entry.getValue().get("objects"); - for (Node object : objects) { - blenderContext.addLinkedFeature(linkedDataFilePath, "OB" + object.getName(), object); - } - List meshes = (List) entry.getValue().get("meshes"); - for (TemporalMesh mesh : meshes) { - blenderContext.addLinkedFeature(linkedDataFilePath, "ME" + mesh.getName(), mesh); - } - List materials = (List) entry.getValue().get("materials"); - for (MaterialContext materialContext : materials) { - blenderContext.addLinkedFeature(linkedDataFilePath, "MA" + materialContext.getName(), materialContext); - } - List textures = (List) entry.getValue().get("textures"); - for (Texture texture : textures) { - blenderContext.addLinkedFeature(linkedDataFilePath, "TE" + texture.getName(), texture); - } - List images = (List) entry.getValue().get("images"); - for (Texture image : images) { - blenderContext.addLinkedFeature(linkedDataFilePath, "IM" + image.getName(), image); - } - List animations = (List) entry.getValue().get("animations"); - for (Animation animation : animations) { - blenderContext.addLinkedFeature(linkedDataFilePath, "AC" + animation.getName(), animation); - } - List cameras = (List) entry.getValue().get("cameras"); - for (Camera camera : cameras) { - blenderContext.addLinkedFeature(linkedDataFilePath, "CA" + camera.getName(), camera); - } - List lights = (List) entry.getValue().get("lights"); - for (Light light : lights) { - blenderContext.addLinkedFeature(linkedDataFilePath, "LA" + light.getName(), light); - } - Spatial sky = (Spatial) entry.getValue().get("sky"); - if (sky != null) { - blenderContext.addLinkedFeature(linkedDataFilePath, sky.getName(), sky); - } - List filters = (List) entry.getValue().get("filters"); - for (Filter filter : filters) { - blenderContext.addLinkedFeature(linkedDataFilePath, filter.getName(), filter); - } + blenderContext.getLinkedFeatures().put(linkedDataFilePath, entry.getValue()); } } else { LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path); diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java index 6e8042b09..58114cf28 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java @@ -44,8 +44,11 @@ import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; import com.jme3.asset.AssetManager; import com.jme3.asset.BlenderKey; +import com.jme3.light.Light; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.post.Filter; +import com.jme3.renderer.Camera; import com.jme3.scene.Node; import com.jme3.scene.plugins.blender.animations.BlenderAction; import com.jme3.scene.plugins.blender.animations.BoneContext; @@ -55,6 +58,8 @@ import com.jme3.scene.plugins.blender.file.DnaBlockData; import com.jme3.scene.plugins.blender.file.FileBlockHeader; import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; import com.jme3.scene.plugins.blender.file.Structure; +import com.jme3.scene.plugins.blender.materials.MaterialContext; +import com.jme3.texture.Texture; /** * The class that stores temporary data and manages it during loading the belnd @@ -77,7 +82,7 @@ public class BlenderContext { /** The asset manager. */ private AssetManager assetManager; /** The blocks read from the file. */ - protected List blocks; + protected List blocks = new ArrayList(); /** * A map containing the file block headers. The key is the old memory address. */ @@ -233,6 +238,7 @@ public class BlenderContext { * the block header to store */ public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { + blocks.add(fileBlockHeader); fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); List headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode()); if (headers == null) { @@ -242,6 +248,13 @@ public class BlenderContext { headers.add(fileBlockHeader); } + /** + * @return the block headers + */ + public List getBlocks() { + return blocks; + } + /** * This method returns the block header of a given memory address. If the * header is not present then null is returned. @@ -332,22 +345,14 @@ public class BlenderContext { * The method adds linked content to the blender context. * @param blenderFilePath * the path of linked blender file - * @param featureName - * the linked feature name + * @param featureGroup + * the linked feature group (ie. scenes, materials, meshes, etc.) * @param feature * the linked feature */ - public void addLinkedFeature(String blenderFilePath, String featureName, Object feature) { - if (feature != null) { - Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); - if (linkedFeatures == null) { - linkedFeatures = new HashMap(); - this.linkedFeatures.put(blenderFilePath, linkedFeatures); - } - if (!linkedFeatures.containsKey(featureName)) { - linkedFeatures.put(featureName, feature); - } - } + @Deprecated + public void addLinkedFeature(String blenderFilePath, String featureGroup, Object feature) { + // the method is deprecated and empty at the moment } /** @@ -358,9 +363,106 @@ public class BlenderContext { * the feature name we want to get * @return linked feature or null if none was found */ + @SuppressWarnings("unchecked") public Object getLinkedFeature(String blenderFilePath, String featureName) { Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); - return linkedFeatures != null ? linkedFeatures.get(featureName) : null; + if(linkedFeatures != null) { + String namePrefix = (featureName.charAt(0) + "" + featureName.charAt(1)).toUpperCase(); + featureName = featureName.substring(2); + + if("SC".equals(namePrefix)) { + List scenes = (List) linkedFeatures.get("scenes"); + if(scenes != null) { + for(Node scene : scenes) { + if(featureName.equals(scene.getName())) { + return scene; + } + } + } + } else if("OB".equals(namePrefix)) { + List features = (List) linkedFeatures.get("objects"); + if(features != null) { + for(Node feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("ME".equals(namePrefix)) { + List features = (List) linkedFeatures.get("meshes"); + if(features != null) { + for(Node feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("MA".equals(namePrefix)) { + List features = (List) linkedFeatures.get("materials"); + if(features != null) { + for(MaterialContext feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("TX".equals(namePrefix)) { + List features = (List) linkedFeatures.get("textures"); + if(features != null) { + for(Texture feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("IM".equals(namePrefix)) { + List features = (List) linkedFeatures.get("images"); + if(features != null) { + for(Texture feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("AC".equals(namePrefix)) { + List features = (List) linkedFeatures.get("animations"); + if(features != null) { + for(Animation feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("CA".equals(namePrefix)) { + List features = (List) linkedFeatures.get("cameras"); + if(features != null) { + for(Camera feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("LA".equals(namePrefix)) { + List features = (List) linkedFeatures.get("lights"); + if(features != null) { + for(Light feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } else if("FI".equals(featureName)) { + List features = (List) linkedFeatures.get("lights"); + if(features != null) { + for(Filter feature : features) { + if(featureName.equals(feature.getName())) { + return feature; + } + } + } + } + } + return null; } /** @@ -659,4 +761,9 @@ public class BlenderContext { public static enum LoadedDataType { STRUCTURE, FEATURE, TEMPORAL_MESH; } + + @Override + public String toString() { + return blenderKey == null ? "BlenderContext [key = null]" : "BlenderContext [ key = " + blenderKey.toString() + " ]"; + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java index dd0160063..f519cb9b5 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java @@ -31,6 +31,9 @@ */ package com.jme3.scene.plugins.blender; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -41,9 +44,13 @@ import java.util.logging.Logger; import com.jme3.animation.Animation; import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetLocator; +import com.jme3.asset.AssetManager; import com.jme3.asset.BlenderKey; import com.jme3.asset.ModelKey; +import com.jme3.asset.StreamAssetInfo; import com.jme3.light.Light; import com.jme3.math.ColorRGBA; import com.jme3.post.Filter; @@ -81,22 +88,17 @@ import com.jme3.texture.Texture; public class BlenderLoader implements AssetLoader { private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); - /** The blocks read from the file. */ - protected List blocks; - /** The blender context. */ - protected BlenderContext blenderContext; - @Override public Spatial load(AssetInfo assetInfo) throws IOException { try { - this.setup(assetInfo); + BlenderContext blenderContext = this.setup(assetInfo); AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); animationHelper.loadAnimations(); BlenderKey blenderKey = blenderContext.getBlenderKey(); LoadedFeatures loadedFeatures = new LoadedFeatures(); - for (FileBlockHeader block : blocks) { + for (FileBlockHeader block : blenderContext.getBlocks()) { switch (block.getCode()) { case BLOCK_OB00: ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); @@ -181,7 +183,7 @@ public class BlenderLoader implements AssetLoader { LOGGER.fine("Loading scenes and attaching them to the root object."); for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) { - loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext))); + loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext), blenderContext)); } LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it."); @@ -220,7 +222,7 @@ public class BlenderLoader implements AssetLoader { } catch (Exception e) { throw new IOException("Unexpected importer exception occured: " + e.getLocalizedMessage(), e); } finally { - this.clear(); + this.clear(assetInfo); } } @@ -228,11 +230,12 @@ public class BlenderLoader implements AssetLoader { * This method converts the given structure to a scene node. * @param structure * structure of a scene + * @param blenderContext the blender context * @return scene's node * @throws BlenderFileException * an exception throw when problems with blender file occur */ - private Node toScene(Structure structure) throws BlenderFileException { + private Node toScene(Structure structure, BlenderContext blenderContext) throws BlenderFileException { ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); Node result = new Node(structure.getName()); List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); @@ -265,7 +268,7 @@ public class BlenderLoader implements AssetLoader { * @throws BlenderFileException * an exception is throw when something wrong happens with blender file */ - protected void setup(AssetInfo assetInfo) throws BlenderFileException { + protected BlenderContext setup(AssetInfo assetInfo) throws BlenderFileException { // registering loaders ModelKey modelKey = (ModelKey) assetInfo.getKey(); BlenderKey blenderKey; @@ -273,16 +276,15 @@ public class BlenderLoader implements AssetLoader { blenderKey = (BlenderKey) modelKey; } else { blenderKey = new BlenderKey(modelKey.getName()); - blenderKey.setAssetRootPath(modelKey.getFolder()); } // opening stream BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); // reading blocks - blocks = new ArrayList(); + List blocks = new ArrayList(); FileBlockHeader fileBlock; - blenderContext = new BlenderContext(); + BlenderContext blenderContext = new BlenderContext(); blenderContext.setBlenderVersion(inputStream.getVersionNumber()); blenderContext.setAssetManager(assetInfo.getManager()); blenderContext.setInputStream(inputStream); @@ -317,15 +319,19 @@ public class BlenderLoader implements AssetLoader { if (sceneFileBlock != null) { blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); } + + // adding locator for linked content + assetInfo.getManager().registerLocator(assetInfo.getKey().getName(), LinkedContentLocator.class); + + return blenderContext; } /** * The internal data is only needed during loading so make it unreachable so that the GC can release * that memory (which can be quite large amount). */ - protected void clear() { - blenderContext = null; - blocks = null; + protected void clear(AssetInfo assetInfo) { + assetInfo.getManager().unregisterLocator(assetInfo.getKey().getName(), LinkedContentLocator.class); } /** @@ -362,4 +368,50 @@ public class BlenderLoader implements AssetLoader { */ private ColorRGBA backgroundColor = ColorRGBA.Gray; } + + public static class LinkedContentLocator implements AssetLocator { + private File rootFolder; + + @Override + public void setRootPath(String rootPath) { + rootFolder = new File(rootPath); + if(rootFolder.isFile()) { + rootFolder = rootFolder.getParentFile(); + } + } + + @SuppressWarnings("rawtypes") + @Override + public AssetInfo locate(AssetManager manager, AssetKey key) { + if(key instanceof BlenderKey) { + File linkedAbsoluteFile = new File(key.getName()); + if(linkedAbsoluteFile.exists() && linkedAbsoluteFile.isFile()) { + try { + return new StreamAssetInfo(manager, key, new FileInputStream(linkedAbsoluteFile)); + } catch (FileNotFoundException e) { + return null; + } + } + + File linkedFileInCurrentAssetFolder = new File(rootFolder, linkedAbsoluteFile.getName()); + if(linkedFileInCurrentAssetFolder.exists() && linkedFileInCurrentAssetFolder.isFile()) { + try { + return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentAssetFolder)); + } catch (FileNotFoundException e) { + return null; + } + } + + File linkedFileInCurrentFolder = new File(".", linkedAbsoluteFile.getName()); + if(linkedFileInCurrentFolder.exists() && linkedFileInCurrentFolder.isFile()) { + try { + return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentFolder)); + } catch (FileNotFoundException e) { + return null; + } + } + } + return null; + } + } } diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java index 8466d5cce..32160c13a 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java @@ -81,6 +81,7 @@ public class LandscapeHelper extends AbstractBlenderHelper { if ((mode & MODE_MIST) != 0) { LOGGER.fine("Loading fog."); result = new FogFilter(); + result.setName("FIfog"); result.setFogColor(this.toBackgroundColor(worldStructure)); } return result; From 1559dacdcabdcb23ab1b0523b8dbce5931cf49c9 Mon Sep 17 00:00:00 2001 From: Julien Gouesse Date: Fri, 30 Oct 2015 21:12:25 +0100 Subject: [PATCH 02/37] Updates JOGL (2.3.2) --- jme3-jogl/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-jogl/build.gradle b/jme3-jogl/build.gradle index e82139729..df24c15ec 100644 --- a/jme3-jogl/build.gradle +++ b/jme3-jogl/build.gradle @@ -5,7 +5,7 @@ if (!hasProperty('mainClass')) { dependencies { compile project(':jme3-core') compile project(':jme3-desktop') - compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.1' - compile 'org.jogamp.jogl:jogl-all-main:2.3.1' - compile 'org.jogamp.joal:joal-main:2.3.1' + compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.2' + compile 'org.jogamp.jogl:jogl-all-main:2.3.2' + compile 'org.jogamp.joal:joal-main:2.3.2' } From f38ea1e3e2ef053f5e09e72a6f9caf0dd7477d99 Mon Sep 17 00:00:00 2001 From: Julien Gouesse Date: Fri, 30 Oct 2015 21:46:25 +0100 Subject: [PATCH 03/37] Displays the JOGL version instead of the NEWT version --- .../main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java index cd2b84f73..56aa2d53f 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java @@ -37,16 +37,18 @@ import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; -import com.jogamp.newt.NewtVersion; + import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; + import com.jogamp.opengl.GL; import com.jogamp.opengl.GLCapabilities; import com.jogamp.opengl.GLContext; import com.jogamp.opengl.GLDrawableFactory; import com.jogamp.opengl.GLOffscreenAutoDrawable; import com.jogamp.opengl.GLProfile; +import com.jogamp.opengl.JoglVersion; public class JoglOffscreenBuffer extends JoglContext implements Runnable { @@ -123,7 +125,7 @@ public class JoglOffscreenBuffer extends JoglContext implements Runnable { @Override public void run(){ loadNatives(); - logger.log(Level.FINE, "Using JOGL {0}", NewtVersion.getInstance().getImplementationVersion()); + logger.log(Level.FINE, "Using JOGL {0}", JoglVersion.getInstance().getImplementationVersion()); initInThread(); while (!needClose.get()){ runLoop(); From ce86a3e555e3c75d36b6e0465cb3a832c530b5f3 Mon Sep 17 00:00:00 2001 From: Julien Gouesse Date: Fri, 30 Oct 2015 21:47:52 +0100 Subject: [PATCH 04/37] Allows to choose between the forward compatible profile and the backward compatible profile in the JOGL backend --- .../src/main/java/com/jme3/system/AppSettings.java | 13 +++++++++++-- .../com/jme3/system/jogl/JoglAbstractDisplay.java | 11 ++++++++--- .../main/java/com/jme3/system/jogl/JoglContext.java | 2 +- .../jme3/system/jogl/JoglNewtAbstractDisplay.java | 11 +++++++---- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/jme3-core/src/main/java/com/jme3/system/AppSettings.java b/jme3-core/src/main/java/com/jme3/system/AppSettings.java index c98845e41..2d7071c9c 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -112,13 +112,22 @@ public final class AppSettings extends HashMap { public static final String ANDROID_OPENAL_SOFT = "OpenAL_SOFT"; /** - * Use JogAmp's JOGL as the display system + * Use JogAmp's JOGL as the display system, with the OpenGL forward compatible profile *

* N.B: This backend is EXPERIMENTAL * * @see AppSettings#setRenderer(java.lang.String) */ - public static final String JOGL = "JOGL"; + public static final String JOGL_OPENGL_FORWARD_COMPATIBLE = "JOGL_OPENGL_FORWARD_COMPATIBLE"; + + /** + * Use JogAmp's JOGL as the display system with the backward compatible profile + *

+ * N.B: This backend is EXPERIMENTAL + * + * @see AppSettings#setRenderer(java.lang.String) + */ + public static final String JOGL_OPENGL_BACKWARD_COMPATIBLE = "JOGL_OPENGL_BACKWARD_COMPATIBLE"; /** * Use JogAmp's JOAL as the display system diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java index 7b3f2e655..3b468f1aa 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java @@ -37,6 +37,7 @@ import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.awt.AwtKeyInput; import com.jme3.input.awt.AwtMouseInput; +import com.jme3.system.AppSettings; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; import com.jogamp.opengl.util.FPSAnimator; @@ -80,9 +81,13 @@ public abstract class JoglAbstractDisplay extends JoglContext implements GLEvent device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - //FIXME use the settings to know whether to use the max programmable profile - //then call GLProfile.getMaxProgrammable(true); - GLCapabilities caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + GLCapabilities caps; + if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { + caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); + } else { + caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + } + caps.setHardwareAccelerated(true); caps.setDoubleBuffered(true); caps.setStencilBits(settings.getStencilBits()); diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java index 25ad4fc8e..497fa8edb 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java @@ -167,7 +167,7 @@ public abstract class JoglContext implements JmeContext { "required for jMonkeyEngine"); } - if (settings.getRenderer().equals("JOGL")) { + if (settings.getRenderer().startsWith("JOGL")) { com.jme3.renderer.opengl.GL gl = new JoglGL(); GLExt glext = new JoglGLExt(); GLFbo glfbo = new JoglGLFbo(); diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java index 4ac6f5c49..9c7a49d17 100644 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java +++ b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java @@ -37,6 +37,7 @@ import com.jme3.input.MouseInput; import com.jme3.input.TouchInput; import com.jme3.input.jogl.NewtKeyInput; import com.jme3.input.jogl.NewtMouseInput; +import com.jme3.system.AppSettings; import com.jogamp.newt.opengl.GLWindow; import com.jogamp.opengl.util.Animator; import com.jogamp.opengl.util.AnimatorBase; @@ -73,10 +74,12 @@ public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLE protected void initGLCanvas() { loadNatives(); - //FIXME use the settings to know whether to use the max programmable profile - //then call GLProfile.getMaxProgrammable(true); - //FIXME use the default profile only on embedded devices - GLCapabilities caps = new GLCapabilities(GLProfile.getDefault()); + GLCapabilities caps; + if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { + caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); + } else { + caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); + } caps.setHardwareAccelerated(true); caps.setDoubleBuffered(true); caps.setStencilBits(settings.getStencilBits()); From ea28e8a4491982d7d0927f00423fdd1bb4fa0bf7 Mon Sep 17 00:00:00 2001 From: Julien Gouesse Date: Sat, 31 Oct 2015 11:00:17 +0100 Subject: [PATCH 05/37] First attempt to fix a bug reported by david_bernard_31, the size of the strings in the shader code was wrongly computed for the JOGL backend --- .../src/main/java/com/jme3/renderer/jogl/JoglGL.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java index e1c2a2505..3da2ed49e 100644 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java +++ b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java @@ -532,6 +532,15 @@ public class JoglGL implements GL, GL2, GL3, GL4 { @Override public void glShaderSource(int param1, String[] param2, IntBuffer param3) { checkLimit(param3); + + int param3pos = param3.position(); + try { + for (final String param2string : param2) { + param3.put(Math.max(param2string.length(), param2string.getBytes().length)); + } + } finally { + param3.position(param3pos); + } GLContext.getCurrentGL().getGL2ES2().glShaderSource(param1, param2.length, param2, param3); } From 836bf856303dff607fd5700961c5b2766780842f Mon Sep 17 00:00:00 2001 From: David Bernard Date: Sat, 7 Nov 2015 15:22:04 +0100 Subject: [PATCH 06/37] jogl: some fixes about mouse handler * Y hotspot is top in jogl, and bottom in jme * in jme when mouse is invisible it should be confined into the window (on mac it could go out of the window) * when window doesn't has the focus the mouse should not behave like when has the focus like * trying to re-center * stay invisible --- .../com/jme3/input/jogl/NewtMouseInput.java | 79 +++++++++++++------ 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java index 773f62b8a..3649b4ba1 100644 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java @@ -50,6 +50,8 @@ import com.jogamp.nativewindow.util.DimensionImmutable; import com.jogamp.nativewindow.util.PixelFormat; import com.jogamp.nativewindow.util.PixelRectangle; import com.jogamp.nativewindow.util.Point; +import com.jogamp.newt.event.WindowAdapter; +import com.jogamp.newt.event.WindowEvent; public class NewtMouseInput implements MouseInput, MouseListener { @@ -106,40 +108,67 @@ public class NewtMouseInput implements MouseInput, MouseListener { component = comp; component.addMouseListener(this); + component.addWindowListener(new WindowAdapter(){ + + @Override + public void windowGainedFocus(WindowEvent e) { + setCursorVisible(visible); + } + + @Override + public void windowLostFocus(WindowEvent e) { + //without those lines, + //on Linux (OpenBox) the mouse is not restored if invisible (eg via Alt-Tab) + component.setPointerVisible(true); + component.confinePointer(false); + } + + }); } + @Override public void initialize() { } + @Override public void destroy() { } + @Override public boolean isInitialized() { return true; } + @Override public void setInputListener(RawInputListener listener) { this.listener = listener; } + @Override public long getInputTimeNanos() { return System.nanoTime(); } + @Override public void setCursorVisible(boolean visible) { - if (this.visible != visible) { - lastKnownLocation.setX(0); - lastKnownLocation.setY(0); - - this.visible = visible; - component.setPointerVisible(visible); - if (!visible) { - recenterMouse(component); - } - } + lastKnownLocation.setX(0); + lastKnownLocation.setY(0); + + this.visible = visible; + component.setPointerVisible(visible); + component.confinePointer(!visible); + hack_confinePointer(); } + private void hack_confinePointer() { + if (component.hasFocus() && component.isPointerConfined() && !component.isPointerVisible()) { + recenterMouse(component); + } + } + + @Override public void update() { + if (!component.hasFocus()) return; if (cursorMoved) { int newX = location.getX(); int newY = location.getY(); @@ -173,15 +202,18 @@ public class NewtMouseInput implements MouseInput, MouseListener { } } + @Override public int getButtonCount() { return 3; } + @Override public void mouseClicked(MouseEvent awtEvt) { // MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); // listener.onMouseButtonEvent(evt); } + @Override public void mousePressed(MouseEvent newtEvt) { MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(newtEvt), true, newtEvt.getX(), newtEvt.getY()); evt.setTime(newtEvt.getWhen()); @@ -190,7 +222,8 @@ public class NewtMouseInput implements MouseInput, MouseListener { } } - public void mouseReleased(MouseEvent awtEvt) { + @Override + public void mouseReleased(MouseEvent awtEvt) { MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), awtEvt.getY()); evt.setTime(awtEvt.getWhen()); synchronized (eventQueue) { @@ -198,18 +231,17 @@ public class NewtMouseInput implements MouseInput, MouseListener { } } + @Override public void mouseEntered(MouseEvent awtEvt) { - if (!visible) { - recenterMouse(component); - } + hack_confinePointer(); } + @Override public void mouseExited(MouseEvent awtEvt) { - if (!visible) { - recenterMouse(component); - } + hack_confinePointer(); } + @Override public void mouseWheelMoved(MouseEvent awtEvt) { //FIXME not sure this is the right way to handle this case // [0] should be used when the shift key is down @@ -218,10 +250,12 @@ public class NewtMouseInput implements MouseInput, MouseListener { cursorMoved = true; } + @Override public void mouseDragged(MouseEvent awtEvt) { mouseMoved(awtEvt); } + @Override public void mouseMoved(MouseEvent awtEvt) { if (isRecentering) { // MHenze (cylab) Fix Issue 35: @@ -239,16 +273,14 @@ public class NewtMouseInput implements MouseInput, MouseListener { int dy = awtEvt.getY() - lastKnownLocation.getY(); location.setX(location.getX() + dx); location.setY(location.getY() + dy); - if (!visible) { - recenterMouse(component); - } + hack_confinePointer(); lastKnownLocation.setX(awtEvt.getX()); lastKnownLocation.setY(awtEvt.getY()); cursorMoved = true; } } - + // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse private void recenterMouse(final GLWindow component) { eventsSinceRecenter = 0; @@ -287,12 +319,13 @@ public class NewtMouseInput implements MouseInput, MouseListener { return index; } + @Override public void setNativeCursor(JmeCursor cursor) { final ByteBuffer pixels = Buffers.copyIntBufferAsByteBuffer(cursor.getImagesData()); final DimensionImmutable size = new Dimension(cursor.getWidth(), cursor.getHeight()); final PixelFormat pixFormat = PixelFormat.RGBA8888; final PixelRectangle.GenericPixelRect rec = new PixelRectangle.GenericPixelRect(pixFormat, size, 0, true, pixels); - final PointerIcon joglCursor = component.getScreen().getDisplay().createPointerIcon(rec, cursor.getXHotSpot(), cursor.getYHotSpot()); + final PointerIcon joglCursor = component.getScreen().getDisplay().createPointerIcon(rec, cursor.getXHotSpot(), cursor.getHeight() - cursor.getYHotSpot()); component.setPointerIcon(joglCursor); } -} \ No newline at end of file +} From 539e49e109fcbbeeea7c0473f7fb1c2748e6b153 Mon Sep 17 00:00:00 2001 From: Pesegato Date: Mon, 9 Nov 2015 14:31:52 +0100 Subject: [PATCH 07/37] Added support to Logitech F310 For both DirectInput and XInput mode. Also tentatively added support of alternate version of xbox360 controller. --- .../resources/joystick-mapping.properties | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index 966832036..b4f1b77bd 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/jme3-core/src/main/resources/joystick-mapping.properties @@ -69,3 +69,47 @@ Microsoft\ PC-joystick\ driver.12=POV +Y Microsoft\ PC-joystick\ driver.13=POV +X Microsoft\ PC-joystick\ driver.14=POV -Y Microsoft\ PC-joystick\ driver.15=POV -X + +# Logitech F310 gamepad with dip switch DirectInput +Logitech\ Dual\ Action.1=2 +Logitech\ Dual\ Action.2=1 +Logitech\ Dual\ Action.3=0 +Logitech\ Dual\ Action.0=3 + +# Logitech F310 gamepad with dip switch XInput +Gamepad\ F310\ (Controller).0=2 +Gamepad\ F310\ (Controller).1=1 +Gamepad\ F310\ (Controller).2=3 +Gamepad\ F310\ (Controller).3=0 + +Gamepad\ F310\ (Controller).6=8 +Gamepad\ F310\ (Controller).7=9 + +Gamepad\ F310\ (Controller).8=10 +Gamepad\ F310\ (Controller).9=11 + +Gamepad\ F310\ (Controller).rx=z +Gamepad\ F310\ (Controller).ry=rz + +# requires custom code to support trigger buttons but this +# keeps it from confusing the .rx mapping. +Gamepad\ F310\ (Controller).z=trigger + +# Alternate version of the XBOX 360 controller +XBOX\ 360\ For\ Windows\ (Controller).0=2 +XBOX\ 360\ For\ Windows\ (Controller).1=1 +XBOX\ 360\ For\ Windows\ (Controller).2=3 +XBOX\ 360\ For\ Windows\ (Controller).3=0 + +XBOX\ 360\ For\ Windows\ (Controller).6=8 +XBOX\ 360\ For\ Windows\ (Controller).7=9 + +XBOX\ 360\ For\ Windows\ (Controller).8=10 +XBOX\ 360\ For\ Windows\ (Controller).9=11 + +XBOX\ 360\ For\ Windows\ (Controller).rx=z +XBOX\ 360\ For\ Windows\ (Controller).ry=rz + +# requires custom code to support trigger buttons but this +# keeps it from confusing the .rx mapping. +XBOX\ 360\ For\ Windows\ (Controller).z=trigger From 2938acec5127680b77f4ff6b0358d9f19ee8a54e Mon Sep 17 00:00:00 2001 From: David Bernard Date: Mon, 9 Nov 2015 21:19:59 +0100 Subject: [PATCH 08/37] opengl3: add GLSLCompat into Gui.j3md --- .../main/resources/Common/MatDefs/Gui/Gui.frag | 2 ++ .../main/resources/Common/MatDefs/Gui/Gui.j3md | 15 +++++++++++++-- .../main/resources/Common/MatDefs/Gui/Gui.vert | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag index 619682ce0..4a4b433de 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.frag @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + #ifdef TEXTURE uniform sampler2D m_Texture; varying vec2 texCoord; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md index f37f6c979..23fbff826 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md @@ -7,8 +7,8 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert - FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag + VertexShader GLSL150: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL150: Common/MatDefs/Gui/Gui.frag WorldParameters { WorldViewProjectionMatrix @@ -21,6 +21,17 @@ MaterialDef Default GUI { } Technique { + VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + Defines { + TEXTURE : Texture + VERTEX_COLOR : VertexColor + } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert index 7640573dc..f0c50f5be 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + uniform mat4 g_WorldViewProjectionMatrix; uniform vec4 m_Color; From a80051c1ceeb54b84da53b324c243ada64574f6b Mon Sep 17 00:00:00 2001 From: jmekaelthas Date: Mon, 9 Nov 2015 22:17:39 +0100 Subject: [PATCH 09/37] Bugfix: very small wight values caused severe animation artifacts. --- .../scene/plugins/blender/meshes/TemporalMesh.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java index d4ef0d1bc..da20d3fb4 100644 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java +++ b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java @@ -46,7 +46,9 @@ import com.jme3.scene.plugins.blender.objects.Properties; */ public class TemporalMesh extends Geometry { private static final Logger LOGGER = Logger.getLogger(TemporalMesh.class.getName()); - + /** A minimum weight value. */ + private static final double MINIMUM_BONE_WEIGHT = FastMath.DBL_EPSILON; + /** The blender context. */ protected final BlenderContext blenderContext; @@ -530,7 +532,11 @@ public class TemporalMesh extends Geometry { for (Entry entry : boneIndexes.entrySet()) { if (vertexGroupsForVertex.containsKey(entry.getKey())) { float weight = vertexGroupsForVertex.get(entry.getKey()); - if (weight > 0) {// no need to use such weights + if (weight > MINIMUM_BONE_WEIGHT) { + // only values of weight greater than MINIMUM_BONE_WEIGHT are used + // if all non zero weights were used, and they were samm enough, problems with normalisation would occur + // because adding a very small value to 1.0 will give 1.0 + // so in order to avoid such errors, which can cause severe animation artifacts we need to use some minimum weight value boneBuffersForVertex.put(weight, entry.getValue()); } } From 00e2717c7c131e61bddf0d88e274a71736a856bb Mon Sep 17 00:00:00 2001 From: Daniel Johansson Date: Tue, 10 Nov 2015 17:03:02 +0000 Subject: [PATCH 10/37] Fixed an issue where on linux using jme-lwjgl3 module would cause the windows dll to be loaded instead of the linux so native library. --- .../src/main/java/com/jme3/system/NativeLibraryLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java index 32bd681c5..707937e78 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/system/NativeLibraryLoader.java @@ -149,7 +149,7 @@ public final class NativeLibraryLoader { registerNativeLibrary("glfw-lwjgl3", Platform.Windows32, "native/windows/glfw32.dll"); registerNativeLibrary("glfw-lwjgl3", Platform.Windows64, "native/windows/glfw.dll"); registerNativeLibrary("glfw-lwjgl3", Platform.Linux32, "native/linux/libglfw32.so"); - registerNativeLibrary("glfw-lwjgl3", Platform.Linux64, "native/linux/libglfw.dll"); + registerNativeLibrary("glfw-lwjgl3", Platform.Linux64, "native/linux/libglfw.so"); registerNativeLibrary("glfw-lwjgl3", Platform.MacOSX32, "native/macosx/libglfw.dylib"); registerNativeLibrary("glfw-lwjgl3", Platform.MacOSX64, "native/macosx/libglfw.dylib"); From 40177c15247f9e2b5bbf24f94997356e8b618a0b Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 11 Nov 2015 18:56:30 +0100 Subject: [PATCH 11/37] Added jme3-lwjgl3/build/ to the gitignore file.... again.. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5e7951c48..8baaf9fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,4 @@ /sdk/nbi/stub/ext/components/products/jdk/build/ /sdk/nbi/stub/ext/components/products/jdk/dist/ /sdk/jme3-dark-laf/nbproject/private/ +jme3-lwjgl3/build/ From 5cb72c6582158b6019fc72796034402a3f0ddd3d Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 11 Nov 2015 18:21:21 +0100 Subject: [PATCH 12/37] Updated the create skybox gui in the SDK so that it fits the new API and supports loading equirect Skybox maps. --- .../terraineditor/sky/AddSkyboxAction.java | 8 ++- .../gde/terraineditor/sky/Bundle.properties | 2 +- .../terraineditor/sky/SkyboxVisualPanel2.form | 54 +++++++++++----- .../terraineditor/sky/SkyboxVisualPanel2.java | 63 ++++++++++++++----- .../terraineditor/sky/SkyboxWizardPanel2.java | 3 +- 5 files changed, 91 insertions(+), 39 deletions(-) diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java index b92aed679..146f1eb17 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/AddSkyboxAction.java @@ -69,14 +69,16 @@ public class AddSkyboxAction extends AbstractNewSpatialWizardAction { } else { Texture textureSingle = (Texture) wiz.getProperty("textureSingle"); Vector3f normalScale = (Vector3f) wiz.getProperty("normalScale"); - boolean useSpheremap = (Boolean) wiz.getProperty("useSpheremap"); + SkyFactory.EnvMapType type = (SkyFactory.EnvMapType) wiz.getProperty("envMapType"); boolean flipY = (Boolean) wiz.getProperty("flipY"); // reload the texture so we can use flipY TextureKey key = (TextureKey) textureSingle.getKey(); TextureKey newKey = new TextureKey(key.getName(), flipY); newKey.setGenerateMips(true); - newKey.setAsCube(!useSpheremap); - return SkyFactory.createSky(pm, pm.loadTexture(newKey), normalScale, useSpheremap); + if(type == SkyFactory.EnvMapType.CubeMap){ + newKey.setTextureTypeHint(Texture.Type.CubeMap); + } + return SkyFactory.createSky(pm, pm.loadTexture(newKey), normalScale, type); } } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties index 794a01ca7..550d79478 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/Bundle.properties @@ -17,7 +17,6 @@ SkyboxVisualPanel2.multipleTexWestLoadButton.text=Load SkyboxVisualPanel2.multipleTexTopLoadButton.text=Load SkyboxVisualPanel2.multipleTexBottomLoadButton.text=Load SkyboxVisualPanel2.jLabel9.text=Normal Scale (x,y,z): -SkyboxVisualPanel2.spheremapCheckBox.text=Sphere map SkyboxVisualPanel2.normal1X.text=1 SkyboxVisualPanel2.normal1Y.text=1 SkyboxVisualPanel2.normal1Z.text=1 @@ -32,3 +31,4 @@ SkyboxVisualPanel2.topPic.text= SkyboxVisualPanel2.bottomPic.text= SkyboxVisualPanel2.singlePic.text= SkyboxVisualPanel2.flipYcheckBox.text=Flip Y +SkyboxVisualPanel2.jLabel10.text=Map type diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form index 291361d1e..26439c888 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.form @@ -1,4 +1,4 @@ - +

@@ -127,7 +127,7 @@ - + @@ -172,7 +172,7 @@ - + @@ -376,11 +376,17 @@ + - + + + + + + - + @@ -391,12 +397,8 @@ - - - - - + @@ -420,7 +422,10 @@ - + + + + @@ -474,24 +479,39 @@ - + - + - + - + - + + + + + + + + + + + + + + + + - + diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java index 7abe313dd..754f5e933 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java @@ -36,9 +36,13 @@ import com.jme3.gde.core.properties.TexturePropertyEditor; import com.jme3.gde.core.properties.preview.DDSPreview; import com.jme3.gde.core.scene.SceneApplication; import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; import java.awt.Component; +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; import javax.swing.Icon; import javax.swing.JCheckBox; +import javax.swing.JComboBox; import javax.swing.JPanel; import javax.swing.JTextField; import jme3tools.converters.ImageToAwt; @@ -51,6 +55,12 @@ public final class SkyboxVisualPanel2 extends JPanel { /** Creates new form SkyboxVisualPanel2 */ public SkyboxVisualPanel2() { initComponents(); + + DefaultComboBoxModel model = new DefaultComboBoxModel(); + for (SkyFactory.EnvMapType value : SkyFactory.EnvMapType.values()) { + model.addElement(value); + } + mapTypeCombo.setModel(model); } @Override @@ -140,11 +150,12 @@ public final class SkyboxVisualPanel2 extends JPanel { normal2X = new javax.swing.JTextField(); normal2Y = new javax.swing.JTextField(); normal2Z = new javax.swing.JTextField(); - spheremapCheckBox = new javax.swing.JCheckBox(); singlePic = new javax.swing.JLabel(); flipYcheckBox = new javax.swing.JCheckBox(); + mapTypeCombo = new javax.swing.JComboBox(); + jLabel10 = new javax.swing.JLabel(); - titleLabel.setFont(new java.awt.Font("Tahoma", 1, 14)); + titleLabel.setFont(new java.awt.Font("Tahoma", 1, 14)); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(titleLabel, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.titleLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.jLabel1.text")); // NOI18N @@ -279,7 +290,7 @@ public final class SkyboxVisualPanel2 extends JPanel { .addComponent(multipleTexTopLoadButton) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(topPic, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE))))) - .addContainerGap(27, Short.MAX_VALUE)) + .addContainerGap(29, Short.MAX_VALUE)) ); multipleTexturePanelLayout.setVerticalGroup( multipleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -315,7 +326,7 @@ public final class SkyboxVisualPanel2 extends JPanel { .addGroup(multipleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) .addComponent(jLabel5) .addComponent(multipleTexTopLoadButton)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) .addGroup(multipleTexturePanelLayout.createSequentialGroup() .addComponent(westPic, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) @@ -346,12 +357,19 @@ public final class SkyboxVisualPanel2 extends JPanel { normal2Z.setText(org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.normal2Z.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(spheremapCheckBox, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.spheremapCheckBox.text")); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(singlePic, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.singlePic.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(flipYcheckBox, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.flipYcheckBox.text")); // NOI18N + mapTypeCombo.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" })); + mapTypeCombo.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + mapTypeComboActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(jLabel10, org.openide.util.NbBundle.getMessage(SkyboxVisualPanel2.class, "SkyboxVisualPanel2.jLabel10.text")); // NOI18N + javax.swing.GroupLayout singleTexturePanelLayout = new javax.swing.GroupLayout(singleTexturePanel); singleTexturePanel.setLayout(singleTexturePanelLayout); singleTexturePanelLayout.setHorizontalGroup( @@ -359,11 +377,16 @@ public final class SkyboxVisualPanel2 extends JPanel { .addGroup(singleTexturePanelLayout.createSequentialGroup() .addContainerGap() .addGroup(singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(flipYcheckBox, javax.swing.GroupLayout.PREFERRED_SIZE, 100, javax.swing.GroupLayout.PREFERRED_SIZE) .addGroup(singleTexturePanelLayout.createSequentialGroup() - .addComponent(jLabel8) + .addComponent(jLabel10) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addComponent(mapTypeCombo, javax.swing.GroupLayout.PREFERRED_SIZE, 166, javax.swing.GroupLayout.PREFERRED_SIZE)) + .addGroup(singleTexturePanelLayout.createSequentialGroup() + .addComponent(jLabel8) + .addGap(21, 21, 21) .addComponent(singleTexLoadButton) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGap(2, 2, 2) .addComponent(singlePic, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(39, 39, 39) .addComponent(jLabel9) @@ -372,11 +395,8 @@ public final class SkyboxVisualPanel2 extends JPanel { .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(normal2Y, javax.swing.GroupLayout.PREFERRED_SIZE, 20, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(6, 6, 6) - .addComponent(normal2Z, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false) - .addComponent(flipYcheckBox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) - .addComponent(spheremapCheckBox, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))) - .addContainerGap(31, Short.MAX_VALUE)) + .addComponent(normal2Z, javax.swing.GroupLayout.PREFERRED_SIZE, 21, javax.swing.GroupLayout.PREFERRED_SIZE))) + .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); singleTexturePanelLayout.setVerticalGroup( singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -394,7 +414,9 @@ public final class SkyboxVisualPanel2 extends JPanel { .addComponent(normal2Z, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addComponent(jLabel9)))) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(spheremapCheckBox) + .addGroup(singleTexturePanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(mapTypeCombo, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(jLabel10)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) .addComponent(flipYcheckBox) .addContainerGap(75, Short.MAX_VALUE)) @@ -565,11 +587,17 @@ public final class SkyboxVisualPanel2 extends JPanel { } } }//GEN-LAST:event_singleTexLoadButtonActionPerformed + + private void mapTypeComboActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_mapTypeComboActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_mapTypeComboActionPerformed + // Variables declaration - do not modify//GEN-BEGIN:variables private javax.swing.JLabel bottomPic; private javax.swing.JLabel eastPic; private javax.swing.JCheckBox flipYcheckBox; private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel10; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; @@ -578,6 +606,7 @@ public final class SkyboxVisualPanel2 extends JPanel { private javax.swing.JLabel jLabel7; private javax.swing.JLabel jLabel8; private javax.swing.JLabel jLabel9; + private javax.swing.JComboBox mapTypeCombo; private javax.swing.JButton multipleTexBottomLoadButton; private javax.swing.JButton multipleTexEastLoadButton; private javax.swing.JButton multipleTexNorthLoadButton; @@ -596,7 +625,6 @@ public final class SkyboxVisualPanel2 extends JPanel { private javax.swing.JButton singleTexLoadButton; private javax.swing.JPanel singleTexturePanel; private javax.swing.JLabel southPic; - private javax.swing.JCheckBox spheremapCheckBox; private javax.swing.JLabel titleLabel; private javax.swing.JLabel topPic; private javax.swing.JLabel westPic; @@ -626,10 +654,11 @@ public final class SkyboxVisualPanel2 extends JPanel { return normal2Z; } - public JCheckBox getSpheremapCheckBox() { - return spheremapCheckBox; + public SkyFactory.EnvMapType getEnvMapType(){ + return (SkyFactory.EnvMapType)mapTypeCombo.getSelectedItem(); } + public JCheckBox getFlipYCheckBox() { return flipYcheckBox; } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java index 67ab8e0f7..267751ab4 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxWizardPanel2.java @@ -123,6 +123,7 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { } } + @Override public void storeSettings(Object settings) { WizardDescriptor wiz = (WizardDescriptor) settings; SkyboxVisualPanel2 comp = (SkyboxVisualPanel2) getComponent(); @@ -143,7 +144,7 @@ public class SkyboxWizardPanel2 implements WizardDescriptor.Panel { float y = new Float(comp.getNormal2Y().getText()); float z = new Float(comp.getNormal2Z().getText()); wiz.putProperty("normalScale", new Vector3f(x,y,z) ); - wiz.putProperty("useSpheremap", comp.getSpheremapCheckBox().isSelected()); + wiz.putProperty("envMapType", comp.getEnvMapType()); wiz.putProperty("flipY", comp.getFlipYCheckBox().isSelected()); } } From cbe87cf41fd0442fdaa586f27c66b5dec33983c0 Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 11 Nov 2015 19:02:23 +0100 Subject: [PATCH 13/37] checkGLError in LwjglOffscreenBuffer is now called only if assertions are on, as it is done in LwjglAbstractDisplay --- .../main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index b06db1b29..ddfb8b62b 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -123,7 +123,7 @@ public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { } listener.update(); - checkGLError(); + assert checkGLError(); renderer.postFrame(); From 3e7119861051f03c6df5a29c4b88432ee99e181f Mon Sep 17 00:00:00 2001 From: David Bernard Date: Wed, 11 Nov 2015 19:58:23 +0100 Subject: [PATCH 14/37] jogl: use pixel unit for window's dimension (fix issue on MaxOS X) --- .../src/main/java/com/jme3/input/jogl/NewtMouseInput.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java index 3649b4ba1..8a345e0d6 100644 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java @@ -176,7 +176,7 @@ public class NewtMouseInput implements MouseInput, MouseListener { // invert DY int actualX = lastKnownLocation.getX(); - int actualY = component.getHeight() - lastKnownLocation.getY(); + int actualY = component.getSurfaceHeight() - lastKnownLocation.getY(); MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY, newX - lastEventX, lastEventY - newY, @@ -285,8 +285,8 @@ public class NewtMouseInput implements MouseInput, MouseListener { private void recenterMouse(final GLWindow component) { eventsSinceRecenter = 0; isRecentering = true; - centerLocation.setX(component.getWidth() / 2); - centerLocation.setY(component.getHeight() / 2); + centerLocation.setX(component.getSurfaceWidth() / 2); + centerLocation.setY(component.getSurfaceHeight() / 2); centerLocationOnScreen.setX(centerLocation.getX()); centerLocationOnScreen.setY(centerLocation.getY()); From 0f140370edbc16aeb4d9fe5f4e391f50645dc5cf Mon Sep 17 00:00:00 2001 From: David Bernard Date: Wed, 11 Nov 2015 22:48:00 +0100 Subject: [PATCH 15/37] jogl: flip y for mouse click --- .../src/main/java/com/jme3/input/jogl/NewtMouseInput.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java index 8a345e0d6..d7dd905fd 100644 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java +++ b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java @@ -215,7 +215,7 @@ public class NewtMouseInput implements MouseInput, MouseListener { @Override public void mousePressed(MouseEvent newtEvt) { - MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(newtEvt), true, newtEvt.getX(), newtEvt.getY()); + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(newtEvt), true, newtEvt.getX(), component.getSurfaceHeight() - newtEvt.getY()); evt.setTime(newtEvt.getWhen()); synchronized (eventQueue) { eventQueue.add(evt); @@ -224,7 +224,7 @@ public class NewtMouseInput implements MouseInput, MouseListener { @Override public void mouseReleased(MouseEvent awtEvt) { - MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), awtEvt.getY()); + MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), component.getSurfaceHeight() - awtEvt.getY()); evt.setTime(awtEvt.getWhen()); synchronized (eventQueue) { eventQueue.add(evt); From dc0bcb5d1366ccc0751580801e92fdd403292f74 Mon Sep 17 00:00:00 2001 From: Daniel Johansson Date: Thu, 12 Nov 2015 15:11:41 +0000 Subject: [PATCH 16/37] Resolves #378, adding support for detecting ARMv8 on Android. --- .../main/java/com/jme3/system/android/JmeAndroidSystem.java | 4 ++++ .../src/main/java/com/jme3/system/JmeSystemDelegate.java | 4 ++++ jme3-core/src/main/java/com/jme3/system/Platform.java | 5 +++++ 3 files changed, 13 insertions(+) diff --git a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java index bbbfaca7a..efaf4f3bb 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java +++ b/jme3-android/src/main/java/com/jme3/system/android/JmeAndroidSystem.java @@ -122,9 +122,13 @@ public class JmeAndroidSystem extends JmeSystemDelegate { return Platform.Android_ARM6; } else if (arch.contains("v7")) { return Platform.Android_ARM7; + } else if (arch.contains("v8")) { + return Platform.Android_ARM8; } else { return Platform.Android_ARM5; // unknown ARM } + } else if (arch.contains("aarch")) { + return Platform.Android_ARM8; } else { return Platform.Android_Other; } diff --git a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java index 150275d46..2134e0a7e 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -153,6 +153,10 @@ public abstract class JmeSystemDelegate { return false; } else if (arch.equals("universal")) { return false; + } else if (arch.equals("aarch32")) { + return false; + } else if (arch.equals("aarch64")) { + return true; } else if (arch.equals("arm")) { return false; } else { diff --git a/jme3-core/src/main/java/com/jme3/system/Platform.java b/jme3-core/src/main/java/com/jme3/system/Platform.java index 8a30fff0c..f9206d33f 100644 --- a/jme3-core/src/main/java/com/jme3/system/Platform.java +++ b/jme3-core/src/main/java/com/jme3/system/Platform.java @@ -88,6 +88,11 @@ public enum Platform { */ Android_ARM7, + /** + * Android ARM8 + */ + Android_ARM8, + /** * Android x86 */ From 9e80d8a7aa55b18cdc0035b8a8ab6a822d42a8b6 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 01:20:28 -0500 Subject: [PATCH 17/37] Beefing up the client server tests a little to add some listeners and to better report what's going on to the console. This is in prep for making a combined test. --- .../java/jme3test/network/TestChatClient.java | 47 ++++++++++++++--- .../java/jme3test/network/TestChatServer.java | 50 +++++++++++++++---- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index fddb047c4..219ccd58f 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -32,6 +32,7 @@ package jme3test.network; import com.jme3.network.Client; +import com.jme3.network.ClientStateListener; import com.jme3.network.Message; import com.jme3.network.MessageListener; import com.jme3.network.Network; @@ -51,11 +52,11 @@ import jme3test.network.TestChatServer.ChatMessage; */ public class TestChatClient extends JFrame { - private Client client; - private JEditorPane chatLog; - private StringBuilder chatMessages = new StringBuilder(); - private JTextField nameField; - private JTextField messageField; + private final Client client; + private final JEditorPane chatLog; + private final StringBuilder chatMessages = new StringBuilder(); + private final JTextField nameField; + private final JTextField messageField; public TestChatClient(String host) throws IOException { super("jME3 Test Chat Client - to:" + host); @@ -90,9 +91,17 @@ public class TestChatClient extends JFrame { client = Network.connectToServer(TestChatServer.NAME, TestChatServer.VERSION, host, TestChatServer.PORT, TestChatServer.UDP_PORT); client.addMessageListener(new ChatHandler(), ChatMessage.class); + client.addClientStateListener(new ChatClientStateListener()); client.start(); } + @Override + public void dispose() { + System.out.println("Chat window closing."); + super.dispose(); + client.close(); + } + public static String getString(Component owner, String title, String message, String initialValue) { return (String) JOptionPane.showInputDialog(owner, message, title, JOptionPane.PLAIN_MESSAGE, null, null, initialValue); @@ -108,12 +117,23 @@ public class TestChatClient extends JFrame { return; } + // Register a shutdown hook to get a message on the console when the + // app actually finishes + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + System.out.println("Chat client is terminating."); + } + }); + + TestChatClient test = new TestChatClient(s); test.setVisible(true); } private class ChatHandler implements MessageListener { + @Override public void messageReceived(Client source, Message m) { ChatMessage chat = (ChatMessage) m; @@ -134,15 +154,30 @@ public class TestChatClient extends JFrame { } } + private class ChatClientStateListener implements ClientStateListener { + + @Override + public void clientConnected(Client c) { + System.out.println("clientConnected()"); + } + + @Override + public void clientDisconnected(Client c, DisconnectInfo info) { + System.out.println("clientDisconnected()"); + } + + } + private class SendAction extends AbstractAction { - private boolean reliable; + private final boolean reliable; public SendAction(boolean reliable) { super(reliable ? "TCP" : "UDP"); this.reliable = reliable; } + @Override public void actionPerformed(ActionEvent evt) { String name = nameField.getText(); String message = messageField.getText(); diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index a2aa2ee1c..cde870f26 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -34,6 +34,7 @@ package jme3test.network; import com.jme3.network.*; import com.jme3.network.serializing.Serializable; import com.jme3.network.serializing.Serializer; +import java.io.IOException; /** * A simple test chat server. When SM implements a set @@ -51,22 +52,37 @@ public class TestChatServer { public static final int PORT = 5110; public static final int UDP_PORT = 5110; - public static void initializeClasses() { - // Doing it here means that the client code only needs to - // call our initialize. - Serializer.registerClass(ChatMessage.class); - } - - public static void main(String... args) throws Exception { + private Server server; + + public TestChatServer() throws IOException { initializeClasses(); // Use this to test the client/server name version check - Server server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); - server.start(); + this.server = Network.createServer(NAME, VERSION, PORT, UDP_PORT); ChatHandler handler = new ChatHandler(); server.addMessageListener(handler, ChatMessage.class); + + server.addConnectionListener(new ChatConnectionListener()); + } + + public void start() { + server.start(); + } + public static void initializeClasses() { + // Doing it here means that the client code only needs to + // call our initialize. + Serializer.registerClass(ChatMessage.class); + } + + public static void main(String... args) throws Exception { + + TestChatServer chatServer = new TestChatServer(); + chatServer.start(); + + System.out.println("Waiting for connections on port:" + PORT); + // Keep running basically forever synchronized (NAME) { NAME.wait(); @@ -78,6 +94,7 @@ public class TestChatServer { public ChatHandler() { } + @Override public void messageReceived(HostedConnection source, Message m) { if (m instanceof ChatMessage) { // Keep track of the name just in case we @@ -96,6 +113,20 @@ public class TestChatServer { } } + private static class ChatConnectionListener implements ConnectionListener { + + @Override + public void connectionAdded( Server server, HostedConnection conn ) { + System.out.println("connectionAdded(" + conn + ")"); + } + + @Override + public void connectionRemoved(Server server, HostedConnection conn) { + System.out.println("connectionRemoved(" + conn + ")"); + } + + } + @Serializable public static class ChatMessage extends AbstractMessage { @@ -126,6 +157,7 @@ public class TestChatServer { return message; } + @Override public String toString() { return name + ":" + message; } From 38fe771ed80d66d89c1cce94775bcae18c22a279 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 01:22:17 -0500 Subject: [PATCH 18/37] Modified the client state messages to include the chat instance in case we add a multi-client test. --- .../src/main/java/jme3test/network/TestChatClient.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index 219ccd58f..0cdc46c39 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -93,6 +93,8 @@ public class TestChatClient extends JFrame { client.addMessageListener(new ChatHandler(), ChatMessage.class); client.addClientStateListener(new ChatClientStateListener()); client.start(); + + System.out.println("Started client:" + client); } @Override @@ -158,12 +160,12 @@ public class TestChatClient extends JFrame { @Override public void clientConnected(Client c) { - System.out.println("clientConnected()"); + System.out.println("clientConnected(" + c + ")"); } @Override public void clientDisconnected(Client c, DisconnectInfo info) { - System.out.println("clientDisconnected()"); + System.out.println("clientDisconnected(" + c + ")"); } } From cea36ffc4753752ca135f2a036b2e3f174cf6c3d Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 01:53:32 -0500 Subject: [PATCH 19/37] Added some more client-side error and connection handling to make a more complete example. It should now be relatively well behaved through all normal shutdown paths. Modified the server to gracefully close the client connections when shutting down rather than just letting the sockets die. --- .../java/jme3test/network/TestChatClient.java | 28 ++++++++- .../java/jme3test/network/TestChatServer.java | 63 ++++++++++++++++--- 2 files changed, 82 insertions(+), 9 deletions(-) diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index 0cdc46c39..e8af4329a 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -33,6 +33,7 @@ package jme3test.network; import com.jme3.network.Client; import com.jme3.network.ClientStateListener; +import com.jme3.network.ErrorListener; import com.jme3.network.Message; import com.jme3.network.MessageListener; import com.jme3.network.Network; @@ -92,6 +93,7 @@ public class TestChatClient extends JFrame { host, TestChatServer.PORT, TestChatServer.UDP_PORT); client.addMessageListener(new ChatHandler(), ChatMessage.class); client.addClientStateListener(new ChatClientStateListener()); + client.addErrorListener(new ChatErrorListener()); client.start(); System.out.println("Started client:" + client); @@ -101,7 +103,9 @@ public class TestChatClient extends JFrame { public void dispose() { System.out.println("Chat window closing."); super.dispose(); - client.close(); + if( client.isConnected() ) { + client.close(); + } } public static String getString(Component owner, String title, String message, String initialValue) { @@ -165,7 +169,27 @@ public class TestChatClient extends JFrame { @Override public void clientDisconnected(Client c, DisconnectInfo info) { - System.out.println("clientDisconnected(" + c + ")"); + System.out.println("clientDisconnected(" + c + "):" + info); + if( info != null ) { + // The connection was closed by the server + JOptionPane.showMessageDialog(rootPane, + info.reason, + "Connection Closed", + JOptionPane.INFORMATION_MESSAGE); + dispose(); + } + } + } + + private class ChatErrorListener implements ErrorListener { + + @Override + public void handleError( Client source, Throwable t ) { + System.out.println("handleError(" + source + ", " + t + ")"); + JOptionPane.showMessageDialog(rootPane, + String.valueOf(t), + "Connection Error", + JOptionPane.ERROR_MESSAGE); } } diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index cde870f26..0f9021152 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -53,6 +53,7 @@ public class TestChatServer { public static final int UDP_PORT = 5110; private Server server; + private boolean isRunning; public TestChatServer() throws IOException { initializeClasses(); @@ -66,10 +67,49 @@ public class TestChatServer { server.addConnectionListener(new ChatConnectionListener()); } - public void start() { + public synchronized void start() { + if( isRunning ) { + return; + } server.start(); + isRunning = true; + } + + public synchronized void close() { + if( !isRunning ) { + return; + } + + // Gracefully let any connections know that the server is + // going down. Without this, their connections will simply + // error out. + for( HostedConnection conn : server.getConnections() ) { + conn.close("Server is shutting down."); + } + try { + Thread.sleep(1000); // wait a couple beats to let the messages go out + } catch( InterruptedException e ) { + e.printStackTrace(); + } + + server.close(); + isRunning = false; + notifyAll(); } + protected void runCommand( HostedConnection conn, String user, String command ) { + if( "/shutdown".equals(command) ) { + server.broadcast(new ChatMessage("server", "Server is shutting down.")); + close(); + } else if( "/help".equals(command) ) { + StringBuilder sb = new StringBuilder(); + sb.append("Chat commands:\n"); + sb.append("/help - prints this message.\n"); + sb.append("/shutdown - shuts down the server."); + server.broadcast(new ChatMessage("server", sb.toString())); + } + } + public static void initializeClasses() { // Doing it here means that the client code only needs to // call our initialize. @@ -84,12 +124,14 @@ public class TestChatServer { System.out.println("Waiting for connections on port:" + PORT); // Keep running basically forever - synchronized (NAME) { - NAME.wait(); + while( chatServer.isRunning ) { + synchronized (chatServer) { + chatServer.wait(); + } } } - private static class ChatHandler implements MessageListener { + private class ChatHandler implements MessageListener { public ChatHandler() { } @@ -100,20 +142,27 @@ public class TestChatServer { // Keep track of the name just in case we // want to know it for some other reason later and it's // a good example of session data - source.setAttribute("name", ((ChatMessage) m).getName()); + ChatMessage cm = (ChatMessage)m; + source.setAttribute("name", cm.getName()); + + // Check for a / command + if( cm.message.startsWith("/") ) { + runCommand(source, cm.name, cm.message); + return; + } System.out.println("Broadcasting:" + m + " reliable:" + m.isReliable()); // Just rebroadcast... the reliable flag will stay the // same so if it came in on UDP it will go out on that too - source.getServer().broadcast(m); + source.getServer().broadcast(cm); } else { System.err.println("Received odd message:" + m); } } } - private static class ChatConnectionListener implements ConnectionListener { + private class ChatConnectionListener implements ConnectionListener { @Override public void connectionAdded( Server server, HostedConnection conn ) { From 17df399f68bd6cc7586a1550ddc8073102933fbf Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 02:13:58 -0500 Subject: [PATCH 20/37] Commented out the message class serialization and left a comment as to why: in 3.1 there is a default service that automatically does this on clients. --- .../src/main/java/jme3test/network/TestChatClient.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index e8af4329a..a36853913 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -114,7 +114,12 @@ public class TestChatClient extends JFrame { } public static void main(String... args) throws Exception { - TestChatServer.initializeClasses(); + + // Note: in JME 3.1 this is generally unnecessary as the server will + // send a message with all server-registered classes. + // TestChatServer.initializeClasses(); + // Leaving the call commented out to be illustrative regarding the + // common old pattern. // Grab a host string from the user String s = getString(null, "Host Info", "Enter chat host:", "localhost"); From e832ad5c949facfd793b178c557de1e46ecc801f Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 02:14:26 -0500 Subject: [PATCH 21/37] Added an isRunning() method so that other classes can check if the server is still running. --- .../src/main/java/jme3test/network/TestChatServer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index 0f9021152..4d8642881 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -67,6 +67,10 @@ public class TestChatServer { server.addConnectionListener(new ChatConnectionListener()); } + public boolean isRunning() { + return isRunning; + } + public synchronized void start() { if( isRunning ) { return; From 2c337123a90138de119fb5e9cab4d2cf9aa4f120 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 02:15:30 -0500 Subject: [PATCH 22/37] Added a test/example of running a client and a server in the same JVM. a) this makes a good example of self-hosted style LAN multiplayer games, and b) it causes the serialization bug to show up so I can fix it. (Already fixed it and that commit will be next... it's almost like TDD.) --- .../network/TestChatClientAndServer.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java new file mode 100644 index 000000000..ee08067ae --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClientAndServer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015 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 jme3test.network; + + +/** + * Combines the server instance and a client instance into the + * same JVM to show an example of, and to test, a pattern like + * self-hosted multiplayer games. + * + * @author Paul Speed + */ +public class TestChatClientAndServer { + + public static void main( String... args ) throws Exception { + + System.out.println("Starting chat server..."); + TestChatServer chatServer = new TestChatServer(); + chatServer.start(); + + System.out.println("Waiting for connections on port:" + TestChatServer.PORT); + + // Now launch a client + + TestChatClient test = new TestChatClient("localhost"); + test.setVisible(true); + + // Register a shutdown hook to get a message on the console when the + // app actually finishes + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + System.out.println("Client and server test is terminating."); + } + }); + + // Keep running basically forever or until the server + // shuts down + while( chatServer.isRunning() ) { + synchronized (chatServer) { + chatServer.wait(); + } + } + } +} From 4b2f3610266b6057e43c88337a81fa0928019f85 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 02:22:53 -0500 Subject: [PATCH 23/37] Modified to skip registering the message classes if they are already registered. This avoids one of the issues of a client running in the same JVM as a server that already registered these classes. This was the easy fix. --- .../ClientSerializerRegistrationsService.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java index 911ce0fb1..be8dfc74a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -39,6 +39,8 @@ import com.jme3.network.message.SerializerRegistrationsMessage; import com.jme3.network.serializing.Serializer; import com.jme3.network.service.AbstractClientService; import com.jme3.network.service.ClientServiceManager; +import java.util.logging.Level; +import java.util.logging.Logger; /** @@ -48,14 +50,21 @@ import com.jme3.network.service.ClientServiceManager; */ public class ClientSerializerRegistrationsService extends AbstractClientService implements MessageListener { + + static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); @Override protected void onInitialize( ClientServiceManager serviceManager ) { - // Make sure our message type is registered - // This is the minimum we'd need just to be able to register - // the rest... otherwise we can't even receive this message. - Serializer.registerClass(SerializerRegistrationsMessage.class); - Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); + + // Make sure our message type is registered if it isn't already + if( Serializer.getSerializer(SerializerRegistrationsMessage.class, false) == null ) { + // This is the minimum we'd need just to be able to register + // the rest... otherwise we can't even receive this message. + Serializer.registerClass(SerializerRegistrationsMessage.class); + Serializer.registerClass(SerializerRegistrationsMessage.Registration.class); + } else { + log.log(Level.INFO, "Skipping registration of SerializerRegistrationsMessage."); + } // Add our listener for that message type serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class); From dfe4b083f0962266f77be913c67b2b1fb01daa2d Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 15 Nov 2015 02:24:18 -0500 Subject: [PATCH 24/37] Added a check to try and detect the case where a server and a client are running on the same instance. This should cover 99% of the cases where this would come up... and the others can't really use this service anyway and so must disable it. --- .../SerializerRegistrationsMessage.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java index 9c3896475..4d1decc08 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java @@ -94,7 +94,7 @@ public class SerializerRegistrationsMessage extends AbstractMessage { public static Registration[] compiled; private static final Serializer fieldSerializer = new FieldSerializer(); - private Registration[] registrations; + private Registration[] registrations; public SerializerRegistrationsMessage() { setReliable(true); @@ -132,7 +132,25 @@ public class SerializerRegistrationsMessage extends AbstractMessage { Serializer.setReadOnly(true); } - public void registerAll() { + public void registerAll() { + + // See if we will have problems because our registry is locked + if( Serializer.isReadOnly() ) { + // Check to see if maybe we are executing this from the + // same JVM that sent the message, ie: client and server are running on + // the same JVM + // There could be more advanced checks than this but for now we'll + // assume that if the registry was compiled here then it means + // we are also the server process. Note that this wouldn't hold true + // under complicated examples where there are clients of one server + // that also run their own servers but realistically they would have + // to disable the ServerSerializerRegistrationsServer anyway. + if( compiled != null ) { + log.log( Level.INFO, "Skipping registration as registry is locked, presumably by a local server process."); + return; + } + } + for( Registration reg : registrations ) { log.log( Level.INFO, "Registering:{0}", reg); reg.register(); From 95603c46c49a6a6dc564161bf2ce379ea980279c Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Mon, 16 Nov 2015 02:03:27 -0500 Subject: [PATCH 25/37] Added a better check. The old one had the side-effect of registering the class if it wasn't already registered. --- .../serializer/ClientSerializerRegistrationsService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java index be8dfc74a..61632282a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -55,9 +55,9 @@ public class ClientSerializerRegistrationsService extends AbstractClientService @Override protected void onInitialize( ClientServiceManager serviceManager ) { - + // Make sure our message type is registered if it isn't already - if( Serializer.getSerializer(SerializerRegistrationsMessage.class, false) == null ) { + if( Serializer.getExactSerializerRegistration(SerializerRegistrationsMessage.class) == null ) { // This is the minimum we'd need just to be able to register // the rest... otherwise we can't even receive this message. Serializer.registerClass(SerializerRegistrationsMessage.class); @@ -70,6 +70,7 @@ public class ClientSerializerRegistrationsService extends AbstractClientService serviceManager.getClient().addMessageListener(this, SerializerRegistrationsMessage.class); } + @Override public void messageReceived( Client source, Message m ) { // We only wait for one kind of message... SerializerRegistrationsMessage msg = (SerializerRegistrationsMessage)m; From 95aa2d72d046ca2e48ae4b3614ea3e6685b6d0d3 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Mon, 16 Nov 2015 02:04:31 -0500 Subject: [PATCH 26/37] Added a basic RMI service that uses the RPC service. I'll add some more javadoc in a sec. --- .../network/service/rmi/Asynchronous.java | 54 +++ .../jme3/network/service/rmi/CallType.java | 43 +++ .../jme3/network/service/rmi/ClassInfo.java | 111 ++++++ .../service/rmi/ClassInfoRegistry.java | 103 ++++++ .../jme3/network/service/rmi/MethodInfo.java | 136 +++++++ .../service/rmi/RemoteObjectHandler.java | 87 +++++ .../network/service/rmi/RmiClientService.java | 147 ++++++++ .../jme3/network/service/rmi/RmiContext.java | 56 +++ .../network/service/rmi/RmiHostedService.java | 203 +++++++++++ .../jme3/network/service/rmi/RmiRegistry.java | 344 ++++++++++++++++++ 10 files changed, 1284 insertions(+) create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java create mode 100644 jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java new file mode 100644 index 000000000..8deada70b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + + +/** + * Indicates that a given method should be executed asynchronously + * through the RMI service. This must annotate the method on the + * shared interface for it to have an effect. + * + * @author Paul Speed + */ +@Retention(value=RUNTIME) +@Target(value=METHOD) +public @interface Asynchronous { + boolean reliable() default true; +} + + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java new file mode 100644 index 000000000..6b218891d --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + + +/** + * + * + * @author Paul Speed + */ +public enum CallType { + Synchronous, Asynchronous, Unreliable +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java new file mode 100644 index 000000000..88d91a686 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import com.jme3.network.serializing.Serializable; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + + +/** + * + * + * @author Paul Speed + */ +@Serializable +public final class ClassInfo { + private String name; + private short typeId; + private MethodInfo[] methods; + + /** + * For serialization only. + */ + public ClassInfo() { + } + + public ClassInfo( short typeId, Class type ) { + this.typeId = typeId; + this.name = type.getName(); + this.methods = toMethodInfo(type, type.getMethods()); + } + + public String getName() { + return name; + } + + public Class getType() { + try { + return Class.forName(name); + } catch( ClassNotFoundException e ) { + throw new RuntimeException("Error finding class for:" + this, e); + } + } + + public short getId() { + return typeId; + } + + public MethodInfo getMethod( short id ) { + return methods[id]; + } + + public MethodInfo getMethod( Method m ) { + for( MethodInfo mi : methods ) { + if( mi.matches(m) ) { + return mi; + } + } + return null; + } + + private MethodInfo[] toMethodInfo( Class type, Method[] methods ) { + List result = new ArrayList(); + short methodId = 0; + for( Method m : methods ) { + // Simple... add all methods exposed through the interface + result.add(new MethodInfo(methodId++, m)); + } + return result.toArray(new MethodInfo[result.size()]); + } + + public MethodInfo[] getMethods() { + return methods; + } + + @Override + public String toString() { + return "ClassInfo[" + name + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java new file mode 100644 index 000000000..557948042 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +/** + * + * + * @author Paul Speed + */ +public class ClassInfoRegistry { + + //private final LoadingCache cache; // Guava version + private final Map cache = new HashMap(); + private final AtomicInteger nextClassId = new AtomicInteger(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public ClassInfoRegistry() { + //this.cache = CacheBuilder.newBuilder().build(new ClassInfoLoader()); // Guava version + } + + public ClassInfo getClassInfo( Class type ) { + //return cache.getUnchecked(type); // Guava version + + // More complicated without guava + lock.readLock().lock(); + try { + ClassInfo result = cache.get(type); + if( result != null ) { + return result; + } + // Else we need to create it and store it... so grab the write + // lock + lock.readLock().unlock(); + lock.writeLock().lock(); + try { + // Note: it's technically possible that a race with another thread + // asking for the same class already created one between our read unlock + // and our write lock. No matter as it's cheap to create one and does + // no harm. Code is simpler without the double-check. + result = new ClassInfo((short)nextClassId.getAndIncrement(), type); + cache.put(type, result); + + // Regrab the read lock before leaving... kind of unnecessary but + // it makes the method cleaner and widens the gap of lock races. + // Downgrading a write lock to read is ok. + lock.readLock().lock(); + + return result; + } finally { + // Unlock the write lock while still holding onto read + lock.writeLock().unlock(); + } + } finally { + lock.readLock().unlock(); + } + } + + /* + would be more straight-forward with guava Guava version + private class ClassInfoLoader extends CacheLoader { + @Override + public ClassInfo load( Class type ) { + return new ClassInfo((short)nextClassId.getAndIncrement(), type); + } + }*/ +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java new file mode 100644 index 000000000..a0138556d --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import com.jme3.network.serializing.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import javax.jws.Oneway; + + +/** + * + * + * @author Paul Speed + */ +@Serializable +public final class MethodInfo { + + public static final MethodInfo NULL_INFO = new MethodInfo(); + + private String representation; + private short id; + private CallType callType; + private transient Method method; + + /** + * For serialization only. + */ + public MethodInfo() { + } + + public MethodInfo( short id, Method m ) { + this.id = id; + this.method = m; + this.representation = methodToString(m); + this.callType = getCallType(m); + } + + public Object invoke( Object target, Object... parms ) { + try { + return method.invoke(target, parms); + } catch (IllegalAccessException e) { + throw new RuntimeException("Error invoking:" + method + " on:" + target, e); + } catch (IllegalArgumentException e) { + throw new RuntimeException("Error invoking:" + method + " on:" + target, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Error invoking:" + method + " on:" + target, e); + } + } + + public short getId() { + return id; + } + + public CallType getCallType() { + return callType; + } + + public boolean matches( Method m ) { + return representation.equals(methodToString(m)); + } + + public static String methodToString( Method m ) { + StringBuilder sb = new StringBuilder(); + for( Class t : m.getParameterTypes() ) { + if( sb.length() > 0 ) + sb.append(", "); + sb.append(t.getName()); + } + return m.getReturnType().getName() + " " + m.getName() + "(" + sb + ")"; + } + + public static CallType getCallType( Method m ) { + if( m.getReturnType() != Void.TYPE ) + return CallType.Synchronous; + if( m.getAnnotation(Oneway.class) != null ) + return CallType.Asynchronous; + if( m.getAnnotation(Asynchronous.class) == null ) + return CallType.Synchronous; + + Asynchronous async = m.getAnnotation(Asynchronous.class); + return async.reliable() ? CallType.Asynchronous : CallType.Unreliable; + } + + @Override + public int hashCode() { + return representation.hashCode(); + } + + @Override + public boolean equals( Object o ) { + if( o == this ) { + return true; + } + if( o == null || o.getClass() != getClass() ) { + return false; + } + MethodInfo other = (MethodInfo)o; + return representation.equals(other.representation); + } + + @Override + public String toString() { + return "MethodInfo[#" + getId() + ", callType=" + callType + ", " + representation + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java new file mode 100644 index 000000000..03309a4fa --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + + +/** + * + * + * @author Paul Speed + */ +public class RemoteObjectHandler implements InvocationHandler { + + private final RmiRegistry rmi; + private final byte channel; + private final short objectId; + private final ClassInfo typeInfo; + private final Map methodIndex = new ConcurrentHashMap(); + + public RemoteObjectHandler( RmiRegistry rmi, byte channel, short objectId, ClassInfo typeInfo ) { + this.rmi = rmi; + this.channel = channel; + this.objectId = objectId; + this.typeInfo = typeInfo; + } + + protected MethodInfo getMethodInfo( Method method ) { + MethodInfo mi = methodIndex.get(method); + if( mi == null ) { + mi = typeInfo.getMethod(method); + if( mi == null ) { + mi = MethodInfo.NULL_INFO; + } + methodIndex.put(method, mi); + } + return mi == MethodInfo.NULL_INFO ? null : mi; + } + + @Override + public Object invoke(Object o, Method method, Object[] os) throws Throwable { + MethodInfo mi = getMethodInfo(method); + if( mi == null ) { + // Try to invoke locally + return method.invoke(this, os); + } + return rmi.invokeRemote(channel, objectId, mi.getId(), mi.getCallType(), os); + } + + @Override + public String toString() { + return "RemoteObject[#" + objectId + ", " + typeInfo.getName() + "]"; + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java new file mode 100644 index 000000000..437a697f2 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import com.jme3.network.MessageConnection; +import com.jme3.network.service.AbstractClientService; +import com.jme3.network.service.ClientServiceManager; +import com.jme3.network.service.rpc.RpcClientService; +import java.util.ArrayList; +import java.util.List; + + +/** + * + * + * @author Paul Speed + */ +public class RmiClientService extends AbstractClientService { + + private RpcClientService rpc; + private byte defaultChannel; + private short rmiObjectId; + private RmiRegistry rmi; + private volatile boolean isStarted = false; + + private final List pending = new ArrayList(); + + public RmiClientService() { + this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE); + } + + public RmiClientService( short rmiObjectId, byte defaultChannel ) { + this.defaultChannel = defaultChannel; + this.rmiObjectId = rmiObjectId; + } + + public void share( T object, Class type ) { + share(defaultChannel, object, type); + } + + public void share( byte channel, T object, Class type ) { + share(channel, type.getName(), object, type); + } + + public void share( String name, T object, Class type ) { + share(defaultChannel, name, object, type); + } + + public void share( byte channel, String name, T object, Class type ) { + if( !isStarted ) { + synchronized(pending) { + if( !isStarted ) { + pending.add(new ObjectInfo(channel, name, object, type)); + return; + } + } + } + + // Else we can add it directly. + rmi.share(channel, name, object, type); + } + + public T getRemoteObject( Class type ) { + return rmi.getRemoteObject(type); + } + + public T getRemoteObject( String name, Class type ) { + return rmi.getRemoteObject(name, type); + } + + @Override + protected void onInitialize( ClientServiceManager s ) { + rpc = getService(RpcClientService.class); + if( rpc == null ) { + throw new RuntimeException("RmiClientService requires RpcClientService"); + } + + // Register it now so that it is available when the + // server starts to send us stuff. Waiting until start() + // is too late in this case. + rmi = new RmiRegistry(rpc.getRpcConnection(), rmiObjectId, defaultChannel); + } + + @Override + public void start() { + super.start(); + + // Register all of the classes that have been waiting. + synchronized(pending) { + for( ObjectInfo info : pending ) { + rmi.share(info.channel, info.name, info.object, info.type); + } + pending.clear(); + isStarted = true; + } + } + + private class ObjectInfo { + byte channel; + String name; + Object object; + Class type; + + public ObjectInfo( byte channel, String name, Object object, Class type ) { + this.channel = channel; + this.name = name; + this.object = object; + this.type = type; + } + + @Override + public String toString() { + return "ObjectInfo[" + channel + ", " + name + ", " + object + ", " + type + "]"; + } + } +} + diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java new file mode 100644 index 000000000..47d115d5b --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import com.jme3.network.HostedConnection; + + +/** + * Keeps track of the current connection performing a particular + * RMI call. RMI-based services can use this to find out which + * connection is calling a particular method without having to + * pass additional problematic data on the method calls. + * + * @author Paul Speed + */ +public class RmiContext { + private static final ThreadLocal connection = new ThreadLocal(); + + public static HostedConnection getRmiConnection() { + return connection.get(); + } + + static void setRmiConnection( HostedConnection conn ) { + connection.set(conn); + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java new file mode 100644 index 000000000..647fe4fb6 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java @@ -0,0 +1,203 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import com.jme3.network.HostedConnection; +import com.jme3.network.MessageConnection; +import com.jme3.network.Server; +import com.jme3.network.serializing.Serializer; +import com.jme3.network.service.AbstractHostedService; +import com.jme3.network.service.HostedServiceManager; +import com.jme3.network.service.rpc.RpcHostedService; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * + * + * @author Paul Speed + */ +public class RmiHostedService extends AbstractHostedService { + + static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); + + public static final String ATTRIBUTE_NAME = "rmi"; + + private RpcHostedService rpcService; + private short rmiId; + private byte defaultChannel; + private boolean autoHost; + private final Map globalShares = new ConcurrentHashMap(); + + public RmiHostedService() { + this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true); + } + + public RmiHostedService( short rmiId, byte defaultChannel, boolean autoHost ) { + this.rmiId = rmiId; + this.defaultChannel = defaultChannel; + this.autoHost = autoHost; + + Serializer.registerClasses(ClassInfo.class, MethodInfo.class); + } + + public void shareGlobal( T object, Class type ) { + shareGlobal(defaultChannel, type.getName(), object, type); + } + + public void shareGlobal( String name, T object, Class type ) { + shareGlobal(defaultChannel, name, object, type); + } + + public void shareGlobal( byte channel, String name, T object, Class type ) { + GlobalShare share = new GlobalShare(channel, object, type); + GlobalShare existing = globalShares.put(name, share); + if( existing != null ) { + // Shouldn't need to do anything actually. + } + + // Go through all of the children + for( HostedConnection conn : getServer().getConnections() ) { + RmiRegistry child = getRmiRegistry(conn); + if( child == null ) { + continue; + } + child.share(channel, name, object, type); + } + } + + public void setAutoHost( boolean b ) { + this.autoHost = b; + } + + public boolean getAutoHost() { + return autoHost; + } + + public RmiRegistry getRmiRegistry( HostedConnection hc ) { + return hc.getAttribute(ATTRIBUTE_NAME); + } + + /** + * Sets up RMI hosting services for the hosted connection allowing + * getRmiRegistry() to return a valid RmiRegistry object. + * This method is called automatically for all new connections if + * autohost is set to true. + */ + public void startHostingOnConnection( HostedConnection hc ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "startHostingOnConnection:{0}", hc); + } + RmiRegistry rmi = new RmiRegistry(hc, rpcService.getRpcConnection(hc), + rmiId, defaultChannel); + hc.setAttribute(ATTRIBUTE_NAME, rmi); + + // Register any global shares + for( Map.Entry e : globalShares.entrySet() ) { + GlobalShare share = e.getValue(); + rmi.share(share.channel, e.getKey(), share.object, share.type); + } + } + + /** + * Removes any RMI hosting services associated with the specified + * connection. Calls to getRmiRegistry() will return null for + * this connection. + * This method is called automatically for all leaving connections if + * autohost is set to true. + */ + public void stopHostingOnConnection( HostedConnection hc ) { + RmiRegistry rmi = hc.getAttribute(ATTRIBUTE_NAME); + if( rmi == null ) { + return; + } + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "stopHostingOnConnection:{0}", hc); + } + hc.setAttribute(ATTRIBUTE_NAME, null); + //rpc.close(); + } + + @Override + protected void onInitialize( HostedServiceManager s ) { + this.rpcService = getService(RpcHostedService.class); + if( rpcService == null ) { + throw new RuntimeException("RmiHostedService requires RpcHostedService"); + } + } + + /** + * Called internally when a new connection is detected for + * the server. If the current autoHost property is true then + * startHostingOnConnection(hc) is called. + */ + @Override + public void connectionAdded(Server server, HostedConnection hc) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "connectionAdded({0}, {1})", new Object[]{server, hc}); + } + if( autoHost ) { + startHostingOnConnection(hc); + } + } + + /** + * Called internally when an existing connection is leaving + * the server. If the current autoHost property is true then + * stopHostingOnConnection(hc) is called. + */ + @Override + public void connectionRemoved(Server server, HostedConnection hc) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "connectionRemoved({0}, {1})", new Object[]{server, hc}); + } + if( autoHost ) { + stopHostingOnConnection(hc); + } + } + + private class GlobalShare { + byte channel; + Object object; + Class type; + + public GlobalShare( byte channel, Object object, Class type ) { + this.channel = channel; + this.object = object; + this.type = type; + } + } +} diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java new file mode 100644 index 000000000..6d1722307 --- /dev/null +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java @@ -0,0 +1,344 @@ +/* + * Copyright (c) 2015 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.network.service.rmi; + +import com.jme3.network.HostedConnection; +import com.jme3.network.MessageConnection; +import com.jme3.network.service.rpc.RpcConnection; +import com.jme3.network.service.rpc.RpcHandler; +import java.lang.reflect.Proxy; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/** + * + * + * @author Paul Speed + */ +public class RmiRegistry { + + static final Logger log = Logger.getLogger(RmiRegistry.class.getName()); + + // RPC IDs for calling our remote endpoint + private static final short NEW_CLASS = 0; + private static final short ADD_OBJECT = 1; + private static final short REMOVE_OBJECT = 2; + + private RpcConnection rpc; + private short rmiId; + private byte defaultChannel; + private final RmiHandler rmiHandler = new RmiHandler(); + private final ClassInfoRegistry classCache = new ClassInfoRegistry(); + private final AtomicInteger nextObjectId = new AtomicInteger(); + + private final ObjectIndex local = new ObjectIndex(); + private final ObjectIndex remote = new ObjectIndex(); + + // Only used on the server to provide thread-local context for + // local RMI calls. + private HostedConnection context; + + public RmiRegistry( RpcConnection rpc, short rmiId, byte defaultChannel ) { + this(null, rpc, rmiId, defaultChannel); + } + + public RmiRegistry( HostedConnection context, RpcConnection rpc, short rmiId, byte defaultChannel ) { + this.context = context; + this.rpc = rpc; + this.rmiId = rmiId; + this.defaultChannel = defaultChannel; + rpc.registerHandler(rmiId, rmiHandler); + } + + public void share( T object, Class type ) { + share(defaultChannel, object, type); + } + + public void share( byte channel, T object, Class type ) { + share(channel, type.getName(), object, type); + } + + public void share( String name, T object, Class type ) { + share(defaultChannel, name, object, type); + } + + public void share( byte channel, String name, T object, Class type ) { + + ClassInfo typeInfo = classCache.getClassInfo(type); + + local.lock.writeLock().lock(); + try { + + // First see if we've told the remote end about this class + // before + if( local.classes.put(typeInfo.getId(), typeInfo) == null ) { + // It's new + rpc.callAsync(defaultChannel, rmiId, NEW_CLASS, typeInfo); + + // Because type info IDs are global to the class cache, + // we could in theory keep a global index that we broadcast + // on first connection setup... we need only prepopulate + // the index in that case. + } + + // See if we already shared an object under that name + SharedObject existing = local.byName.remove(name); + if( existing != null ) { + local.byId.remove(existing.objectId); + rpc.removeHandler(existing.objectId, rmiHandler); + + // Need to delete the old one from the remote end + rpc.callAsync(defaultChannel, rmiId, REMOVE_OBJECT, existing.objectId); + + // We don't reuse the ID because it's kind of dangerous. + // Churning through a new ID is our safety net for accidents. + } + + SharedObject newShare = new SharedObject(name, object, type, typeInfo); + local.byName.put(name, newShare); + local.byId.put(newShare.objectId, newShare); + + // Make sure we are setup to receive the remote method calls through + // the RPC service + rpc.registerHandler(newShare.objectId, rmiHandler); + + // Let the other end know + rpc.callAsync(defaultChannel, rmiId, ADD_OBJECT, channel, newShare.objectId, name, typeInfo.getId()); + + // We send the ADD_OBJECT to the other end before releasing the + // lock to avoid a potential inconsistency if two threads try to + // jam the same name at the same time. Otherwise, if the timing were + // right, the remove for one object could get there before its add. + + } finally { + local.lock.writeLock().unlock(); + } + } + + /** + * Returns an object that was previously registered with share(). + */ + public T getSharedObject( Class type ) { + return getSharedObject(type.getName(), type); + } + + /** + * Returns an object that was previously registered with share(). + */ + public T getSharedObject( String name, Class type ) { + local.lock.readLock().lock(); + try { + return type.cast(local.byName.get(name)); + } finally { + local.lock.readLock().unlock(); + } + } + + public T getRemoteObject( Class type ) { + return getRemoteObject(type.getName(), type); + } + + public T getRemoteObject( String name, Class type ) { + remote.lock.readLock().lock(); + try { + return type.cast(remote.byName.get(name)); + } finally { + remote.lock.readLock().unlock(); + } + } + + protected void addRemoteClass( ClassInfo info ) { + if( remote.classes.put(info.getId(), info) != null ) { + throw new RuntimeException("Error class already exists for ID:" + info.getId()); + } + } + + protected void removeRemoteObject( short objectId ) { + if( log.isLoggable(Level.FINEST) ) { + log.log(Level.FINEST, "removeRemoteObject({0})", objectId); + } + throw new UnsupportedOperationException("Removal not yet implemented."); + } + + protected void addRemoteObject( byte channel, short objectId, String name, ClassInfo typeInfo ) { + if( log.isLoggable(Level.FINEST) ) { + log.finest("addRemoveObject(" + objectId + ", " + name + ", " + typeInfo + ")"); + } + remote.lock.writeLock().lock(); + try { + Object existing = remote.byName.get(name); + if( existing != null ) { + throw new RuntimeException("Object already registered for:" + name); + } + + RemoteObjectHandler remoteHandler = new RemoteObjectHandler(this, channel, objectId, typeInfo); + + Object remoteObject = Proxy.newProxyInstance(getClass().getClassLoader(), + new Class[] {typeInfo.getType()}, + remoteHandler); + + remote.byName.put(name, remoteObject); + remote.byId.put(objectId, remoteObject); + } finally { + remote.lock.writeLock().unlock(); + } + } + + protected Object invokeRemote( byte channel, short objectId, short procId, CallType callType, Object[] args ) { + if( log.isLoggable(Level.FINEST) ) { + log.finest("invokeRemote(" + channel + ", " + objectId + ", " + procId + ", " + + callType + ", " + (args == null ? "null" : Arrays.asList(args)) + ")"); + } + switch( callType ) { + case Asynchronous: + log.finest("Sending reliable asynchronous."); + rpc.callAsync(channel, objectId, procId, args); + return null; + case Unreliable: + log.finest("Sending unreliable asynchronous."); + rpc.callAsync((byte)MessageConnection.CHANNEL_DEFAULT_UNRELIABLE, objectId, procId, args); + return null; + default: + case Synchronous: + log.finest("Sending synchronous."); + Object result = rpc.callAndWait(channel, objectId, procId, args); + if( log.isLoggable(Level.FINEST) ) { + log.finest("->got:" + result); + } + return result; + } + } + + /** + * Handle remote object registry updates from the other end. + */ + protected void rmiUpdate( short procId, Object[] args ) { + if( log.isLoggable(Level.FINEST) ) { + log.finest("rmiUpdate(" + procId + ", " + Arrays.asList(args) + ")"); + } + switch( procId ) { + case NEW_CLASS: + addRemoteClass((ClassInfo)args[0]); + break; + case REMOVE_OBJECT: + removeRemoteObject((Short)args[0]); + break; + case ADD_OBJECT: + ClassInfo info = remote.classes.get((Short)args[3]); + addRemoteObject((Byte)args[0], (Short)args[1], (String)args[2], info); + break; + } + } + + /** + * Handle the actual remote object method calls. + */ + protected Object invokeLocal( short objectId, short procId, Object[] args ) { + // Actually could use a regular concurrent map for this + + // Only lock the local registry during lookup and + // not invocation. It prevents a deadlock if the invoked method + // tries to share an object. It should be safe. + SharedObject share = local.byId.get(objectId); + local.lock.readLock().lock(); + try { + share = local.byId.get(objectId); + } finally { + local.lock.readLock().unlock(); + } + + try { + RmiContext.setRmiConnection(context); + return share.invoke(procId, args); + } finally { + RmiContext.setRmiConnection(null); + } + } + + private class SharedObject { + private final short objectId; + private final String name; + private final Object object; + private final Class type; + private final ClassInfo classInfo; + + public SharedObject( String name, Object object, Class type, ClassInfo classInfo ) { + this.objectId = (short)nextObjectId.incrementAndGet(); + this.name = name; + this.object = object; + this.type = type; + this.classInfo = classInfo; + } + + public Object invoke( short procId, Object[] args ) { + return classInfo.getMethod(procId).invoke(object, args); + } + } + + private class RmiHandler implements RpcHandler { + @Override + public Object call( RpcConnection conn, short objectId, short procId, Object... args ) { + if( objectId == rmiId ) { + rmiUpdate(procId, args); + return null; + } else { + return invokeLocal(objectId, procId, args); + } + } + } + + /** + * Keeps a coincident index between short ID, name, and related class info. + * There will be one of these to track our local objects and one to track + * the remote objects and a lock that can guard them. + */ + private class ObjectIndex { + final Map byName = new HashMap(); + final Map byId = new HashMap(); + final Map classes = new HashMap(); + final ReadWriteLock lock = new ReentrantReadWriteLock(); + + public ObjectIndex() { + } + } + +} + + From 3a4624a5fe4684d8df5260c4d5320a762c65b3f7 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Mon, 16 Nov 2015 02:38:45 -0500 Subject: [PATCH 27/37] Added a bunch of javadoc and changed the names of one of the method sets to be a little less confusing. --- .../network/service/rmi/Asynchronous.java | 4 +- .../jme3/network/service/rmi/CallType.java | 21 ++++++- .../jme3/network/service/rmi/ClassInfo.java | 3 +- .../service/rmi/ClassInfoRegistry.java | 3 +- .../jme3/network/service/rmi/MethodInfo.java | 3 +- .../service/rmi/RemoteObjectHandler.java | 2 +- .../network/service/rmi/RmiClientService.java | 48 +++++++++++++++ .../jme3/network/service/rmi/RmiContext.java | 6 +- .../network/service/rmi/RmiHostedService.java | 59 +++++++++++++++++++ .../jme3/network/service/rmi/RmiRegistry.java | 53 +++++++++++++++-- 10 files changed, 189 insertions(+), 13 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java index 8deada70b..a26d09635 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/Asynchronous.java @@ -41,7 +41,9 @@ import static java.lang.annotation.RetentionPolicy.*; /** * Indicates that a given method should be executed asynchronously * through the RMI service. This must annotate the method on the - * shared interface for it to have an effect. + * shared interface for it to have an effect. If reliable=false + * is specified then remote method invocation is done over UDP + * instead of TCP, ie: unreliably... but faster. * * @author Paul Speed */ diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java index 6b218891d..5167bd7eb 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/CallType.java @@ -34,10 +34,27 @@ package com.jme3.network.service.rmi; /** - * + * Internal type denoting the type of call to make when remotely + * invoking methods. * * @author Paul Speed */ public enum CallType { - Synchronous, Asynchronous, Unreliable + /** + * Caller will block until a response is received and returned. + */ + Synchronous, + + /** + * Caller does not block or wait for a response. The other end + * of the connection will also not send one. + */ + Asynchronous, + + /** + * Similar to asynchronous in that no response is expected or sent + * but differs in that the call will be sent over UDP and so may + * not make it to the other end. + */ + Unreliable } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java index 88d91a686..4f7f7de32 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java @@ -39,7 +39,8 @@ import java.util.List; /** - * + * Internal information about a shared class. This is the information + * that is sent over the wire for shared types. * * @author Paul Speed */ diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java index 557948042..addc03593 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java @@ -40,7 +40,8 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * + * Internal registry of shared types and their ClassInfo and MethodInfo + * objects. * * @author Paul Speed */ diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java index a0138556d..9b324a6c3 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java @@ -39,7 +39,8 @@ import javax.jws.Oneway; /** - * + * Internal information about shared methods. This is part of the data that + * is passed over the wire when an object is shared. * * @author Paul Speed */ diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java index 03309a4fa..f42ada84f 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java @@ -39,7 +39,7 @@ import java.util.concurrent.ConcurrentHashMap; /** - * + * Used internally to remotely invoke methods on RMI shared objects. * * @author Paul Speed */ diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java index 437a697f2..ea1506f72 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java @@ -41,7 +41,23 @@ import java.util.List; /** + * A service that can be added to the client to support a simple + * shared objects protocol. * + *

Objects are shared by adding them to the RmiRegistry with one of the + * share() methods. Shared objects must have a separate interface and implementation. + * The interface is what the other end of the connection will use to interact + * with the object and that interface class must be available on both ends of + * the connection. The implementing class need only be on the sharing end.

+ * + *

Shared objects can be accessed on the other end of the connection by + * using one of the RmiRegistry's getRemoteObject() methods. These can be + * used to lookup an object by class if it is a shared singleton or by name + * if it was registered with a name.

+ * + *

Note: This RMI implementation is not as advanced as Java's regular + * RMI as it won't marshall shared references, ie: you can't pass + * a shared objects as an argument to another shared object's method.

* * @author Paul Speed */ @@ -64,18 +80,42 @@ public class RmiClientService extends AbstractClientService { this.rmiObjectId = rmiObjectId; } + /** + * Shares the specified object with the server and associates it with the + * specified type. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + */ public void share( T object, Class type ) { share(defaultChannel, object, type); } + /** + * Shares the specified object with the server and associates it with the + * specified type. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + * All object related communication will be done over the specified connection + * channel. + */ public void share( byte channel, T object, Class type ) { share(channel, type.getName(), object, type); } + /** + * Shares the specified object with the server and associates it with the + * specified name. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + */ public void share( String name, T object, Class type ) { share(defaultChannel, name, object, type); } + /** + * Shares the specified object with the server and associates it with the + * specified name. Objects shared in this way are available in the connection-specific + * RMI registry on the server and are not available to other connections. + * All object related communication will be done over the specified connection + * channel. + */ public void share( byte channel, String name, T object, Class type ) { if( !isStarted ) { synchronized(pending) { @@ -90,10 +130,18 @@ public class RmiClientService extends AbstractClientService { rmi.share(channel, name, object, type); } + /** + * Looks up a remote object on the server by type and returns a local proxy to the + * remote object that was shared on the other end of the network connection. + */ public T getRemoteObject( Class type ) { return rmi.getRemoteObject(type); } + /** + * Looks up a remote object on the server by name and returns a local proxy to the + * remote object that was shared on the other end of the network connection. + */ public T getRemoteObject( String name, Class type ) { return rmi.getRemoteObject(name, type); } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java index 47d115d5b..547e83dd4 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java @@ -45,7 +45,11 @@ import com.jme3.network.HostedConnection; */ public class RmiContext { private static final ThreadLocal connection = new ThreadLocal(); - + + /** + * Returns the HostedConnection that is responsible for any + * RMI-related calls on this thread. + */ public static HostedConnection getRmiConnection() { return connection.get(); } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java index 647fe4fb6..997658a77 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java @@ -46,7 +46,30 @@ import java.util.logging.Logger; /** + * A service that can be added to the host to support a simple + * shared objects protocol. * + *

Objects are shared by adding them to the RmiRegistry with one of the + * share() methods. Shared objects must have a separate interface and implementation. + * The interface is what the other end of the connection will use to interact + * with the object and that interface class must be available on both ends of + * the connection. The implementing class need only be on the sharing end.

+ * + *

Shared objects can be accessed on the other end of the connection by + * using one of the RmiRegistry's getRemoteObject() methods. These can be + * used to lookup an object by class if it is a shared singleton or by name + * if it was registered with a name.

+ * + *

On the hosting side, a special shardGlobal() method is provided that + * will register shared objects that will automatically be provided to every + * new joining client and they will all be calling the same server-side instance. + * Normally, shared objects themselves are connection specific and handled + * at the connection layer. The shareGlobal() space is a way to have global + * resources passed directly though the need is relatively rare.

+ * + *

Note: This RMI implementation is not as advanced as Java's regular + * RMI as it won't marshall shared references, ie: you can't pass + * a shared objects as an argument to another shared object's method.

* * @author Paul Speed */ @@ -74,14 +97,34 @@ public class RmiHostedService extends AbstractHostedService { Serializer.registerClasses(ClassInfo.class, MethodInfo.class); } + /** + * Shares a server-wide object associated with the specified type. All connections + * with RMI hosting started will have access to this shared object as soon as they + * connect and they will all share the same instance. It is up to the shared object + * to handle any multithreading that might be required. + */ public void shareGlobal( T object, Class type ) { shareGlobal(defaultChannel, type.getName(), object, type); } + /** + * Shares a server-wide object associated with the specified name. All connections + * with RMI hosting started will have access to this shared object as soon as they + * connect and they will all share the same instance. It is up to the shared object + * to handle any multithreading that might be required. + */ public void shareGlobal( String name, T object, Class type ) { shareGlobal(defaultChannel, name, object, type); } + /** + * Shares a server-wide object associated with the specified name over the specified + * channel. All connections with RMI hosting started will have access to this shared + * object as soon as they connect and they will all share the same instance. It is up + * to the shared object to handle any multithreading that might be required. + * All network communcation associated with the shared object will be done over + * the specified channel. + */ public void shareGlobal( byte channel, String name, T object, Class type ) { GlobalShare share = new GlobalShare(channel, object, type); GlobalShare existing = globalShares.put(name, share); @@ -99,14 +142,30 @@ public class RmiHostedService extends AbstractHostedService { } } + /** + * Set to true if all new connections should automatically have RMI hosting started. + * Set to false if the game-specific connection setup will call startHostingOnConnection() + * after some connection setup is done (for example, logging in). Note: generally + * is is safe to autohost RMI as long as callers are careful about what they've added + * using shareGlobal(). One reasonable use-case is to shareGlobal() some kind of login + * service and nothing else. All other shared objects would then be added as connection + * specific objects during successful login processing. + */ public void setAutoHost( boolean b ) { this.autoHost = b; } + /** + * Returns true if RMI hosting is automatically started for all new connections. + */ public boolean getAutoHost() { return autoHost; } + /** + * Returns the RMI registry for the specific HostedConection. Each connection + * has its own registry with its own connection-specific shared objects. + */ public RmiRegistry getRmiRegistry( HostedConnection hc ) { return hc.getAttribute(ATTRIBUTE_NAME); } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java index 6d1722307..c04a8c7b3 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiRegistry.java @@ -87,18 +87,45 @@ public class RmiRegistry { rpc.registerHandler(rmiId, rmiHandler); } + /** + * Exposes the specified object to the other end of the connection as + * the specified interface type. The object can be looked up by type + * on the other end. + */ public void share( T object, Class type ) { share(defaultChannel, object, type); } + /** + * Exposes, through a specific connection channel, the specified object + * to the other end of the connection as the specified interface type. + * The object can be looked up by type on the other end. + * The specified channel will be used for all network communication + * specific to this object. + */ public void share( byte channel, T object, Class type ) { share(channel, type.getName(), object, type); } + /** + * Exposes the specified object to the other end of the connection as + * the specified interface type and associates it with the specified name. + * The object can be looked up by the associated name on the other end of + * the connection. + */ public void share( String name, T object, Class type ) { share(defaultChannel, name, object, type); } + /** + * Exposes, through a specific connection channel, the specified object to + * the other end of the connection as the specified interface type and associates + * it with the specified name. + * The object can be looked up by the associated name on the other end of + * the connection. + * The specified channel will be used for all network communication + * specific to this object. + */ public void share( byte channel, String name, T object, Class type ) { ClassInfo typeInfo = classCache.getClassInfo(type); @@ -153,16 +180,18 @@ public class RmiRegistry { } /** - * Returns an object that was previously registered with share(). + * Returns a local object that was previously registered with share() using + * just type registration. */ - public T getSharedObject( Class type ) { - return getSharedObject(type.getName(), type); + public T getLocalObject( Class type ) { + return getLocalObject(type.getName(), type); } /** - * Returns an object that was previously registered with share(). + * Returns a local object that was previously registered with share() using + * name registration. */ - public T getSharedObject( String name, Class type ) { + public T getLocalObject( String name, Class type ) { local.lock.readLock().lock(); try { return type.cast(local.byName.get(name)); @@ -171,10 +200,24 @@ public class RmiRegistry { } } + /** + * Looks up a remote object by type and returns a local proxy to the remote object + * that was shared on the other end of the network connection. If this is called + * from a client then it is accessing a shared object registered on the server. + * If this is called from the server then it is accessing a shared object registered + * on the client. + */ public T getRemoteObject( Class type ) { return getRemoteObject(type.getName(), type); } + /** + * Looks up a remote object by name and returns a local proxy to the remote object + * that was shared on the other end of the network connection. If this is called + * from a client then it is accessing a shared object registered on the server. + * If this is called from the server then it is accessing a shared object registered + * on the client. + */ public T getRemoteObject( String name, Class type ) { remote.lock.readLock().lock(); try { From 31cab674b3cf6c8e0ad6c8e47560f2150167ecff Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Mon, 16 Nov 2015 03:03:53 -0500 Subject: [PATCH 28/37] Removing the dodgy 'optimization' that broke some people and caused other 'makeup' changes to better support the dodginess. (And I do realize I have the benefit of analyzing the aftermath, hindsight is 20/20, etc.) Included a big long comment about the right way to implement this optimization. --- .../src/main/java/com/jme3/scene/Node.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index 5edfa021b..b388a1e5a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -570,6 +570,35 @@ public class Node extends Spatial { // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. // The idea is when there are few children, it can be too expensive to test boundingVolume first. + /* + I'm removing this change until some issues can be addressed and I really + think it needs to be implemented a better way anyway. + + First, it causes issues for anyone doing collideWith() with BoundingVolumes + and expecting it to trickle down to the children. For example, children + with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing + a collision check at the parent level then has to do a BoundingSphere to BoundingBox + collision which isn't resolved. (Having to come up with a collision point in that + case is tricky and the first sign that this is the wrong approach.) + + Second, the rippling changes this caused to 'optimize' collideWith() for this + special use-case are another sign that this approach was a bit dodgy. The whole + idea of calculating a full collision just to see if the two shapes collide at all + is very wasteful. + + A proper implementation should support a simpler boolean check that doesn't do + all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9% + of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much + faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done. + + I don't have time to do it right now but I'll at least un-break a bunch of peoples' + code until it can be 'optimized' properly. Hopefully it's not too late to back out + the other dodgy ripples this caused. -pspeed (hindsight-expert ;)) + + Note: the code itself is relatively simple to implement but I don't have time to + a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast + enough to do all the time for > 1. + if (children.size() > 4) { BoundingVolume bv = this.getWorldBound(); @@ -578,6 +607,7 @@ public class Node extends Spatial { // collideWith without CollisionResults parameter used to avoid allocation when possible if (bv.collideWith(other) == 0) return 0; } + */ for (Spatial child : children.getArray()){ total += child.collideWith(other, results); } From cde35a005afa4a14a7833874d1e77d649631a7ff Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Tue, 17 Nov 2015 01:46:38 -0500 Subject: [PATCH 29/37] Just fixing an indent. --- jme3-core/src/main/java/com/jme3/scene/Node.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index b388a1e5a..74b14188d 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -195,7 +195,7 @@ public class Node extends Spatial { void invalidateUpdateList() { updateListValid = false; if ( parent != null ) { - parent.invalidateUpdateList(); + parent.invalidateUpdateList(); } } From d57147e3929f35c49fcefcc29cab08263c761a33 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Tue, 17 Nov 2015 02:39:46 -0500 Subject: [PATCH 30/37] A small optimization. The BitmapTextPage does not require custom updates so it now signifies that. --- jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java index 1edac967c..58dc87550 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java @@ -59,6 +59,7 @@ class BitmapTextPage extends Geometry { BitmapTextPage(BitmapFont font, boolean arrayBased, int page) { super("BitmapFont", new Mesh()); + setRequiresUpdates(false); setBatchHint(BatchHint.Never); if (font == null) { throw new IllegalArgumentException("font cannot be null."); From ad4634ce04cab3c41f88ed6c61efc62f4a2fe277 Mon Sep 17 00:00:00 2001 From: Nehon Date: Tue, 17 Nov 2015 14:54:59 +0100 Subject: [PATCH 31/37] .hdr files are now loaded in sRGB color space as there is no reason it should be loaded in linear space. --- .../src/plugins/java/com/jme3/texture/plugins/HDRLoader.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java index 611e208d4..8060197b2 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java @@ -308,9 +308,8 @@ public class HDRLoader implements AssetLoader { } in.close(); - dataStore.rewind(); - //TODO, HDR color space? considered linear here - return new Image(pixelFormat, width, height, dataStore, ColorSpace.Linear); + dataStore.rewind(); + return new Image(pixelFormat, width, height, dataStore, ColorSpace.sRGB); } public Object load(AssetInfo info) throws IOException { From 822d327236f29d87a2e465d52ed0e2ef20087963 Mon Sep 17 00:00:00 2001 From: Nehon Date: Wed, 18 Nov 2015 15:06:58 +0100 Subject: [PATCH 32/37] Image now properly save it's colorSpace when saved --- jme3-core/src/main/java/com/jme3/texture/Image.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jme3-core/src/main/java/com/jme3/texture/Image.java b/jme3-core/src/main/java/com/jme3/texture/Image.java index ca9404720..91b80ad3e 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Image.java +++ b/jme3-core/src/main/java/com/jme3/texture/Image.java @@ -1041,6 +1041,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { capsule.write(mipMapSizes, "mipMapSizes", null); capsule.write(multiSamples, "multiSamples", 1); capsule.writeByteBufferArrayList(data, "data", null); + capsule.write(colorSpace, "colorSpace", null); } public void read(JmeImporter e) throws IOException { @@ -1052,6 +1053,7 @@ public class Image extends NativeObject implements Savable /*, Cloneable*/ { mipMapSizes = capsule.readIntArray("mipMapSizes", null); multiSamples = capsule.readInt("multiSamples", 1); data = (ArrayList) capsule.readByteBufferArrayList("data", null); + colorSpace = capsule.readEnum("colorSpace", ColorSpace.class, null); if (mipMapSizes != null) { needGeneratedMips = false; From 50a9a8636ba5a1c682fb95ac0a42095f967b22c6 Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 20 Nov 2015 08:59:42 +0100 Subject: [PATCH 33/37] HDRLoader, reverted the change that loaded the HDR files in sRGB space, since the specs says that data is in linear space for this format. --- .../src/plugins/java/com/jme3/texture/plugins/HDRLoader.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java index 8060197b2..315d71955 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java @@ -309,7 +309,8 @@ public class HDRLoader implements AssetLoader { in.close(); dataStore.rewind(); - return new Image(pixelFormat, width, height, dataStore, ColorSpace.sRGB); + //HDR files color data is actually stored in linear space. + return new Image(pixelFormat, width, height, dataStore, ColorSpace.Linear); } public Object load(AssetInfo info) throws IOException { From 022899c1993dbd1c0815ace2576febd38d24e871 Mon Sep 17 00:00:00 2001 From: Nehon Date: Fri, 20 Nov 2015 16:27:47 +0100 Subject: [PATCH 34/37] Changed the way texture previews are done in the SDK. All texture loading is now done on JME's thread and not on the awt thread anymore to avoid to stall the UI when loading big textures. --- .../gde/core/properties/TextureBrowser.form | 1 + .../gde/core/properties/TextureBrowser.java | 43 ++--- .../properties/TexturePropertyEditor.java | 7 + .../core/properties/preview/DDSPreview.java | 140 -------------- .../properties/preview/TexturePreview.java | 179 ++++++++++++++++++ .../core/properties/preview/tex3DThumb.frag | 9 +- .../core/properties/preview/tex3DThumb.vert | 4 +- .../multiview/widgets/TexturePanel.java | 25 +-- .../TerrainEditorTopComponent.java | 62 +++--- .../terraineditor/TerrainToolController.java | 3 +- .../terraineditor/sky/SkyboxVisualPanel2.java | 134 +++---------- 11 files changed, 274 insertions(+), 333 deletions(-) delete mode 100644 sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java create mode 100644 sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form index 424078412..a5ad10fe2 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.form @@ -9,6 +9,7 @@ + diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java index c199fe39a..285ada07c 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/TextureBrowser.java @@ -31,9 +31,8 @@ */ package com.jme3.gde.core.properties; -import com.jme3.asset.TextureKey; import com.jme3.gde.core.assets.ProjectAssetManager; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.core.util.TreeUtil; import com.jme3.texture.Texture; import java.awt.event.MouseEvent; @@ -42,6 +41,7 @@ import java.awt.event.WindowListener; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.logging.Level; import java.util.logging.Logger; import java.util.prefs.Preferences; import javax.swing.DefaultListSelectionModel; @@ -52,8 +52,6 @@ import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; -import jme3tools.converters.ImageToAwt; -import org.openide.util.ImageUtilities; /** * Displays all textures in the ProjectAssetManager, @@ -68,7 +66,7 @@ public class TextureBrowser extends javax.swing.JDialog implements TreeSelection private ProjectAssetManager assetManager; private TexturePropertyEditor editor; - private DDSPreview ddsPreview; + private TexturePreview texPreview; private Preferences prefs; private static final String PREF_LAST_SELECTED = "lastSelectedTexture"; @@ -234,8 +232,8 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G if (node != null && node.isLeaf()) { String selected = TreeUtil.getPath(node.getUserObjectPath()); selected = selected.substring(0, selected.lastIndexOf("/")); - Texture tex = assetManager.loadTexture(selected); - editor.setValue(tex); +// Texture tex = assetManager.loadTexture(selected); +// editor.setValue(tex); editor.setAsText(selected); return true; } @@ -270,7 +268,7 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G private void setSelectedTexture(Texture texture) { if (texture != null) { - Logger.getLogger(TextureBrowser.class.getName()).finer("Looking for Texture: " + texture.getName()); + Logger.getLogger(TextureBrowser.class.getName()).log(Level.FINER, "Looking for Texture: {0}", texture.getName()); String[] path = ("/" + texture.getName()).split("/"); TreePath parent = new TreePath((TreeNode) jTree1.getModel().getRoot()); TreePath selectedTreePath = TreeUtil.buildTreePath(jTree1, parent, path, 0, true); @@ -287,6 +285,7 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G } } + @Override public void valueChanged(TreeSelectionEvent e) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) jTree1.getLastSelectedPathComponent(); @@ -295,25 +294,14 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G return; } - //Object nodeInfo = node.getUserObject(); if (node.isLeaf()) { String selected = TreeUtil.getPath(node.getUserObjectPath()); selected = selected.substring(0, selected.lastIndexOf("/")); Icon newicon = null; - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview(assetManager); - } - ddsPreview.requestPreview(selected, (String) node.getUserObject(), 450, 450, imagePreviewLabel, infoLabel); - - } else { - Texture tex = assetManager.loadTexture(selected); - newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - assetManager.deleteFromCache(new TextureKey(selected)); - imagePreviewLabel.setIcon(newicon); - infoLabel.setText(" " + node.getUserObject() + " w : " + newicon.getIconWidth() + " h : " + newicon.getIconHeight()); + if (texPreview == null) { + texPreview = new TexturePreview(assetManager); } - + texPreview.requestPreview(selected, (String) node.getUserObject(), 450, 450, imagePreviewLabel, infoLabel); prefs.put(PREF_LAST_SELECTED, selected); } else { imagePreviewLabel.setIcon(null); @@ -323,27 +311,34 @@ private void noTexturebuttonActionPerformed(java.awt.event.ActionEvent evt) {//G } + @Override public void windowOpened(WindowEvent e) { } + @Override public void windowClosing(WindowEvent e) { - if (ddsPreview != null) { - ddsPreview.cleanUp(); + if (texPreview != null) { + texPreview.cleanUp(); } } + @Override public void windowClosed(WindowEvent e) { } + @Override public void windowIconified(WindowEvent e) { } + @Override public void windowDeiconified(WindowEvent e) { } + @Override public void windowActivated(WindowEvent e) { } + @Override public void windowDeactivated(WindowEvent e) { } diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java index 9f4658e02..555fc5160 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/TexturePropertyEditor.java @@ -80,7 +80,14 @@ public class TexturePropertyEditor implements PropertyEditor { } } + @Override public Object getValue() { + if(texture == null && assetKey != null){ + if (manager == null){ + manager = SceneApplication.getApplication().getAssetManager(); + } + texture = manager.loadTexture(assetKey); + } return texture; } diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java deleted file mode 100644 index c6597ff6a..000000000 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/DDSPreview.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * 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.gde.core.properties.preview; - -import com.jme3.asset.TextureKey; -import com.jme3.gde.core.assets.ProjectAssetManager; -import com.jme3.gde.core.scene.PreviewRequest; -import com.jme3.gde.core.scene.SceneApplication; -import com.jme3.gde.core.scene.SceneListener; -import com.jme3.gde.core.scene.SceneRequest; -import com.jme3.material.Material; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import com.jme3.texture.Texture; -import com.jme3.util.SkyFactory; -import javax.swing.ImageIcon; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; - -/** - * - * @author Nehon - */ -public class DDSPreview implements SceneListener { - - private final ProjectAssetManager assetManager; - private JComponent picPreview; - private final Geometry quad; - private final Geometry quad3D; - private final Material material; - private final Material material3D; - - public DDSPreview(ProjectAssetManager assetManager) { - this.assetManager = assetManager; - - Quad quadMesh = new Quad(4.5f, 4.5f); - Quad quadMesh3D = new Quad(4.5f, 4.5f); - quadMesh3D.scaleTextureCoordinates(new Vector2f(4, 4)); - quad = new Geometry("previewQuad", quadMesh); - quad.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); - quad3D = new Geometry("previewQuad", quadMesh3D); - quad3D.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); - material3D = new Material(assetManager, "com/jme3/gde/core/properties/preview/tex3DThumb.j3md"); - material3D.setFloat("InvDepth", 1f / 16f); - material3D.setInt("Rows", 4); - material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - SceneApplication.getApplication().addSceneListener(this); - } - - public void requestPreview(String textureName, String displayName, int width, int height, JComponent picLabel, JLabel infoLabel) { - TextureKey key = new TextureKey(textureName); - picPreview = picLabel; - assetManager.deleteFromCache(key); - Texture t = assetManager.loadTexture(key); - Spatial geom = quad; - if (key.getTextureTypeHint() == Texture.Type.TwoDimensional) { - material.setTexture("ColorMap", t); - geom.setMaterial(material); - if (infoLabel != null) { - infoLabel.setText(" " + displayName + " w : " + t.getImage().getWidth() + " h : " + t.getImage().getHeight()); - } - } else if (key.getTextureTypeHint() == Texture.Type.ThreeDimensional) { - geom = quad3D; - assetManager.deleteFromCache(key); - key.setTextureTypeHint(Texture.Type.ThreeDimensional); - t = assetManager.loadTexture(key); - material3D.setTexture("Texture", t); - geom.setMaterial(material3D); - if (infoLabel != null) { - infoLabel.setText(" " + displayName + " (Texture3D) w : " + t.getImage().getWidth() + " h : " + t.getImage().getHeight() + " d : " + t.getImage().getDepth()); - } - } else if (key.getTextureTypeHint() == Texture.Type.CubeMap) { - assetManager.deleteFromCache(key); - geom = SkyFactory.createSky(assetManager, textureName, SkyFactory.EnvMapType.CubeMap); - if (infoLabel != null) { - infoLabel.setText(" " + displayName + " (CubeMap) w : " + t.getImage().getWidth() + " h : " + t.getImage().getHeight()); - } - } - - PreviewRequest request = new PreviewRequest(this, geom, width, height); - request.getCameraRequest().setLocation(new Vector3f(0, 0, 5.3f)); - request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y.mult(-1)); - SceneApplication.getApplication().createPreview(request); - } - - public void cleanUp() { - SceneApplication.getApplication().removeSceneListener(this); - } - - public void sceneOpened(SceneRequest request) { - } - - public void sceneClosed(SceneRequest request) { - } - - public void previewCreated(PreviewRequest request) { - if (request.getRequester() == this) { - final ImageIcon icon = new ImageIcon(request.getImage()); - if (picPreview instanceof JLabel) { - ((JLabel) picPreview).setIcon(icon); - } - if (picPreview instanceof JButton) { - ((JButton) picPreview).setIcon(icon); - } - } - } -} diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java new file mode 100644 index 000000000..337e3a34c --- /dev/null +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/TexturePreview.java @@ -0,0 +1,179 @@ +/* + * 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.gde.core.properties.preview; + +import com.jme3.asset.TextureKey; +import com.jme3.gde.core.assets.ProjectAssetManager; +import com.jme3.gde.core.scene.PreviewRequest; +import com.jme3.gde.core.scene.SceneApplication; +import com.jme3.gde.core.scene.SceneListener; +import com.jme3.gde.core.scene.SceneRequest; +import com.jme3.material.Material; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; +import java.util.concurrent.Callable; +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JLabel; + +/** + * + * @author Nehon + */ +public class TexturePreview implements SceneListener { + + private final ProjectAssetManager assetManager; + private JComponent picPreview; + private final Geometry quad; + private final Geometry quad3D; + private final Material material; + private final Material material3D; + + public TexturePreview(ProjectAssetManager assetManager) { + this.assetManager = assetManager; + + Quad quadMesh = new Quad(4.5f, 4.5f); + Quad quadMesh3D = new Quad(4.5f, 4.5f); + quadMesh3D.scaleTextureCoordinates(new Vector2f(4, 4)); + quad = new Geometry("previewQuad", quadMesh); + quad.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); + quad3D = new Geometry("previewQuad", quadMesh3D); + quad3D.setLocalTranslation(new Vector3f(-2.25f, -2.25f, 0)); + material3D = new Material(assetManager, "com/jme3/gde/core/properties/preview/tex3DThumb.j3md"); + material3D.setFloat("InvDepth", 1f / 16f); + material3D.setInt("Rows", 4); + material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + SceneApplication.getApplication().addSceneListener(this); + } + + public void requestPreview(final String textureName, final String displayName, final int width, final int height, final JComponent picLabel, final JLabel infoLabel) { + + picPreview = picLabel; + clearPreview(); + if (infoLabel != null) { + infoLabel.setText(" Creating preview..."); + } + + SceneApplication.getApplication().enqueue(new Callable() { + + @Override + public Void call() throws Exception { + TextureKey key = new TextureKey(textureName); + Texture t = assetManager.loadTexture(key); + Spatial geom = quad; + if (key.getTextureTypeHint() == Texture.Type.TwoDimensional) { + material.setTexture("ColorMap", t); + geom.setMaterial(material); + setLabel(infoLabel, displayName, t.getImage().getWidth(), t.getImage().getHeight(), -1); + } else if (key.getTextureTypeHint() == Texture.Type.ThreeDimensional) { + geom = quad3D; + material3D.setTexture("Texture", t); + geom.setMaterial(material3D); + setLabel(infoLabel, displayName + " (Texture3D)", t.getImage().getWidth(), t.getImage().getHeight(), t.getImage().getDepth()); + + } else if (key.getTextureTypeHint() == Texture.Type.CubeMap) { + geom = SkyFactory.createSky(assetManager, textureName, SkyFactory.EnvMapType.CubeMap); + setLabel(infoLabel, displayName + " (CubeMap)", t.getImage().getWidth(), t.getImage().getHeight(), -1); + } + + PreviewRequest request = new PreviewRequest(TexturePreview.this, geom, width, height); + request.getCameraRequest().setLocation(new Vector3f(0, 0, 5.3f)); + request.getCameraRequest().setLookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y.mult(-1)); + SceneApplication.getApplication().createPreview(request); + + return null; + } + + }); + } + + public void cleanUp() { + SceneApplication.getApplication().removeSceneListener(this); + } + + @Override + public void sceneOpened(SceneRequest request) { + } + + @Override + public void sceneClosed(SceneRequest request) { + } + + private void setLabel(final JLabel label, final String text, final int width, final int height, final int depth) { + + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + if (label != null) { + String labText = " " + text + " w : " + width + " h : " + height; + if (depth > 0) { + labText += " d : " + depth; + } + label.setText(labText); + } + } + }); + } + + private void clearPreview() { + if (picPreview instanceof JLabel) { + ((JLabel) picPreview).setIcon(null); + } + if (picPreview instanceof JButton) { + ((JButton) picPreview).setIcon(null); + } + } + + @Override + public void previewCreated(final PreviewRequest request) { + java.awt.EventQueue.invokeLater(new Runnable() { + @Override + public void run() { + if (request.getRequester() == TexturePreview.this) { + final ImageIcon icon = new ImageIcon(request.getImage()); + if (picPreview instanceof JLabel) { + ((JLabel) picPreview).setIcon(icon); + } + if (picPreview instanceof JButton) { + ((JButton) picPreview).setIcon(icon); + } + } + } + }); + } +} diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag index f6eb25a3b..4ae52bd3f 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.frag @@ -5,10 +5,9 @@ uniform float m_InvDepth; varying vec2 texCoord; void main(){ -float depthx=floor(texCoord.x); - float depthy=(m_Rows-1.0) - floor(texCoord.y); - //vec3 texC=vec3(texCoord.x,texCoord.y ,0.7);// + float depthx = floor(texCoord.x); + float depthy = (float(m_Rows) - 1.0) - floor(texCoord.y); - vec3 texC=vec3(fract(texCoord.x),fract(texCoord.y),(depthy*m_Rows+depthx)*m_InvDepth);// - gl_FragColor= texture3D(m_Texture,texC); + vec3 texC = vec3(fract(texCoord.x), fract(texCoord.y), (depthy * float(m_Rows) + depthx) * m_InvDepth);// + gl_FragColor = texture3D(m_Texture, texC); } \ No newline at end of file diff --git a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert index 6d27bc030..fd940682b 100644 --- a/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert +++ b/sdk/jme3-core/src/com/jme3/gde/core/properties/preview/tex3DThumb.vert @@ -6,6 +6,6 @@ attribute vec3 inPosition; varying vec2 texCoord; void main(){ - gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition,1.0); - texCoord=inTexCoord; + gl_Position = g_WorldViewProjectionMatrix * vec4(inPosition, 1.0); + texCoord = inTexCoord; } \ No newline at end of file diff --git a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java index 0129535e4..1e5856fd1 100644 --- a/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java +++ b/sdk/jme3-materialeditor/src/com/jme3/gde/materials/multiview/widgets/TexturePanel.java @@ -13,7 +13,7 @@ package com.jme3.gde.materials.multiview.widgets; import com.jme3.asset.AssetNotFoundException; import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.properties.TexturePropertyEditor; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.materials.MaterialProperty; import com.jme3.gde.materials.multiview.MaterialEditorTopComponent; import com.jme3.texture.Texture; @@ -39,7 +39,7 @@ public class TexturePanel extends MaterialPropertyWidget { private boolean flip = false; private boolean repeat = false; private String textureName = null; - private DDSPreview ddsPreview; + private TexturePreview texPreview; private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1); /** Creates new form SelectionPanel */ @@ -53,22 +53,13 @@ public class TexturePanel extends MaterialPropertyWidget { if (!"".equals(textureName)) { exec.execute(new Runnable() { + @Override public void run() { try{ - Texture tex = manager.loadTexture(textureName); - if (textureName.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview(manager); + if (texPreview == null) { + texPreview = new TexturePreview(manager); } - ddsPreview.requestPreview(textureName, "", 80, 80, texturePreview, null); - } else { - final Icon newicon = ImageUtilities.image2Icon(resizeImage(ImageToAwt.convert(tex.getImage(), false, true, 0))); - SwingUtilities.invokeLater(new Runnable() { - public void run() { - texturePreview.setIcon(newicon); - } - }); - } + texPreview.requestPreview(textureName, "", 80, 25, texturePreview, null); } catch (AssetNotFoundException a) { Logger.getLogger(MaterialEditorTopComponent.class.getName()).log(Level.WARNING, "Could not load texture {0}", textureName); } @@ -318,8 +309,8 @@ public class TexturePanel extends MaterialPropertyWidget { @Override public void cleanUp() { - if (ddsPreview != null) { - ddsPreview.cleanUp(); + if (texPreview != null) { + texPreview.cleanUp(); } exec.shutdownNow(); } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java index 9e12371ea..b99a24274 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainEditorTopComponent.java @@ -34,7 +34,7 @@ package com.jme3.gde.terraineditor; import com.jme3.gde.core.assets.AssetDataObject; import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.properties.TexturePropertyEditor; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.core.scene.PreviewRequest; import com.jme3.gde.core.scene.SceneApplication; import com.jme3.gde.core.scene.SceneListener; @@ -122,7 +122,7 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce //private TerrainNodeListener terrainDeletedNodeListener; private boolean availableNormalTextures; private HelpCtx ctx = new HelpCtx("sdk.terrain_editor"); - private DDSPreview ddsPreview; + private TexturePreview texPreview; private Map buttons = new HashMap(); private JPanel insideToolSettings; @@ -153,35 +153,29 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce private float max = 0; private final Object lock = new Object(); + @Override public void incrementProgress(float f) { progress += f; progressHandle.progress((int) progress); } + @Override public void setMonitorMax(float f) { max = f; -// java.awt.EventQueue.invokeLater(new Runnable() { -// public void run() { -// synchronized(lock){ if (progressHandle == null) { progressHandle = ProgressHandleFactory.createHandle("Calculating terrain entropies..."); progressHandle.start((int) max); } -// } -// } -// }); } + @Override public float getMonitorMax() { return max; } + @Override public void progressComplete() { -// SwingUtilities.invokeLater(new Runnable() { -// public void run() { progressHandle.finish(); -// } -// }); } } @@ -1631,6 +1625,13 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce protected abstract Texture getTextureFromModel(int index); protected abstract boolean supportsNullTexture(); + + private TexturePreview getTexturePreview(){ + if (texPreview == null) { + texPreview = new TexturePreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); + } + return texPreview; + } private JButton getButton(Object value, final int row, final int column) { @@ -1656,21 +1657,9 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce } Texture tex = getTextureFromModel(index); // delegate to sub-class - - //Texture tex = SceneApplication.getApplication().getAssetManager().loadTexture((String)value); if (tex != null) { String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, lbl, null); - - } else { - Icon icon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - lbl.setIcon(icon); - } + getTexturePreview().requestPreview(selected, "", 80, 80, lbl, null); } } @@ -1694,24 +1683,17 @@ public final class TerrainEditorTopComponent extends TopComponent implements Sce TexturePropertyEditor editor = new TexturePropertyEditor(selectedTex); Component view = editor.getCustomEditor(); view.setVisible(true); - Texture tex = (Texture) editor.getValue(); - if (editor.getValue() != null) { - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, lbl, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - lbl.setIcon(newicon); - } + + if (editor.getAsText() != null) { + + String selected = editor.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, lbl, null); + Texture tex = SceneApplication.getApplication().getAssetManager().loadTexture(selected); + setTextureInModel(row, tex); } else if (supportsNullTexture()) { lbl.setIcon(null); } - setTextureInModel(row, tex); + } finally { alreadyChoosing = false; } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java index e89175b76..d0072d06a 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/TerrainToolController.java @@ -260,8 +260,7 @@ public class TerrainToolController extends SceneToolController { * The action on the tool has ended (mouse button up), record the Undo (for painting only now) */ void doTerrainEditToolActionEnded() { - if (terrainTool != null) { - System.out.println("undo tagged"); + if (terrainTool != null) { terrainTool.actionEnded(jmeRootNode, editorController.getCurrentDataObject()); } } diff --git a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java index 754f5e933..9e3cd0396 100644 --- a/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java +++ b/sdk/jme3-terrain-editor/src/com/jme3/gde/terraineditor/sky/SkyboxVisualPanel2.java @@ -33,16 +33,14 @@ package com.jme3.gde.terraineditor.sky; import com.jme3.gde.core.assets.ProjectAssetManager; import com.jme3.gde.core.properties.TexturePropertyEditor; -import com.jme3.gde.core.properties.preview.DDSPreview; +import com.jme3.gde.core.properties.preview.TexturePreview; import com.jme3.gde.core.scene.SceneApplication; import com.jme3.texture.Texture; import com.jme3.util.SkyFactory; import java.awt.Component; -import javax.swing.ComboBoxModel; import javax.swing.DefaultComboBoxModel; import javax.swing.Icon; import javax.swing.JCheckBox; -import javax.swing.JComboBox; import javax.swing.JPanel; import javax.swing.JTextField; import jme3tools.converters.ImageToAwt; @@ -50,7 +48,7 @@ import org.openide.util.ImageUtilities; public final class SkyboxVisualPanel2 extends JPanel { - private DDSPreview ddsPreview; + private TexturePreview texPreview; /** Creates new form SkyboxVisualPanel2 */ public SkyboxVisualPanel2() { @@ -111,6 +109,14 @@ public final class SkyboxVisualPanel2 extends JPanel { return editorWest; } + + private TexturePreview getTexturePreview(){ + if (texPreview == null) { + texPreview = new TexturePreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); + } + return texPreview; + } + /** This method is called from within the constructor to * initialize the form. * WARNING: Do NOT modify this code. The content of this method is @@ -450,141 +456,63 @@ public final class SkyboxVisualPanel2 extends JPanel { private void multipleTexSouthLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexSouthLoadButtonActionPerformed Component view = editorSouth.getCustomEditor(); view.setVisible(true); - if (editorSouth.getValue() != null) { - Texture tex = (Texture) editorSouth.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, southPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - southPic.setIcon(newicon); - } + if (editorSouth.getAsText()!= null) { + String selected = editorSouth.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, southPic, null); } }//GEN-LAST:event_multipleTexSouthLoadButtonActionPerformed private void multipleTexNorthLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexNorthLoadButtonActionPerformed Component view = editorNorth.getCustomEditor(); view.setVisible(true); - if (editorNorth.getValue() != null) { - Texture tex = (Texture) editorNorth.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, northPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - northPic.setIcon(newicon); - } + if (editorNorth.getAsText() != null) { + String selected = editorNorth.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, northPic, null); } }//GEN-LAST:event_multipleTexNorthLoadButtonActionPerformed private void multipleTexEastLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexEastLoadButtonActionPerformed Component view = editorEast.getCustomEditor(); view.setVisible(true); - if (editorEast.getValue() != null) { - Texture tex = (Texture) editorEast.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, eastPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - eastPic.setIcon(newicon); - } + if (editorEast.getAsText() != null) { + String selected = editorEast.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, eastPic, null); } }//GEN-LAST:event_multipleTexEastLoadButtonActionPerformed private void multipleTexWestLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexWestLoadButtonActionPerformed Component view = editorWest.getCustomEditor(); view.setVisible(true); - if (editorWest.getValue() != null) { - Texture tex = (Texture) editorWest.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, westPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - westPic.setIcon(newicon); - } + if (editorWest.getAsText() != null) { + String selected = editorWest.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, westPic, null); } }//GEN-LAST:event_multipleTexWestLoadButtonActionPerformed private void multipleTexTopLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexTopLoadButtonActionPerformed Component view = editorTop.getCustomEditor(); view.setVisible(true); - if (editorTop.getValue() != null) { - Texture tex = (Texture) editorTop.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, topPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - topPic.setIcon(newicon); - } + if (editorTop.getAsText() != null) { + String selected = editorTop.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, topPic, null); } }//GEN-LAST:event_multipleTexTopLoadButtonActionPerformed private void multipleTexBottomLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_multipleTexBottomLoadButtonActionPerformed Component view = editorBottom.getCustomEditor(); view.setVisible(true); - if (editorBottom.getValue() != null) { - Texture tex = (Texture) editorBottom.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, bottomPic, null); - - } else { - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - bottomPic.setIcon(newicon); - } + if (editorBottom.getAsText() != null) { + String selected = editorBottom.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, bottomPic, null); } }//GEN-LAST:event_multipleTexBottomLoadButtonActionPerformed private void singleTexLoadButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_singleTexLoadButtonActionPerformed Component view = editorSingle.getCustomEditor(); view.setVisible(true); - if (editorSingle.getValue() != null) { - Texture tex = (Texture) editorSingle.getValue(); - String selected = tex.getKey().getName(); - - if (selected.toLowerCase().endsWith(".dds")) { - if (ddsPreview == null) { - ddsPreview = new DDSPreview((ProjectAssetManager) SceneApplication.getApplication().getAssetManager()); - } - ddsPreview.requestPreview(selected, "", 80, 80, singlePic, null); - - } else { - - Icon newicon = ImageUtilities.image2Icon(ImageToAwt.convert(tex.getImage(), false, true, 0)); - singlePic.setIcon(newicon); - } + if (editorSingle.getAsText()!= null) { + String selected = editorSingle.getAsText(); + getTexturePreview().requestPreview(selected, "", 80, 80, singlePic, null); } }//GEN-LAST:event_singleTexLoadButtonActionPerformed From 00c687b4d87c3eb986da8dd6a94cdb206348ebac Mon Sep 17 00:00:00 2001 From: Dokthar Date: Sat, 21 Nov 2015 13:06:30 +0100 Subject: [PATCH 35/37] fix #383 : in PhysicsRigidBody the old statement in read() (l.741) was always false --- .../main/java/com/jme3/bullet/objects/PhysicsRigidBody.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java index 6f6eb4d76..0342f4c77 100644 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java +++ b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java @@ -737,8 +737,8 @@ public class PhysicsRigidBody extends PhysicsCollisionObject { setKinematic(capsule.readBoolean("kinematic", false)); setRestitution(capsule.readFloat("restitution", 0)); - Vector3f angularFactor = (Vector3f) capsule.readSavable("angularFactor", Vector3f.NAN.clone()); - if(angularFactor == Vector3f.NAN) { + Vector3f angularFactor = (Vector3f) capsule.readSavable("angularFactor", null); + if(angularFactor == null) { setAngularFactor(capsule.readFloat("angularFactor", 1)); } else { setAngularFactor(angularFactor); From 388a8a8bd788b7cc09f97aaa8c2302bc4c88106b Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 22 Nov 2015 05:13:12 -0500 Subject: [PATCH 36/37] Added a bunch of lower level logging that can be used to either watch traffic or debug serialization issues, etc.. Went ahead and instrumented the service manager while I was at it... and fixed a potential NPE in the AbstractService's toString() method. Fixed a bug in the DefaultClient where it couldn't be shutdown if attempted before the services had been started. --- .../com/jme3/network/base/DefaultClient.java | 15 ++++++++-- .../com/jme3/network/base/DefaultServer.java | 15 ++++++++++ .../jme3/network/serializing/Serializer.java | 5 ++++ .../jme3/network/service/AbstractService.java | 2 +- .../jme3/network/service/ServiceManager.java | 28 +++++++++++++++++++ 5 files changed, 61 insertions(+), 4 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index b297a933b..82c9f70cd 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -100,6 +100,7 @@ public class DefaultClient implements Client } protected void addStandardServices() { + log.fine("Adding standard services..."); services.addService(new ClientSerializerRegistrationsService()); } @@ -222,6 +223,9 @@ public class DefaultClient implements Client public void send( Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0})", message); + } if( message.isReliable() || channels.get(CH_UNRELIABLE) == null ) { send(CH_RELIABLE, message, true); } else { @@ -231,6 +235,9 @@ public class DefaultClient implements Client public void send( int channel, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0}, {1})", new Object[]{channel, message}); + } if( channel >= 0 ) { // Make sure we aren't still connecting. Channels // won't be valid until we are fully connected since @@ -287,9 +294,11 @@ public class DefaultClient implements Client if( !isRunning ) return; - // Let the services get a chance to stop before we - // kill the connection. - services.stop(); + if( services.isStarted() ) { + // Let the services get a chance to stop before we + // kill the connection. + services.stop(); + } // Send a close message diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index 2b9add2ef..d7ee17033 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -108,6 +108,7 @@ public class DefaultServer implements Server } protected void addStandardServices() { + log.fine("Adding standard services..."); services.addService(new ServerSerializerRegistrationsService()); } @@ -221,6 +222,10 @@ public class DefaultServer implements Server public void broadcast( Filter filter, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "broadcast({0}, {1})", new Object[]{filter, message}); + } + if( connections.isEmpty() ) return; @@ -239,6 +244,10 @@ public class DefaultServer implements Server public void broadcast( int channel, Filter filter, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "broadcast({0}, {1}. {2})", new Object[]{channel, filter, message}); + } + if( connections.isEmpty() ) return; @@ -540,6 +549,9 @@ public class DefaultServer implements Server public void send( Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0})", message); + } ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); if( message.isReliable() || channels[CH_UNRELIABLE] == null ) { channels[CH_RELIABLE].send( buffer ); @@ -550,6 +562,9 @@ public class DefaultServer implements Server public void send( int channel, Message message ) { + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "send({0}, {1})", new Object[]{channel, message}); + } checkChannel(channel); ByteBuffer buffer = MessageProtocol.messageToBuffer(message, null); channels[channel+CH_FIRST].send(buffer); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java index d4c054403..7e6b18849 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java @@ -45,6 +45,7 @@ import java.nio.ByteBuffer; import java.util.*; import java.util.jar.Attributes; import java.util.logging.Level; +import java.util.logging.LogManager; import java.util.logging.Logger; /** @@ -403,6 +404,10 @@ public abstract class Serializer { if (reg == null) { throw new SerializerException( "Class not registered:" + type ); } + + if( log.isLoggable(Level.FINER) ) { + log.log(Level.FINER, "writing class:{0} with ID:{1}", new Object[]{type, reg.getId()}); + } buffer.putShort(reg.getId()); return reg; } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java index 370b1c5af..3123e8c82 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -106,6 +106,6 @@ public abstract class AbstractService implements Servi @Override public String toString() { - return getClass().getName() + "[serviceManager.class=" + serviceManager.getClass() + "]"; + return getClass().getName() + "[serviceManager.class=" + (serviceManager != null ? serviceManager.getClass() : "") + "]"; } } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java index b8ee7d3c4..605c7e642 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -34,6 +34,8 @@ package com.jme3.network.service; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; /** * The base service manager class from which the HostedServiceManager @@ -44,6 +46,8 @@ import java.util.concurrent.CopyOnWriteArrayList; */ public abstract class ServiceManager { + static final Logger log = Logger.getLogger(ServiceManager.class.getName()); + private List> services = new CopyOnWriteArrayList>(); private volatile boolean started = false; @@ -76,6 +80,9 @@ public abstract class ServiceManager { return; } for( Service s : services ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Starting service:{0}", s); + } s.start(); } started = true; @@ -96,6 +103,9 @@ public abstract class ServiceManager { throw new IllegalStateException(getClass().getSimpleName() + " not started."); } for( Service s : services ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Stopping service:{0}", s); + } s.stop(); } started = false; @@ -106,9 +116,18 @@ public abstract class ServiceManager { * has already been started then the service will also be started. */ public > void addService( S s ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "addService({0})", s); + } services.add(s); + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Initializing service:{0}", s); + } s.initialize(getParent()); if( started ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Starting service:{0}", s); + } s.start(); } } @@ -120,10 +139,19 @@ public abstract class ServiceManager { * the service will be terminated. */ public > void removeService( S s ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "removeService({0})", s); + } if( started ) { + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Stopping service:{0}", s); + } s.stop(); } services.remove(s); + if( log.isLoggable(Level.FINE) ) { + log.log(Level.FINE, "Terminating service:{0}", s); + } s.terminate(getParent()); } From 197ed33c9e6a6e9f84adad75649407a2d4c5a986 Mon Sep 17 00:00:00 2001 From: Paul Speed Date: Sun, 22 Nov 2015 05:23:24 -0500 Subject: [PATCH 37/37] Added some missing @Overrides and finals. --- .../com/jme3/network/base/DefaultClient.java | 34 ++++++++--- .../com/jme3/network/base/DefaultServer.java | 56 ++++++++++++++----- .../jme3/network/base/MessageProtocol.java | 2 +- .../jme3/network/service/ServiceManager.java | 2 +- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index 82c9f70cd..9881830a8 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -67,18 +67,18 @@ public class DefaultClient implements Client private static final int CH_UNRELIABLE = 1; private static final int CH_FIRST = 2; - private ThreadLocal dataBuffer = new ThreadLocal(); + private final ThreadLocal dataBuffer = new ThreadLocal(); private int id = -1; private boolean isRunning = false; - private CountDownLatch connecting = new CountDownLatch(1); + private final CountDownLatch connecting = new CountDownLatch(1); private String gameName; private int version; - private MessageListenerRegistry messageListeners = new MessageListenerRegistry(); - private List stateListeners = new CopyOnWriteArrayList(); - private List> errorListeners = new CopyOnWriteArrayList>(); - private Redispatch dispatcher = new Redispatch(); - private List channels = new ArrayList(); + private final MessageListenerRegistry messageListeners = new MessageListenerRegistry(); + private final List stateListeners = new CopyOnWriteArrayList(); + private final List> errorListeners = new CopyOnWriteArrayList>(); + private final Redispatch dispatcher = new Redispatch(); + private final List channels = new ArrayList(); private ConnectorFactory connectorFactory; @@ -129,6 +129,7 @@ public class DefaultClient implements Client throw new IllegalStateException( "Client is not started." ); } + @Override public void start() { if( isRunning ) @@ -180,6 +181,7 @@ public class DefaultClient implements Client } } + @Override public boolean isStarted() { return isRunning; } @@ -196,31 +198,37 @@ public class DefaultClient implements Client } } + @Override public boolean isConnected() { return id != -1 && isRunning; } + @Override public int getId() { return id; } + @Override public String getGameName() { return gameName; } + @Override public int getVersion() { return version; } + @Override public ClientServiceManager getServices() { return services; } + @Override public void send( Message message ) { if( log.isLoggable(Level.FINER) ) { @@ -233,6 +241,7 @@ public class DefaultClient implements Client } } + @Override public void send( int channel, Message message ) { if( log.isLoggable(Level.FINER) ) { @@ -282,6 +291,7 @@ public class DefaultClient implements Client channels.get(channel).write(buffer); } + @Override public void close() { checkRunning(); @@ -322,41 +332,49 @@ public class DefaultClient implements Client services.terminate(); } + @Override public void addClientStateListener( ClientStateListener listener ) { stateListeners.add( listener ); } + @Override public void removeClientStateListener( ClientStateListener listener ) { stateListeners.remove( listener ); } + @Override public void addMessageListener( MessageListener listener ) { messageListeners.addMessageListener( listener ); } + @Override public void addMessageListener( MessageListener listener, Class... classes ) { messageListeners.addMessageListener( listener, classes ); } + @Override public void removeMessageListener( MessageListener listener ) { messageListeners.removeMessageListener( listener ); } + @Override public void removeMessageListener( MessageListener listener, Class... classes ) { messageListeners.removeMessageListener( listener, classes ); } + @Override public void addErrorListener( ErrorListener listener ) { errorListeners.add( listener ); } + @Override public void removeErrorListener( ErrorListener listener ) { errorListeners.remove( listener ); @@ -470,11 +488,13 @@ public class DefaultClient implements Client protected class Redispatch implements MessageListener, ErrorListener { + @Override public void messageReceived( Object source, Message m ) { dispatch( m ); } + @Override public void handleError( Object source, Throwable t ) { // Only doing the DefaultClient.this to make the code diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index d7ee17033..5c62733e8 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -66,26 +66,26 @@ public class DefaultServer implements Server private static final int CH_FIRST = 2; private boolean isRunning = false; - private AtomicInteger nextId = new AtomicInteger(0); + private final AtomicInteger nextId = new AtomicInteger(0); private String gameName; private int version; - private KernelFactory kernelFactory = KernelFactory.DEFAULT; + private final KernelFactory kernelFactory = KernelFactory.DEFAULT; private KernelAdapter reliableAdapter; private KernelAdapter fastAdapter; - private List channels = new ArrayList(); - private List alternatePorts = new ArrayList(); - private Redispatch dispatcher = new Redispatch(); - private Map connections = new ConcurrentHashMap(); - private Map endpointConnections + private final List channels = new ArrayList(); + private final List alternatePorts = new ArrayList(); + private final Redispatch dispatcher = new Redispatch(); + private final Map connections = new ConcurrentHashMap(); + private final Map endpointConnections = new ConcurrentHashMap(); // Keeps track of clients for whom we've only received the UDP // registration message - private Map connecting = new ConcurrentHashMap(); + private final Map connecting = new ConcurrentHashMap(); - private MessageListenerRegistry messageListeners + private final MessageListenerRegistry messageListeners = new MessageListenerRegistry(); - private List connectionListeners = new CopyOnWriteArrayList(); + private final List connectionListeners = new CopyOnWriteArrayList(); private HostedServiceManager services; @@ -112,21 +112,25 @@ public class DefaultServer implements Server services.addService(new ServerSerializerRegistrationsService()); } + @Override public String getGameName() { return gameName; } + @Override public int getVersion() { return version; } + @Override public HostedServiceManager getServices() { return services; } + @Override public int addChannel( int port ) { if( isRunning ) @@ -165,6 +169,7 @@ public class DefaultServer implements Server } } + @Override public void start() { if( isRunning ) @@ -186,11 +191,13 @@ public class DefaultServer implements Server services.start(); } + @Override public boolean isRunning() { return isRunning; } + @Override public void close() { if( !isRunning ) @@ -215,11 +222,13 @@ public class DefaultServer implements Server } } + @Override public void broadcast( Message message ) { broadcast( null, message ); } + @Override public void broadcast( Filter filter, Message message ) { if( log.isLoggable(Level.FINER) ) { @@ -242,6 +251,7 @@ public class DefaultServer implements Server } } + @Override public void broadcast( int channel, Filter filter, Message message ) { if( log.isLoggable(Level.FINER) ) { @@ -260,46 +270,55 @@ public class DefaultServer implements Server channels.get(channel+CH_FIRST).broadcast( adapter, buffer, true, false ); } + @Override public HostedConnection getConnection( int id ) { return connections.get(id); } + @Override public boolean hasConnections() { return !connections.isEmpty(); } + @Override public Collection getConnections() { return Collections.unmodifiableCollection((Collection)connections.values()); } + @Override public void addConnectionListener( ConnectionListener listener ) { connectionListeners.add(listener); } + @Override public void removeConnectionListener( ConnectionListener listener ) { connectionListeners.remove(listener); } + @Override public void addMessageListener( MessageListener listener ) { messageListeners.addMessageListener( listener ); } + @Override public void addMessageListener( MessageListener listener, Class... classes ) { messageListeners.addMessageListener( listener, classes ); } + @Override public void removeMessageListener( MessageListener listener ) { messageListeners.removeMessageListener( listener ); } + @Override public void removeMessageListener( MessageListener listener, Class... classes ) { messageListeners.removeMessageListener( listener, classes ); @@ -493,12 +512,12 @@ public class DefaultServer implements Server protected class Connection implements HostedConnection { - private int id; + private final int id; private boolean closed; private Endpoint[] channels; private int setChannelCount = 0; - private Map sessionData = new ConcurrentHashMap(); + private final Map sessionData = new ConcurrentHashMap(); public Connection( int channelCount ) { @@ -532,21 +551,25 @@ public class DefaultServer implements Server return setChannelCount == channels.length; } + @Override public Server getServer() { return DefaultServer.this; } + @Override public int getId() { return id; } + @Override public String getAddress() { return channels[CH_RELIABLE] == null ? null : channels[CH_RELIABLE].getAddress(); } + @Override public void send( Message message ) { if( log.isLoggable(Level.FINER) ) { @@ -560,6 +583,7 @@ public class DefaultServer implements Server } } + @Override public void send( int channel, Message message ) { if( log.isLoggable(Level.FINER) ) { @@ -588,6 +612,7 @@ public class DefaultServer implements Server fireConnectionRemoved( this ); } + @Override public void close( String reason ) { // Send a reason @@ -608,6 +633,7 @@ public class DefaultServer implements Server } } + @Override public Object setAttribute( String name, Object value ) { if( value == null ) @@ -616,11 +642,13 @@ public class DefaultServer implements Server } @SuppressWarnings("unchecked") + @Override public T getAttribute( String name ) { return (T)sessionData.get(name); } + @Override public Set attributeNames() { return Collections.unmodifiableSet(sessionData.keySet()); @@ -636,6 +664,7 @@ public class DefaultServer implements Server protected class Redispatch implements MessageListener { + @Override public void messageReceived( HostedConnection source, Message m ) { dispatch( source, m ); @@ -644,13 +673,14 @@ public class DefaultServer implements Server protected class FilterAdapter implements Filter { - private Filter delegate; + private final Filter delegate; public FilterAdapter( Filter delegate ) { this.delegate = delegate; } + @Override public boolean apply( Endpoint input ) { HostedConnection conn = getConnection( input ); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java index 6751ecc3f..bd85ececc 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageProtocol.java @@ -53,7 +53,7 @@ import java.util.LinkedList; */ public class MessageProtocol { - private LinkedList messages = new LinkedList(); + private final LinkedList messages = new LinkedList(); private ByteBuffer current; private int size; private Byte carry; diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java index 605c7e642..afcfd73c2 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -48,7 +48,7 @@ public abstract class ServiceManager { static final Logger log = Logger.getLogger(ServiceManager.class.getName()); - private List> services = new CopyOnWriteArrayList>(); + private final List> services = new CopyOnWriteArrayList>(); private volatile boolean started = false; protected ServiceManager() {