Thread problem with JMEDesktop - random?

Hey!



I've got another strange error with the JMEDesktop.

First I explain what I am doing:



1.) in the init() method i create a new Thread (to avoid deadlock between main thread and the OpenGl thread in which the JMEDesktop needs to be created)

2.) this thread creates a number of JMEDesktops

3.) which start their specific swing-ui-creation Runnable. This is either invoked bei SwingUtilities.invokeLater/AndWait or simply bei new Thread(runnable).start();



Now the problem:

Not every time the Desktops are created and the programm starts every desktop is displayed - it's less then a 50:50 chance that a desktop is shown. When I want to add swing ui to a desktop (in the runnable) it hasn't been displayed once.



My understanding of the problem:

I think there is somewhere a multithreading problem (very wise…), but which all checking and debugging I can't see an error. Swing UI is created whithin a swing event dispatcher thread (and only accessed in one), the other Nodes (Boxes etc.) are created even when the Desktop isn't displayed - and the hitbox (debug mode "b") is there at any time, whether the desktop is displayed or not.



I hope for advices,

thanx in advance,

kain

The solution is simple: you cannot use thread in this case - dont try to :)  (some operations must be in GL thread others in AWT event thread, no options)

sorry, i think my description was unclear.



to avoid the deadlock, i create a thread - in this thread the JMEDesktop3d is invoked into the OGL Thread by Future.get and later the UI is invoked via the Swingutilities.



so UI creation is in the Swing thread, JMEDesktop creation is in the OGL Thread (only the new JMEDesktop - is this enough?)

So everything is at the right place, isn't it?



Otherwise, how to create a JMEDesktop with a running engine? init() blocks the ogl thread - but needs the JMEDesktop to be created by it. Deadlock?



Thanks for your VERY fast response,

kain

Um, I'm not aware of any deadlock problems with creating a JMEDesktop. It should be fine without any additional threads…

as you said, some calls must be in specific threads, as the JMEDesktop.setup() Method must be in the OpenGL thread. It needs to be invoked in it with the Future.get() method.



The deadlock here is the init() method of a Gamestate, which blocks the Opengl thread as long as it is executed. So the setup can't be done in the Ogl thread as it is blocked. To solve this problem I simply created a new Thread that could be blocked until Future.get() has returned my JMEDesktop.



I can't see a solution, please help :wink:



edit if you want, i can give you svn access to my code for a better look, please tell me!

Ah, so this is related to StandardGame and it's states. Now I get it. Darkfrog, can you comment on this? How is this usually done with StandardGame?

There is a JMEDesktopState which you can use.

Also make sure, that StandardGame has been started before you create the JMEDesktopGameState.



'desktop = future.get()' will only return, if StandardGame has been started.

yes, but in this case i wanted to create the desktops in an other gamestate, without using the JMEDesktopState - i don't need the GameState functionality at this position.



But I use nearly the same call hierarchie as used in the JMEDesktopState, so my creation and the JMEDestopState should be nearly identical.



And my StandardGame is definitively startet, as the first "entry" Gamestate (and so my Desktops) are created after the Standardgame has been started.

Are you calling get() on your Future within the update thread of OpenGL (that would be anything that executes within the update or render methods of a GameState)?  That would cause the OpenGL to pause for the Future to return, but since the OpenGL thread is being blocked it will never execute the GameTaskQueue's update method.

No, it is called within an newly started thread started by the Gamestate.init() method to avoid the problem you described



the GameTaskQueue gets executed because sometimes the Desktop is shown as it should be (experimenting with 5 desktops 2-3 are usually shown) and movehandler are working as well (i could move, so update() is called)



1.) init() method of my gamestate is called

2.) init() creates a new Thread

3.) thread creates one JMEDesktop by another

4.) in the creation the Future is called

5.) after the call Future.get() is awaited

6.) my swing runnable is invoked via SwingUtilities.invokeLater/AndWait

thats exactly what I am doing.



thanks for your help - I am very impressed that the developers are helping here within a reaaally short time! great work!

Why is there any form of synchronisation and blocking, surely a fifo concurentQueue is the way to go.

