JMEDEsktopState "hangs"

Me again with a wired problem:



I have three GameStates: a "normal" BackgroundGameState (derived from BasicGameState), and MenuGameSate and VideoMenuGameState (both derived from JMEDesktopState). Every state on it's own is working. I try to do the following:



    public static void main(String[] args) throws Exception {
        preferences = ...       
        gameSettings = new PreferencesGameSettings(preferences);
        game = new StandardGame("KanjiTori", StandardGame.GameType.GRAPHICAL, gameSettings);
        game.setBackgroundColor(new ColorRGBA(0.1f, 0.5f, 0.4f, 0f));
        game.start();
        setMenuGameState();
   }
   
    private static void setState(GameState gameState) {
         GameStateManager.getInstance().attachChild(gameState);
         gameState.setActive(true);
    }
   
    private static void clearStates() {
        GameStateManager.getInstance().deactivateAllChildren();
        GameStateManager.getInstance().detachAllChildren();
    }

    public static void setMenuGameState() {
        clearStates();
        setState(new BackgroundGameState("data/menu/menuMain.png"));
        setState(new MenuGameState());
    }
   
    public static void setVideoGameState() {
        clearStates();
        setState(new BackgroundGameState("data/menu/menuVideo.png"));
        setState(new VideoMenuGameState(gameSettings)); //<


FREEZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
    }



When I call setVideoGameState, the VideoMenuGameState "freezes". If I comment out that call, BackgroundGameState works fine. The problem must be in JMEDesktopState, because I can't even call System.out.println() in the constructor of VideoMenuGameState, and if I replace that line with setState(new JMEDesktopState()); I get the same behavior: I don't get an error, the app is just frozen.
First I thought that the things I do in clearState() could be the reason, but commenting it out had no effect.
Then I thought, maybe I'm in thread-trouble because that method is called by an ActionListener, so I tried to wrap the call with GameTaskQueueManager.getInstance().update(), also without success.

Everything was working fine as long as I created all my gameStates at the beginning, and only activated and deactivated it. However the current change is required, because I want to be able to change the resolution in-game, and that create-everything-from-scratch-design would save me from all kinds of crazy reinitializations. Any ideas to save the noob?

I think I know what causes the problem: JMEDesktopState waits on the result of constructing an JMEDesktop:



//in ini(), called by constructor:

Future<JMEDesktop> future = GameTaskQueueManager.getManager().update(new Callable<JMEDesktop>() {
   public JMEDesktop call() throws Exception {
     if (variableDesktopSize) {
         return new JMEDesktop("Desktop", DisplaySystem.getDisplaySystem().getWidth(), DisplaySystem.getDisplaySystem().getHeight(), guiInput);
     } else {
        return new JMEDesktop("Desktop", width, height, guiInput);
     }
   }
});
...
desktop = future.get();



So it seems I have a deadlock :S

I tried everything: GameTaskQueueManager, SwingUtilities, a simple Thread.start, nothing works. I can choose between a deadlock, or this error message:



