New Feature and potential new feature

I just checked in com.jme.util.NanoTimer into CVS.  This is a basic implementation of com.jme.util.Timer that utilizes System.nanoTime.  I wrote it for myself when handling jME on a headless server.  The LWJGLTimer cannot be instantiated in a headless server so I had to create an alternative.



Also, on Roll-A-Rama I created a class called DesktopBox that simply creates six JMEDesktops, InputHandlers for them, and has getters to get a reference to them.  In addition it provides methods to rotate the box from one face to another (and it handles the disabling of the current face and enabling of the next face).  I don't know how useful this would be to jME but thought I would mention it if people think it should be contributed to jME or if people would just like the source code posted online?

darkfrog,



That timer sounds cool, and I'd definetly be interested in that desktop cube thing!!



Maybe you can submit it to jME as a tutorial or in one of the jmetest packages. I know I'm working on a game right now and was trying to come up with a way to integrate the JMEDesktop stuff as my menu system, so it'd definetly be interesting to see how you've utilized it in your game.

+1



Interested too for the BoxDesktop  :smiley:



Why did u do this Box?

I'm thinking at a "configuration menu" with this BoxDesktop, rolling for each menu's panel… which sounds great :slight_smile:

Felt, you can check it out in action: http://rollarama.captiveimagination.com



It’s actually pretty basic, it’s just a lot of code to actually make it work so it probably isn’t necessary to be tutorial, but I’m happy to just post the code online.  It was sort of just thrown together for my cause, but here’s the source code.  I’ve pretty much decided against it being in jME as I don’t really think it belongs there:


/*
 * Created on Jan 12, 2006
 */
package com.captiveimagination.jme;

import java.awt.*;

import com.jme.input.*;
import com.jme.math.*;
import com.jme.renderer.*;
import com.jme.scene.*;
import com.jme.scene.state.*;
import com.jmex.awt.swingui.*;

/**
 * @author Matthew D. Hicks
 */
public class DesktopBox extends Node {
    private static final long serialVersionUID = 1L;

    private static Vector3f temp = new Vector3f();
   
    public static final int NONE = 0;
    public static final int ONE = 1;
    public static final int TWO = 2;
    public static final int THREE = 3;
    public static final int FOUR = 4;
    public static final int FIVE = 5;
    public static final int SIX = 6;
   
    private InputHandler input;
    private Camera camera;
    private JMEDesktop deskOne, deskTwo, deskThree, deskFour, deskFive, deskSix;
    private InputHandler inputOne, inputTwo, inputThree, inputFour, inputFive, inputSix;
    private int position;
   
    private Vector3f cameraLocation;
   
    public DesktopBox(Camera camera) {
        super("DesktopBox");
        input = new InputHandler();
        this.camera = camera;
        init();
        position = ONE;
    }
   