I'm doing what is necessary to avoid the deadlock…but my desktops aren't always shown when they should be!

Well, let me ask this. Why are you calling future.get() at all?  Why do you need blocking for the future to return before you continue? If there is something else that needs to happen why not just push that off to the future as well?  I'm not sure what's causing your problem, but locking threads with future.get() is definitely not preferred treatment of threads if there's an alternative.

i don't understand that.



do I really need to render the boxes by hand?



but why on earth is it shown SOME times???



either not a single time, or everytime - is that wrong?





the GameTastQueueManagers update and render methods need a callable…but what should be in it? nodes.render() and nodes.update() calls?

Check out the testGameStateSystem in the jME examples, it shows how to use the GameStates the darkfrog is talking about.

so, I'm still on that problem, was busy some time…



I've tried what you have mentioned - moving everything to the future. still the same problem…



Here is my code…please have a look!


import java.util.concurrent.Callable;
import javax.swing.SwingUtilities;
import com.jme.input.InputHandler;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.scene.Node;
import com.jme.scene.shape.Box;
import com.jme.util.GameTaskQueueManager;
import com.jmex.awt.swingui.JMEDesktop;
import Desktops.DesktopExecutor;


public class TestDesktopArray extends Node {
   
   private static final long serialVersionUID = 13221;
   
   private Node desktops[];
   
   private InputHandler input;
   
   public TestDesktopArray(final int DesktopCount, final int dist, final int size, final int height, final float angle, final DesktopExecutor executors[])
   {
      
      desktops = new Node[DesktopCount];
      
      input = new InputHandler();
      
      GameTaskQueueManager.getManager().update(new Callable<Object>() {
         public Object call() throws Exception {
            
            
            for (int i=0; i<DesktopCount; i++)
            {
               desktops[i] = new Node();
               
               //creating the JMEDEsktop
               JMEDesktop t = new JMEDesktop("Desktop", size*2, size*2, input);
               
               // starting the Swing UI-Creation
               executors[i].setDesktopPane(t.getJDesktop());
               //SwingUtilities.invokeLater(executors[i]);
               SwingUtilities.invokeAndWait(executors[i]);
               
               // moving the JMEDesktop and the box to the right place
               t.getLocalRotation().fromAngles(-angle * FastMath.DEG_TO_RAD, 0, 0);
               t.setLocalTranslation(0, height + size* FastMath.sin(FastMath.DEG_TO_RAD * angle),-size * FastMath.cos(FastMath.DEG_TO_RAD*angle));

               Box b = new Box("Box Desktop",new Vector3f(0,0,0), size, height, 1);
               
               desktops[i].attachChild(t);
               desktops[i].attachChild(b);
               
               // arranging the Desktop + Box in a circle
               desktops[i].setLocalTranslation(dist * FastMath.sin(i*360f/DesktopCount * FastMath.DEG_TO_RAD),
                     0,
                     dist * FastMath.cos(i*360f/DesktopCount * FastMath.DEG_TO_RAD));
               desktops[i].getLocalRotation().fromAngleAxis(180 * FastMath.DEG_TO_RAD + i*360f/DesktopCount * FastMath.DEG_TO_RAD, new Vector3f(0,1,0));



               TestDesktopArray.this.attachChild(desktops[i]);
               TestDesktopArray.this.updateRenderState();
               
            }
            return null;
         }
        });
      
   }

}



and the second file:

import info.clearthought.layout.TableLayout;

import java.awt.Color;

import javax.swing.JButton;
import javax.swing.JDesktopPane;
import javax.swing.JPanel;
import Desktops.DesktopExecutor;
import com.jme.app.SimpleGame;
import com.jme.input.InputHandler;
import com.jme.input.KeyboardLookHandler;


public class SimpleDesktopArray extends SimpleGame {

   /**
    * @param args
    */
   public static void main(String[] args) {
      // TODO Auto-generated method stub

      new SimpleDesktopArray().start();
   }
   
   public SimpleDesktopArray()
   {
      this.setDialogBehaviour(SimpleDesktopArray.FIRSTRUN_OR_NOCONFIGFILE_SHOW_PROPS_DIALOG);
   }