java.lang.IllegalStateException: not in swing thread!
        at com.jmex.awt.swingui.JMEDesktop.dispatchEvent(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.setFocusOwner(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.setup(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.<init>(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.<init>(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktopState$1.call(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktopState$1.call(Unknown Source)
        at com.jme.util.GameTask.invoke(Unknown Source)
        at com.jme.util.GameTaskQueue.execute(Unknown Source)
        at com.jmex.game.StandardGame.update(Unknown Source)
        at com.jmex.game.StandardGame.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:595)
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: not in swing thread!
        at com.jme.util.GameTask.invoke(Unknown Source)
        at com.jme.util.GameTaskQueue.execute(Unknown Source)
        at com.jmex.game.StandardGame.update(Unknown Source)
        at com.jmex.game.StandardGame.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:595)
Caused by: java.lang.IllegalStateException: not in swing thread!
        at com.jmex.awt.swingui.JMEDesktop.dispatchEvent(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.setFocusOwner(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.setup(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.<init>(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktop.<init>(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktopState$1.call(Unknown Source)
        at com.jmex.awt.swingui.JMEDesktopState$1.call(Unknown Source)
        ... 5 more



Note that I've removed all GUI code from my state, the problem is in JMEDesktopState...

Here is a minimal example reproducing the problem. If you press the button (because it says so), you see the output "start", but then everything is frozen.



import com.jme.input.MouseInput;
import com.jme.util.GameTaskQueueManager;
import com.jmex.awt.swingui.JMEDesktopState;
import com.jmex.game.StandardGame;
import com.jmex.game.state.GameStateManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.concurrent.Callable;
import javax.swing.JButton;

/**
 *
 * @author Pirx
 */
public class JMEDesktopDeadlock {
    public static void main(String... args) {
        StandardGame game = new StandardGame("JMEDesktopDeadlock");
        game.start();
        JMEDesktopState state = new JMEDesktopState() {
            protected void buildUI() {
                JButton button = new JButton("Press me!");
                button.setSize(100,100);
                button.setLocation(200,200);
                button.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        System.out.println("start");
                        JMEDesktopState newState = new JMEDesktopState();
                        System.out.println("ready");
                        GameStateManager.getInstance().attachChild(newState);
                        newState.setActive(true);
                    }
                });
                getDesktop().getJDesktop().add(button);
                GameTaskQueueManager.getManager().update(new Callable<Object>() {
                    public Object call() throws Exception {
                        MouseInput.get().setCursorVisible(true);
                        return null;
                    }
                });
            }
        };
        GameStateManager.getInstance().attachChild(state);
        state.setActive(true);
    }
}



Note that even when you use GameTaskQueueManager, e.g. by using this version of the ActionListener, the code doesn't work:


             button.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent ae) {
                        GameTaskQueueManager.getManager().render(new Callable<Object>() {
                            public Object call() throws Exception {
                                System.out.println("start");
                                JMEDesktopState newState = new JMEDesktopState();
                                System.out.println("ready");
                                GameStateManager.getInstance().attachChild(newState);
                                newState.setActive(true);
                                return null;
                            }
                        });
                    }
                });

Similar to GameTaskQueue I believe Irrisor wrote some functionality to inject code into the Swing thread…I can't remember what that is off the top of my head though.  Perhaps that would help.

I realise that the last post on this was back in July, but I've just encountered the same deadlock problem with JMEDesktop… and there was no solution posted… arrrrrgh!!!



So having hacked about I've found a solution to the deadlock and thought I'd post it for future prosperity.



For me the deadlock was caused by waiting on the OpenGL queue,



 Future<JMEDesktop> future = GameTaskQueueManager.getManager ().update (new Callable<JMEDesktop>()
{
     public JMEDesktop call () throws Exception
     {
           return new JMEDesktop ("Desktop", width, height , input);
      }
});

desktop = future.get();    // Deadlock here - process hangs



and my solution was.....


...
[b]GameTaskQueueManager.getManager ().getQueue (GameTaskQueue.UPDATE).execute ();[/b]
desktop = future.get(); 



and hey presto no more deadlock!

(I'm sure there's a much better way to achieve the same effect without forcing the queue update, but I couldn't find it)

That COMPLETELY defeats the purpose of injecting into the GameTaskQueueManager if you're going to immediately invoke the execute you may as not put it in there in the first place.  Further, you're potentially causing other issues if something else got injected into the GameTaskQueueManager that would then be invoked in whatever thread that is.

Darkfrog your completely right, both are fair and valid points (it was a hack of a solution).



Admittedly I now feel a bit noobish, as I went back and tried it without using the concurrency classes (no insertion into OpenGL thread) and it worked, very strange because it did not before and I have absolutely no idea why.



I believe my misunderstanding came from an idea that JMEDesktop needed to be created being inserted into the OpenGL thread, this simply seems not to be the case.



Apologies.

No problem…though not always the most enjoyable way to learn, the majority of learning comes from our mistakes. :slight_smile:

Landei said:

I think I know what causes the problem: JMEDesktopState waits on the result of constructing an JMEDesktop.


That means JMEDesktopState only works in conjunction with Multithreadiness right ?
If i use a single threaded App, i need to create my own copy of JMEDesktopState without the GameTaskQueueManager stuff in it?

I ask, because at the moment i play around with GameStates and VariableTimestepGame (which is single threaded).

Maybe a 2nd constructor with a boolean multiThreaded would be nice to have.

if multiThreaded == false, don't make use of GameTaskQueueManager.



Or alternatively a constructor where i could give a reference to JMEDesktop as a parameter, then it wouldn't need to be created in the init() function.



I am not sure whats the best solution.

This situation is very similar to the one observed in ModelLoader, and I am suggesting a workaround (not extremely clean, I know, but if you use GameQueues then it cannot be very clean). See this thread for reference: http://www.jmonkeyengine.com/jmeforum/index.php?topic=4735.0