    public void init() {
        inputOne = new InputHandler();
        input.addToAttachedHandlers(inputOne);
        deskOne = new JMEDesktop("DesktopOne", 250, 250, inputOne);
        configure(deskOne);
        deskOne.getLocalTranslation().set(0.0f, 0.0f, 50.0f);
        deskOne.lock();
       
        inputTwo = new InputHandler();
        inputTwo.setEnabled(false);
        input.addToAttachedHandlers(inputTwo);
        deskTwo = new JMEDesktop("DesktopTwo", 250, 250, inputTwo);
        configure(deskTwo);
        deskTwo.getLocalTranslation().set(50.0f, 0.0f, 0.0f);
        deskTwo.getLocalRotation().fromAngleAxis(0.5f * FastMath.PI, new Vector3f(0.0f, 1.0f, 0.0f));
        deskTwo.lock();
       
        inputThree = new InputHandler();
        inputThree.setEnabled(false);
        input.addToAttachedHandlers(inputThree);
        deskThree = new JMEDesktop("DesktopThree", 250, 250, inputThree);
        configure(deskThree);
        deskThree.getLocalTranslation().set(0.0f, 0.0f, -50.0f);
        deskThree.getLocalRotation().fromAngleAxis(1.0f * FastMath.PI, new Vector3f(0.0f, 1.0f, 0.0f));
        deskThree.lock();
       
        inputFour = new InputHandler();
        inputFour.setEnabled(false);
        input.addToAttachedHandlers(inputFour);
        deskFour = new JMEDesktop("DesktopFour", 250, 250, inputFour);
        configure(deskFour);
        deskFour.getLocalTranslation().set(-50.0f, 0.0f, 0.0f);
        deskFour.getLocalRotation().fromAngleAxis(1.5f * FastMath.PI, new Vector3f(0.0f, 1.0f, 0.0f));
        deskFour.lock();
       
        inputFive = new InputHandler();
        inputFive.setEnabled(false);
        input.addToAttachedHandlers(inputFive);
        deskFive = new JMEDesktop("DesktopFive", 250, 250, inputFive);
        configure(deskFive);
        deskFive.getLocalTranslation().set(0.0f, 50.0f, 0.0f);
        Quaternion q = new Quaternion();
        q.fromAngleAxis(1.0f * FastMath.PI, new Vector3f(0.0f, 1.0f, 0.0f));
        deskFive.getLocalRotation().fromAngleAxis(0.5f * FastMath.PI, new Vector3f(1.0f, 0.0f, 0.0f));
        deskFive.getLocalRotation().multLocal(q);
        deskFive.lock();
       
        inputSix = new InputHandler();
        inputSix.setEnabled(false);
        input.addToAttachedHandlers(inputSix);
        deskSix = new JMEDesktop("DesktopSix", 250, 250, inputSix);
        configure(deskSix);
        deskSix.getLocalTranslation().set(0.0f, -50.0f, 0.0f);
        deskSix.getLocalRotation().fromAngleAxis(0.5f * FastMath.PI, new Vector3f(1.0f, 0.0f, 0.0f));
        deskSix.lock();
       
        setCullMode(Spatial.CULL_NEVER);
        setLightCombineMode(LightState.OFF);
        updateRenderState();
        updateGeometricState(0.0f, true);
    }
   
    public int getPosition() {
        return position;
    }
   
    private void configure(JMEDesktop desktop) {
        desktop.setLocalScale(new Vector3f(0.4f, 0.4f, 0.4f));
        desktop.getJDesktop().setBackground(new Color(0.0f, 0.0f, 1.0f, 0.2f));
        attachChild(desktop);
    }

    public void update(float interpolation) {
        if (cameraLocation != null) {
            camera.setLocation(cameraLocation);
            if ((camera.getLocation().x == 0.0f) && (camera.getLocation().z == 0.0f)) {
                camera.lookAt(new Vector3f(0.0f, 0.0f, 0.0f), new Vector3f(0.0f, 0.0f, 1.0f));
            } else {
                camera.lookAt(new Vector3f(0.0f, 0.0f, 0.0f), new Vector3f(0.0f, 1.0f, 0.0f));
            }
        }
        input.update(interpolation);
    }
   
    public void setCameraLocation(Vector3f cameraLocation) {
        this.cameraLocation = cameraLocation;
    }
   
    public JMEDesktop getDeskFive() {
        return deskFive;
    }

    public JMEDesktop getDeskFour() {
        return deskFour;
    }

    public JMEDesktop getDeskOne() {
        return deskOne;
    }
   
    public JMEDesktop getDeskSix() {
        return deskSix;
    }
   
    public JMEDesktop getDeskThree() {
        return deskThree;
    }

    public JMEDesktop getDeskTwo() {
        return deskTwo;
    }
   
    public void moveTo(int endPosition) {
        setEnabled(position, false);
        rotate(250.0f, getPosition(position), getPosition(endPosition));
        setEnabled(endPosition, true);
        position = endPosition;
    }
   
    public void setEnabled(int position, boolean enabled) {
        InputHandler input = null;
        if (position == ONE) {
            input = inputOne;
        } else if (position == TWO) {
            input = inputTwo;
        } else if (position == THREE) {
            input = inputThree;
        } else if (position == FOUR) {
            input = inputFour;
        } else if (position == FIVE) {
            input = inputFive;
        } else if (position == SIX) {
            input = inputSix;
        }
        input.setEnabled(enabled);
    }
   