   @Override
   protected void simpleInitGame() {

      InputHandler handlerForDefaultKeyActions = input;
        handlerForDefaultKeyActions.removeAllFromAttachedHandlers();
       
        // input handler
        input = new InputHandler();
        input.addToAttachedHandlers( handlerForDefaultKeyActions );
       
        //cn = new CameraNode("cn", display.getRenderer().getCamera());
       //rootNode.attachChild(cn);
       
        KeyboardLookHandler lookHandler = new KeyboardLookHandler( cam, 50,1 );
        input.addToAttachedHandlers( lookHandler );
      
      /////////////////////////////////////////
        /////////////////////////////////////////
       
        DesktopExecutor exec[] = new DesktopExecutor[3];
       
        exec[2] = new DesktopExecutor()
        {
           public void run()
           {
            JDesktopPane pane = desktoppane;
            
            pane.setBackground(Color.blue);
            
            pane.validate();
            pane.repaint();
           }
        };
        exec[1] = new DesktopExecutor()
        {
           public void run()
           {
            JDesktopPane pane = desktoppane;
            
            pane.setBackground(Color.blue);
            
            pane.validate();
            pane.repaint();
           }
        };
        exec[0] = new DesktopExecutor()
        {
           public void run()
           {
            JDesktopPane pane = desktoppane;
            
            JPanel p = new JPanel();
               pane.add(p);
            
            double size[][] = {{TableLayout.FILL},{TableLayout.FILL}};
            
            p.setLayout(new TableLayout(size));
            
            p.setBackground(Color.red);
            p.add( new JButton("test"), "0,0");
            
            p.validate();
            p.repaint();
            pane.validate();
            pane.repaint();
           }
        };
       
        final TestDesktopArray array = new TestDesktopArray(3, 200, 300, 10, 45f, exec );
       
        array.setLocalScale(0.2f);
       
        rootNode.attachChild(array);
       
       
   }

}



the DesktopExecutor:

package Desktops;

import javax.swing.JDesktopPane;

public abstract class DesktopExecutor implements Runnable {

   protected JDesktopPane desktoppane;
   
   public void setDesktopPane(JDesktopPane pane)   
   {
      desktoppane = pane;
   }
   
   public JDesktopPane getDesktopPane()
   {
      return desktoppane;
   }
   
   public abstract void run() ;
}



with this version, the desktops nearly aren't shown at all...but if you hit b you see that the Quad has been created...

Ummm…you're using SimpleGame…it does not appear as you're ever calling the GameTaskQueueManager's update and render methods in the corresponding simpleUpdate and simpleRender?  In SimpleGame it does not inherently support GameTaskQueue, you have to add the support. I believe StandardGame is the only class that inherently provides support for GameTaskQueue.

yeah…i had a look at it.



but my problem here is that the GameTaskQueueManager.update/render just want to have a callable. I tried getRenderer.draw(my nodes) in it, but that doesn't give the trick…



i tried it also with a standardgame…but doesnt work either

Example of using the GameTaskQueueManager:


            GameTaskQueueManager.getManager().update(new Callable<Object>() {
                public Object call() throws Exception {
                    Node.lock();
                    return null;
                }
            });



What this does is locks (creates a display list) a Node in the OpenGL thread, this example shows one of the few things that MUST be processed in the rendering thread; code outside of this area is processed in a seperate (non OpenGL) thread.

I don't think you should be using GameTaskQueueManager to schedule a render, but I could be wrong here.  That is what the render and update methods in each State you create are for.

the thing i don't really understand is this:

- the box below the JMDesktop and the hitbox (Debug-Gamestate button "b") are shown everytime
- the JMEDesktop is shown some times...

so the whole node is rendered, and it is also created and the engine knows it, but the texture on the JMEDesktops isn't there at any time.

What does this texture affect?
it's all executed within the OGL Thread...so that could not be the problem.
it's shown severall times...so missing calls aren't a problem
it's bound box is shown every time...so creation is ok, at least to a degree