    public void setEnabled(boolean enabled) {
        if (enabled) {
            inputOne.setEnabled(false);
            inputTwo.setEnabled(false);
            inputThree.setEnabled(false);
            inputFour.setEnabled(false);
            inputFive.setEnabled(false);
            inputSix.setEnabled(false);
            setEnabled(position, enabled);
        } else {
            inputOne.setEnabled(enabled);
            inputTwo.setEnabled(enabled);
            inputThree.setEnabled(enabled);
            inputFour.setEnabled(enabled);
            inputFive.setEnabled(enabled);
            inputSix.setEnabled(enabled);
        }
    }
   
    public static Vector3f getPosition(int position) {
        if (position == ONE) {
            return new Vector3f(0.0f, 0.0f, 250.0f);
        } else if (position == TWO) {
            return new Vector3f(250.0f, 0.0f, 0.0f);
        } else if (position == THREE) {
            return new Vector3f(0.0f, 0.0f, -250.0f);
        } else if (position == FOUR) {
            return new Vector3f(-250.0f, 0.0f, 0.0f);
        } else if (position == FIVE) {
            return new Vector3f(0.0f, 250.0f, 0.0f);
        } else if (position == SIX) {
            return new Vector3f(0.0f, -250.0f, 0.0f);
        }
        return null;
    }
 
    private void rotate(float distance, Vector3f start, Vector3f end) {
        float inc = 0.01f;
        start.subtract(end, temp);
       
        if (temp.x == 0) {
            if (FastMath.abs(end.y) < FastMath.abs(start.y)) {
                inc = -inc;
            }
        } else if (temp.y == 0) {
            if (FastMath.abs(end.x) < FastMath.abs(start.x)) {
                inc = -inc;
            }
        } else if (temp.z == 0) {
            if (FastMath.abs(end.x) < FastMath.abs(start.x)) {
                inc = -inc;
            }
        }
        float r = distance;
        float a;
        float x = 0.0f;
        float y = 0.0f;
        float z = 0.0f;
       
        float angle = 0.01f;
        if (inc < 0) {
            angle = 0.49f;
        }
        for (int i = 1; i < 50; i++) {
            angle += inc;
            a = 2 * r * FastMath.sin(0.5f * angle * (FastMath.PI * 2));
           
            if (temp.x == 0) {
                y = ((a * a) / (2 * a));
                if ((start.y < 0) || (end.y < 0)) {
                    y = -y;
                }
                z = FastMath.sqrt((r * r) - (y * y));
                if ((start.z < 0) || (end.z < 0)) {
                    z = -z;
                }
            } else if (temp.y == 0) {
                x = ((a * a) / (2 * a));
                if ((start.x < 0) || (end.x < 0)) {
                    x = -x;
                }
                z = FastMath.sqrt((r * r) - (x * x));
                if ((start.z < 0) || (end.z < 0)) {
                    z = -z;
                }
            } else if (temp.z == 0) {
                x = ((a * a) / (2 * a));
                if ((start.x < 0) || (end.x < 0)) {
                    x = -x;
                }
                y = FastMath.sqrt((r * r) - (x * x));
                if ((start.y < 0) || (end.y < 0)) {
                    y = -y;
                }
            }
            cameraLocation = new Vector3f(x, y, z);
           
            try {
                Thread.sleep(10);
            } catch(InterruptedException exc) {
                exc.printStackTrace();
            }
        }
    }

    public void dispose() {
        getDeskOne().dispose();
        getDeskTwo().dispose();
        getDeskThree().dispose();
        getDeskFour().dispose();
        getDeskFive().dispose();
        getDeskSix().dispose();
    }
}

The demo is exactly what i was thinking about :slight_smile:

I'll surely find something to do with it :wink:



Nice job & thanks!

Darkfrog,



Did you make this DesktopBox in a different GameState then the game itself and if so how did you get it to work?



I'm making a game and want my menu's to be handled by GameState then the game itself, but with JMEDesktop I get a GL13 error.

I know that all GL call must be made from the same thread, but does this mean I can't use JMEDesktop or is it unwise to handle my menu's and the game in different threads.



I'm still pretty new to JME and i'm trying to switch from simpleGame to StandardGame, but it's not as easy as you say.

I'm still pretty new to JME and i'm trying to switch from simpleGame to StandardGame, but it's not as easy as you say.


Well that's disheartening.

There are places in StandardGame where you really have to have a good understanding of threads to understand the cause of your problems, but in many cases if you can track down where the exception is being thrown and just use the GameTaskQueueManager to get it into the OpenGL thread you can typically resolve it pretty easily.

Yes, I did make DesktopBox in a separate GameState and getting it to work was actually quite straight-forward in comparison to non-StandardGame implementation.  Drop some code on here and lets see if we can figure out what it is that's causing the problems.

Actually I'm very lazy. I just want things to work without to much research. I'll dive into the GameState System and figure it out. If I have more questions I'll certainly post them.



No offence to you darkfrog, your tutorials are great. Do you plan to finish the SimpleGame to StandardGame tut any time soon. I do not intent to rush, but I just curious.

Yeah, I do, I just got sidetracked with my job…I know, I know, bad excuse.  :stuck_out_tongue:

darkfrog said:

There are places in StandardGame where you really have to have a good understanding of threads to understand the cause of your problems, but in many cases if you can track down where the exception is being thrown and just use the GameTaskQueueManager to get it into the OpenGL thread you can typically resolve it pretty easily.


I'll note that the 'assert DisplaySystem.inRenderThread()' calls I've added to my local copy (see http://www.jmonkeyengine.com/jmeforum/index.php?topic=3642.60) make it much easier to track down these subtle StandardGame bugs -- you can see right away when you call a method which needs to be in the GL thread (well, once you turn on assertions).

What bugs in StandardGame are you referring to?  If you are simply referencing aspects of jME that must be executed in the OpenGL thread I would not call those bugs for StandardGame as it has nothing to do with them.  It really isn't truly a bug because up until recently jME was not intended for multithreading and we're still slowly just working through trying to resolve threading issues.



Further, knowing if you're in the render thread doesn't really help you to know whether you should be executing the code in the render thread, it simply tells you where you are which the application should already know (as stated over and over).



The thing that really needs to happen is to either make all jME calls able to be executed outside the OpenGL thread or document better what aspects must be invoked there.

darkfrog said:

It really isn't truly a bug because up until recently jME was not intended for multithreading and we're still slowly just working through trying to resolve threading issues.


Well, I'm trying to help you work through & resolve these, but it's certainly an uphill battle!
cananian said:

darkfrog said:

It really isn't truly a bug because up until recently jME was not intended for multithreading and we're still slowly just working through trying to resolve threading issues.


Well, I'm trying to help you work through & resolve these, but it's certainly an uphill battle!


I definitely appreciate your willingness to help, and I can definitely understand your plight.  Though not bad ideas you've enountered a lot of apprehension for ideas based on disagreement in project focus.  Don't give up your willingness to help, but you do have to be willing to work within the project goals and willing to give-in if it is decided that we're not going to accept all of the ideas given (I had to accept that one early on....the majority of my first several ideas when I first came to jME were rejected outright).

So what StandardGame bugs are you referencing or were you simply referring to thread-safety issues?  Definitely this is something we could use more eyes on as it's not a high priority for most of the devs here.  I think Llama and I are the only ones really doing any active development for threading (although I know renanse has made some references too, but I'm not sure how much he's doing multithreaded).

Just for the sake of argument, is there any circumstance in anything you're currently working on that the inGLThread() is absolutely necessary?

It's necessary to make the core APIs of jME reasonably thread-safe.  It also helps the client developer make their own classes thread safe.  I've described two uses so far: one fixes a mostly-theoretical threading issue in LWGLTextureState, and the other fixes a threading issue in StandardGameState which has repeatedly bit developers – to the extent that you have now deprecated StandardGameState in an attempt to avoid having to fix the bug.  Camera transformations are another source of problems: many operations on Cameras invoke GL methods as a side effect; these could (and should) be made thread-safe as well, by the same mechanism.  TextureState.load() might be another candidate to make thread-safe, since it is used during texture animation in cases like HUDs where the updates might come from a different thread.



The goal should be to (a) document clearly methods which must be called from the GL thread, and (b) make useful methods safe to call from any thread.  Ideally, the methods marked in (a) should be obvious to the programmer without having to consult the javadoc – ie, Renderer.draw() and related methods.  But method (b) should be employed where possible (since the overhead is very low!) to eliminate gotchas for the programmer.  There is nothing obvious about Camera or about StandardGameState that requires them to be used in the GL thread; it is trivial to make them thread-safe using the mechanism I've proposed.



(Note that the overhead of using GameTaskQueue.update() is two object allocations in the single-threaded case.  The places where I've shown its use have been in initialization code, where the allocation is unimportant.  For making Camera thread-safe, you would eliminate the object allocation with an explicit call to DisplaySystem.inRenderThread(), possibly costing a little bit of code duplication, since the camera methods might be called in every update and removing the allocation is important.  I can provide code samples to illustrate this if you like.)

The reason I've deprecated StandardGameState is primarily due to naming confusion.  People tend to think it is supposed to be used with StandardGame.  It is not due to bugs that this is problematic, but rather that the two are incompatible since StandardGame currently provides its own Camera (which I'm considering rethinking because there might be situations where more than one camera might be needed) and StandardGameState creates another Camera.  These causes a great deal of confusion and since it really doesn't offer anything in that class besides creating of a camera I think it's better off removed.



I'm personally not opposed to adopting GameTaskQueueManager as a non-optional feature for people doing multithreading…I would tend to agree with that thinking, but at the same time creating of an extra Callable and Future per invocation in situations where its single-threaded causes lots of wasted time and memory. It's primarily from the single-threaded devs here that you'll find kick-back on this because it's adding overhead for something they don't need.



We need to find a way to provide thread-safety without sacrificing any performance on the single-threaded user side (and ideally without adding any overhead to the multithreaded side).  Perhaps an LWJGL instruction queue for lower-level instructions or providing some better handling in the scenegraph of things like texture loading so they get a flag set to load and then on an update or controller they get invoked (someplace guaranteed to be in the GL thread).

Please re-read my post.  The "simple" way to code the thread-safety does add two-object allocations, even for single-threaded mode.  But that is only to be used in initialization code, where the object allocations are harmless.  The core 'inRenderThread' method doesn't add any allocation overhead.  For performance-critical methods (like in Camera), the thread-safety would look like this:


if (DisplaySystem.inRenderThread())
     doX();
else
     GameTaskQueueManager.getManager().update(new Callable<Void>() {
         public Void call() { doX(); }
     });



So you get a little bit of code duplication, but no allocation overhead for the single-threaded case.  It's a little more complicated, so this would only be used for performance-sensitive code.  But obviously, you need the 'inRenderThread()' infrastructure present to be able to do this.

I'm not opposed to other ways of solving the threading issue, but I'm not seeing any of that effort happening in the codebase.  The problem with the 'flag setting' approach is somehow ensuring that you don't get 1 frame latency: setting the flag *after* you'd already done the check "in this frame" and so not actually having the flag's effects occur until the next frame.  Being able to check 'inRenderThread()' and doing the computation immediately if true prevents the latency in the single-threaded case.

Duplication of code is bad if we can avoid it, I assume you'll agree.  In the "non-performance-critical" methods you've got two extra objects being created.  This is what I'm trying to avoid.



If we do as I suggested and have some sort of enqueuing of work that needs to be done on the Renderer level or looks at changes in the spatials then it can better handle all situations and it's completely seamless to the developer.

I look forward to seeing your code, and how you solve the latency problems.

What I'm suggesting is what jME already does internally for other things.  It's just certain aspects of jME don't already do it.



For example, say you decide to create a new texture outside the OpenGL thread, if the texture instantiation code were to set values in an object that would be parsed by an update() call for anything specific to LWJGL then we could work without regard to threading at all and it would simply be managed "elsewhere" where it is guaranteed to be in the OpenGL thread.

I suggest you start by looking at LWGLCamera.onFrustumChange/onViewPortChange/onFrameChange.  If those methods could be deferred until render, then (a) some redundant work will be eliminated, in cases where the camera is updated multiple times per frame (ie, both by a controller and by an input handler), and (b) most of the thread-unsafety in jME (that I know about, at least) would be eliminated.  For example, StandardGameState would be thread-safe again.



It seems like Texture.load() could be make thread-safe in this manner as well, since there is already code in Texture.apply (I think) to load a texture if it is not already loaded; we'd only need a method to invalidate the old texture without forcing the immediate load of the new one (maybe this method exists already and I don't know it?).