How to launch one StandardGame from another StandardGame

I would like to start up my Server when I click a button in my Client. However I also want the Server and the Client to be separate processes - have separate windows etc. How can I go ahead with this?



I have been advised to use: Runtime.getRuntime().exec()



However having been a Java dev only for a year and always using IDE (or just deploying web-applications on Tomcat), I have no experience with command line. I have found a guide to this command on this page, but I do not really even understand what I need to do to get my app working the way I want it… so I am not only stuck on syntax but the general idea also.



My project is set up currently like this:

Project folder - c:/project

Necessary libs 1 - c:/project/libs/libs1

Necessary libs 2 - c:/project/libs/libs2

Native libs - c:/project/libs/native

Client class - c:/project/src/package/TestClient.java

Server class - c:/project/src/package/TestServer.java



What would be the best way for me to launch this TestServer from within my TestClient static method? This does not obvjously work… but im stuck…

Runtime.getRuntime().exec(“java -cp C:/project/lib package.TestServer”);



Is this .exec really the best and only way? Any and all help would be very appreciated.

Is the server and client always running on the same machine? if yes the system exec is not such a bad idea i guess.



If not, maybe you could call a php script from the client which starts the server (if a webserver runs of course).

Or you could make a lightweight java server process which always runs, the client can connect to the lightweight server which then starts the real server (through a socket or RMI connection).

Yes pretty much always on same machine. I expect sometimes to run the server alone in dedicated mode, but usually together with Client.



EDIT: Nevermind no need to reply any more… I am going another route with this

Another option would be to use ClassLoading hierarchy to support this.  It would mean a single JVM that is running both (which will increase the demands on the single VM, but probably overall decrease the memory usage on the machine).  Simply put you'd have an initialization ClassLoader (that would not have any reference to your required JARs - that is, if you're concerned about confusion between static instancing between server and client) and you create a new ClassLoader beneath it to run your client. You would reference all JARs necessary in that ClassLoader.  Your outter ClassLoader system would simply need to have a method "startServer()" to start the server when the client needs it to start and it could create another ClassLoader environment underneath the top-level and load all the JARs necessary for the server.  This is actually very similar to the way multiple web applications are deployed to a single web server.  There's a little bit of a learning curve, but it's really not as bad as it may seem…and very good knowledge to have.

You can create a batch file also and have

Runtime.getRuntime().exec("my command")

launch the app

batch file != cross-platform

fine - just know what you platform you want to support - no real diference to supporting shaders, different depth bits, no if textures .



But if you want to open a new JVM …

What worked for me…



I wanted to launch server when I push a button on the client. I accepted that the server must be in headless mode in this case. However when I start the server as a standalone app, it would have graphics also. Since I had problems with (I think it is impossible?) with just firing up a new StandardGame when one is running already (well, simple methods I mean), I modified a few bits and pieces.



GameStateManager - stopped being a singleton

StandardGame - Got its own GameStateManager, available everywhere with standardGame.getGameStateManager(), which would return the gamestatemanager for this instance of StandardGame. I also removed the use of DummyDisplaySystem from StandardGame and stopped it from running the render method when it was in headless mode.



It can all be probably done in a more eloquent way but well… thats what I did. It ended up allowing me to just call server = new Server(); which starts up a new headless StandardGame. It works quite well for me right now… I just keep remembering Darkfrogs words that 'if you find yourself changing or extending StandardGame, you must be doing something wrong' :slight_smile:

Runtime.exec is replaced by the newer ProcessBuilder since Java 1.5 and should be favored above runtime.exec().



Read this page: http://www.rgagnon.com/javadetails/java-0014.html and mess arround with the example. There are more examples on the internet though, just use google.



I managed to start a process using SSH on a different server with the ProcessBuilder so I'd imagine this does everything you want. Take note however that you have to think of a way to interact with the process. JGN could be an option.



Edit: Note that starting a new process will start a new JVM, in case it wasn't clear.  :slight_smile:

darkfrog said:

Another option would be to use ClassLoading hierarchy to support this...


I guess, this is the most beautiful solution but you may consider memory limitation.  :wink:

Also note that enterprise application servers (JBoss, BEA Weblogic, Websphere...) use intensively this concept of multiple classloader.

The wikipedia page contains interesting links (bottom).

N.
Mindgamer said:

What worked for me...

I wanted to launch server when I push a button on the client. I accepted that the server must be in headless mode in this case. However when I start the server as a standalone app, it would have graphics also. Since I had problems with (I think it is impossible?) with just firing up a new StandardGame when one is running already (well, simple methods I mean), I modified a few bits and pieces.

GameStateManager - stopped being a singleton
StandardGame - Got its own GameStateManager, available everywhere with standardGame.getGameStateManager(), which would return the gamestatemanager for this instance of StandardGame. I also removed the use of DummyDisplaySystem from StandardGame and stopped it from running the render method when it was in headless mode.

It can all be probably done in a more eloquent way but well.. thats what I did. It ended up allowing me to just call server = new Server(); which starts up a new headless StandardGame. It works quite well for me right now... I just keep remembering Darkfrogs words that 'if you find yourself changing or extending StandardGame, you must be doing something wrong' :)


Perhaps I should add on to that statement, "...or StandardGame needs to be patched". :)  Feel free to submit a patch to support this functionality directly in StandardGame.

Well here it is if anyone thinks they can use it…



The patch - its the first one I have ever created, so sorry if something is wrong…


### Eclipse Workspace Patch 1.0
#P jME v1
Index: src/com/jmex/game/StandardGame.java
===================================================================
RCS file: /cvs/jme/src/com/jmex/game/StandardGame.java,v
retrieving revision 1.25
diff -u -r1.25 StandardGame.java
--- src/com/jmex/game/StandardGame.java   20 Jan 2008 18:46:45 -0000   1.25
+++ src/com/jmex/game/StandardGame.java   1 Jul 2008 21:05:01 -0000
@@ -53,14 +53,15 @@
 import com.jme.system.DisplaySystem;
 import com.jme.system.GameSettings;
 import com.jme.system.PreferencesGameSettings;
-import com.jme.system.dummy.DummyDisplaySystem;
 import com.jme.util.GameTaskQueue;
 import com.jme.util.GameTaskQueueManager;
 import com.jme.util.NanoTimer;
 import com.jme.util.TextureManager;
 import com.jme.util.Timer;
 import com.jmex.audio.AudioSystem;
+import com.jmex.game.state.GameState;
 import com.jmex.game.state.GameStateManager;
+import com.jmex.game.state.GameStateNode;
 
 /**
  * <code>StandardGame</code> intends to be a basic implementation of a game that can be
@@ -96,7 +97,8 @@
    private UncaughtExceptionHandler exceptionHandler;
    
    private Canvas canvas;
-
+   private GameStateNode<GameState> gameStateManager;
+   private boolean multipleGames = false;
    private Lock updateLock;
    
    public StandardGame(String gameName) {
@@ -106,6 +108,11 @@
    public StandardGame(String gameName, GameType type) {
       this(gameName, type, null);
    }
+   
+   public StandardGame(String gameName, GameType type, boolean multipleGames) {
+      this(gameName, type, null);
+      setMultipleGames(multipleGames);
+   }
 
    public StandardGame(String gameName, GameType type, GameSettings settings) {
       this(gameName, type, settings, null);
@@ -183,7 +190,7 @@
       // Main game loop
       float tpf;
       started = true;
-      while ((!finished) && (!display.isClosing())) {
+      while ((!finished) && (display == null || !display.isClosing())) {
          // Fixed framerate Start
          if (preferredTicksPerFrame >= 0) {
             frameStartTick = timer.getTime();
@@ -196,8 +203,10 @@
             InputSystem.update();
          }
          update(tpf);
-         render(tpf);
-         display.getRenderer().displayBackBuffer();
+         if (type == GameType.GRAPHICAL) {
+            render(tpf);
+            display.getRenderer().displayBackBuffer();
+         }
 
          // Fixed framerate End
          if (preferredTicksPerFrame >= 0) {
@@ -262,7 +271,7 @@
                 initSound();
          }
       } else {
-         display = new DummyDisplaySystem();
+         display = null;
       }
    }
 
@@ -277,8 +286,10 @@
    }
    
     protected void initSound() {
-        AudioSystem.getSystem().getEar().trackOrientation(camera);
-        AudioSystem.getSystem().getEar().trackPosition(camera);
+       if (type == GameType.GRAPHICAL) {
+           AudioSystem.getSystem().getEar().trackOrientation(camera);
+           AudioSystem.getSystem().getEar().trackPosition(camera);
+       }
     }
 
    private void displayMins() {
@@ -308,7 +319,12 @@
 
    protected void initGame() {
       // Create the GameStateManager
-      GameStateManager.create();
+      if (isMultipleGames()){
+         gameStateManager = new GameStateNode<GameState>("GameStateManager for " + gameName);
+      }else{
+         GameStateManager.create();
+         gameStateManager = GameStateManager.getInstance();
+      }
    }
 
    protected void update(float interpolation) {
@@ -320,7 +336,7 @@
       GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();
 
       // Update the GameStates
-      GameStateManager.getInstance().update(interpolation);
+      gameStateManager.update(interpolation);
 
       if (type == GameType.GRAPHICAL) {
 
@@ -332,14 +348,16 @@
    }
 
    protected void render(float interpolation) {
-      display.getRenderer().clearStatistics();
-      display.getRenderer().clearBuffers();
-
-      // Execute renderQueue item
-      GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
-
-      // Render the GameStates
-      GameStateManager.getInstance().render(interpolation);
+      if (type == GameType.GRAPHICAL) {
+         display.getRenderer().clearStatistics();
+         display.getRenderer().clearBuffers();
+   
+         // Execute renderQueue item
+         GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
+   
+         // Render the GameStates
+         gameStateManager.render(interpolation);
+      }
    }
    
    public void reinit() {
@@ -348,20 +366,22 @@
    }
    
    public void reinitAudio() {
-      if (AudioSystem.isCreated()) {
+      if (AudioSystem.isCreated() && type == GameType.GRAPHICAL) {
             AudioSystem.getSystem().cleanup();
         }
    }
    
    public void reinitVideo() {
-      displayMins();
-       
-      display.recreateWindow(settings.getWidth(), settings.getHeight(), settings.getDepth(), settings.getFrequency(),
-                  settings.isFullscreen());
-      camera = display.getRenderer().createCamera(display.getWidth(), display.getHeight());
-      display.getRenderer().setBackgroundColor(backgroundColor);
-      if ((settings.isMusic()) || (settings.isSFX())) {
-            initSound();
+      if (type == GameType.GRAPHICAL) {
+         displayMins();
+          
+         display.recreateWindow(settings.getWidth(), settings.getHeight(), settings.getDepth(), settings.getFrequency(),
+                     settings.isFullscreen());
+         camera = display.getRenderer().createCamera(display.getWidth(), display.getHeight());
+         display.getRenderer().setBackgroundColor(backgroundColor);
+         if ((settings.isMusic()) || (settings.isSFX())) {
+               initSound();
+         }
       }
    }
 
@@ -375,16 +395,18 @@
    }
 
    protected void cleanup() {
-      GameStateManager.getInstance().cleanup();
+      gameStateManager.cleanup();
       
-      DisplaySystem.getDisplaySystem().getRenderer().cleanup();
-      TextureManager.doTextureCleanup();
-      TextureManager.clearCache();
+      if (type == GameType.GRAPHICAL) {
+         DisplaySystem.getDisplaySystem().getRenderer().cleanup();
+         TextureManager.doTextureCleanup();
+         TextureManager.clearCache();
       
-      JoystickInput.destroyIfInitalized();
-        if (AudioSystem.isCreated()) {
-            AudioSystem.getSystem().cleanup();
-        }
+         JoystickInput.destroyIfInitalized();
+           if (AudioSystem.isCreated()) {
+               AudioSystem.getSystem().cleanup();
+           }
+      }
    }
 
    protected void quit() {
@@ -432,6 +454,21 @@
    public GameSettings getSettings() {
       return settings;
    }
+   
+   /**
+    * Returns the <code>GameStateNode</code> that acts as the GameStateManager
+    * for this instance of <code>StandardGame</code>. If the game was started
+    * with the variable multipleGames set to TRUE, this returned GameStateNode
+    * is connected only the current StandardGame. If the game was started with
+    * the variable multipleGames set to FALSE (default) then this method returns
+    * the same GameStateNode as GameStateManager.getInstance() would.
+    *
+    * @return
+    *       GameStateNode
+    */
+   public GameStateNode<GameState> getGameStateManager() {
+      return gameStateManager;
+   }
 
    /**
     * Override the background color defined for this game. The reinit() method
@@ -550,6 +587,19 @@
     public void setIcons( Image[] icons) {
        this.icons = icons;
     }
+
+   public boolean isMultipleGames() {
+      return multipleGames;
+   }
+
+   
+   public void setMultipleGames(boolean multipleGames) {
+      if (!isStarted()){
+         this.multipleGames = multipleGames;
+      }else{
+         logger.log(Level.SEVERE, "Setting cannot be changed as game has already been started");
+      }
+   }
 }
 
 class DefaultUncaughtExceptionHandler implements UncaughtExceptionHandler {



I don't know if this fix is good enough for general use... as it has a couple of drawbacks.. but well.. as said it works for me..

And a testcase… due to post length limitation I cannot post everything at once



main class of example:


package net.mindgamer.test.CustomStandardGame;

import com.jmex.editors.swing.settings.GameSettingsPanel;
import com.jmex.game.state.BasicGameState;

public class RunTest {

   private static StandardGame game1;         // Game root 1
   private static StandardGame game2;         // Game root 2
   private static BasicGameState gamestate1;         // Our gamestate 1
   private static BasicGameState gamestate2;         // Our gamestate 2
   
   /**
    * Entry point to our simple test game
    *
    * @param argv The arguments passed into the game
    */
   public static void main(String[] args) {
      RunTest runTest = new RunTest();
   }
   
   public RunTest() {
      game1 = new StandardGame("Game 1", StandardGame.GameType.GRAPHICAL, true);
      
      try{
         GameSettingsPanel.prompt(game1.getSettings());
      }catch(Exception e){
         e.printStackTrace();
      }
      game1.getSettings();
      game1.start();
      
      
      gamestate1 = new MainGamestate();
      game1.getGameStateManager().attachChild(gamestate1);
      gamestate1.setActive(true);
   }
   
   public static void startHeadlessGame(){
      game2 = new StandardGame("Game 2", StandardGame.GameType.HEADLESS);
      game2.getSettings();
      game2.setMultipleGames(true);
      game2.start();
      
      gamestate2 = new HeadlessGamestate("Headless");
      game2.getGameStateManager().attachChild(gamestate2);
      gamestate2.setActive(true);
   }
   
   public static void shutdownHeadlessGame(){
      if (game2 != null) game2.shutdown();
   }
   
   public static void shutdown(){
      if (game2 != null) game2.shutdown();
      game1.shutdown();
      System.exit(0);
   }
   
}



Gamestate for the graphical StandardGame:


package net.mindgamer.test.CustomStandardGame;

import java.awt.event.ActionEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDesktopPane;

import com.jme.input.MouseInput;
import com.jmex.awt.swingui.JMEDesktopState;

public class MainGamestate extends JMEDesktopState {

    public MainGamestate() {
        super(true);
       
        JDesktopPane jDesktop = getDesktop().getJDesktop();
       
        for (int i = 0; i < actions.length; i++) {
            JButton button = new JButton(actions[i]);
            button.setSize(150, 20);
            button.setLocation(30, 30 + (30*i));
            jDesktop.add(button);
        }
       
        jDesktop.invalidate();
        jDesktop.validate();
        jDesktop.repaint();
       
        MouseInput.get().setCursorVisible( true );
    }
   
    private Action startHeadlessGameAction = new AbstractAction() {
      private static final long serialVersionUID = 1L;{
            putValue(NAME, "Start headless");
            putValue(SHORT_DESCRIPTION, "Starts up the second game in headless mode");
        }
        public void actionPerformed(ActionEvent e) {
           RunTest.startHeadlessGame();
        }
    };
   
    private Action shutdownHeadlessGameAction = new AbstractAction() {
      private static final long serialVersionUID = 1L;{
            putValue(NAME, "Shutdown headless");
            putValue(SHORT_DESCRIPTION, "Shuts down the game that is running in headless mode");
        }
        public void actionPerformed(ActionEvent e) {
           RunTest.shutdownHeadlessGame();
        }
    };
   
    private Action exitGameAction = new AbstractAction() {
      private static final long serialVersionUID = 1L;{
            putValue(NAME, "Exit");
            putValue(SHORT_DESCRIPTION, "Exits both games");
        }
        public void actionPerformed(ActionEvent e) {
           RunTest.shutdown();
        }
    };
   
    private Action[] actions = new Action[]{
          startHeadlessGameAction, shutdownHeadlessGameAction, exitGameAction
    };
   
}



Gamestate for the Headless StandardGame:


package net.mindgamer.test.CustomStandardGame;

import com.jme.util.Timer;
import com.jmex.game.state.BasicGameState;

public class HeadlessGamestate extends BasicGameState{
   
   private Timer timer;
   private int time;
   
   public HeadlessGamestate(String name){
      super(name);
      init();
   }
   
   private void init(){
      timer = Timer.getTimer();
   }

   
    public void update(float tpf) {
       super.update(tpf);
       if (time != (int) timer.getTimeInSeconds()){
          time = (int) timer.getTimeInSeconds();
          System.out.println("time = " + time);
       }
   }
   
}

And the full StandardGame as I use it, if you do not want to use the patch yourself:



package net.mindgamer.test.CustomStandardGame;

import java.awt.Canvas;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import com.jme.app.AbstractGame;
import com.jme.image.Image;
import com.jme.input.InputSystem;
import com.jme.input.MouseInput;
import com.jme.input.joystick.JoystickInput;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.system.DisplaySystem;
import com.jme.system.GameSettings;
import com.jme.system.PreferencesGameSettings;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.NanoTimer;
import com.jme.util.TextureManager;
import com.jme.util.Timer;
import com.jmex.audio.AudioSystem;
import com.jmex.game.state.GameState;
import com.jmex.game.state.GameStateManager;
import com.jmex.game.state.GameStateNode;

/**
 * <code>StandardGame</code> intends to be a basic implementation of a game that can be
 * utilized in games as a logical next step from <code>SimpleGame</code> and can be utilized
 * in production games.
 *
 * @author Matthew D. Hicks
 */
public final class StandardGame extends AbstractGame implements Runnable {
    private static final Logger logger = Logger.getLogger(StandardGame.class
            .getName());
   
    public static final int DISPLAY_WINDOW = 1;
    public static final int DISPLAY_CANVAS = 2;
   
    public static boolean THREAD_FRIENDLY = true;
    public static int DISPLAY_MODE = DISPLAY_WINDOW;
   
   public static enum GameType {
      GRAPHICAL, HEADLESS
   }

   private Thread gameThread;
   private String gameName;
   private GameType type;
   private GameSettings settings;
   private boolean started;
   private Image[] icons;
   
   private Timer timer;
   private Camera camera;
   private ColorRGBA backgroundColor;
   private UncaughtExceptionHandler exceptionHandler;
   
   private Canvas canvas;
   private GameStateNode<GameState> gameStateManager;
   private boolean multipleGames = false;
   private Lock updateLock;
   
   public StandardGame(String gameName) {
      this(gameName, GameType.GRAPHICAL, null);
   }

   public StandardGame(String gameName, GameType type) {
      this(gameName, type, null);
   }
   
   public StandardGame(String gameName, GameType type, boolean multipleGames) {
      this(gameName, type, null);
      setMultipleGames(multipleGames);
   }

   public StandardGame(String gameName, GameType type, GameSettings settings) {
      this(gameName, type, settings, null);
   }

   public StandardGame(String gameName, GameType type, GameSettings settings, UncaughtExceptionHandler exceptionHandler) {
      this.gameName = gameName;
      this.type = type;
      this.settings = settings;
      this.exceptionHandler = exceptionHandler;
      backgroundColor = ColorRGBA.black.clone();

      // Validate settings
      if (this.settings == null) {
         this.settings = new PreferencesGameSettings(Preferences.userRoot().node(gameName));
      }

      // Create Lock
      updateLock = new ReentrantLock(true); // Make our lock be fair (first come, first serve)
   }

   public GameType getGameType() {
      return type;
   }

   public void start() {
      gameThread = new Thread(this);
      if (exceptionHandler == null) {
         exceptionHandler = new DefaultUncaughtExceptionHandler(this);
      }
      gameThread.setUncaughtExceptionHandler(exceptionHandler);
      
      // Assign a name to the thread
      gameThread.setName("OpenGL");
      
      gameThread.start();

      // Wait for main game loop before returning
      try {
         while (!isStarted()) {
            Thread.sleep(1);
         }
      } catch (InterruptedException exc) {
         logger.logp(Level.SEVERE, this.getClass().toString(), "start()", "Exception", exc);
      }
   }

   public void run() {
      lock();
      initSystem();
      if (type != GameType.HEADLESS) {
         assertDisplayCreated();
         
           // Default the mouse cursor to off
           MouseInput.get().setCursorVisible(false);
      }
       
        initGame();
      if (type == GameType.GRAPHICAL) {
         timer = Timer.getTimer();
      } else if (type == GameType.HEADLESS) {
         timer = new NanoTimer();
      }

      // Configure frame rate
      int preferredFPS = settings.getFramerate();
      long preferredTicksPerFrame = -1;
      long frameStartTick = -1;
      long frames = 0;
      long frameDurationTicks = -1;
      if (preferredFPS >= 0) {
         preferredTicksPerFrame = Math.round((float)timer.getResolution() / (float)preferredFPS);
      }

      // Main game loop
      float tpf;
      started = true;
      while ((!finished) && (display == null || !display.isClosing())) {
         // Fixed framerate Start
         if (preferredTicksPerFrame >= 0) {
            frameStartTick = timer.getTime();
         }

         timer.update();
         tpf = timer.getTimePerFrame();

         if (type == GameType.GRAPHICAL) {
            InputSystem.update();
         }
         update(tpf);
         if (type == GameType.GRAPHICAL) {
            render(tpf);
            display.getRenderer().displayBackBuffer();
         }

         // Fixed framerate End
         if (preferredTicksPerFrame >= 0) {
            frames++;
            frameDurationTicks = timer.getTime() - frameStartTick;
            while (frameDurationTicks < preferredTicksPerFrame) {
               long sleepTime = ((preferredTicksPerFrame - frameDurationTicks) * 1000) / timer.getResolution();
               try {
                  Thread.sleep(sleepTime);
               } catch (InterruptedException exc) {
                  logger.log(Level.SEVERE,
                                   "Interrupted while sleeping in fixed-framerate",
                                   exc);
               }
               frameDurationTicks = timer.getTime() - frameStartTick;
            }
            if (frames == Long.MAX_VALUE) frames = 0;
         }

         if (THREAD_FRIENDLY) Thread.yield();
      }
      started = false;
      cleanup();
      quit();
   }

   protected void initSystem() {
      if (type == GameType.GRAPHICAL) {

         // Configure Joystick
         if (JoystickInput.getProvider() == null) {
            JoystickInput.setProvider(InputSystem.INPUT_SYSTEM_LWJGL);
         }

         display = DisplaySystem.getDisplaySystem(settings.getRenderer());
         displayMins();
         
         display.setTitle(gameName);
         if( icons != null) {
            display.setIcon( icons);
         }

         if (DISPLAY_MODE == DISPLAY_WINDOW) {
            display.createWindow(settings.getWidth(), settings.getHeight(), settings.getDepth(), settings
                        .getFrequency(), settings.isFullscreen());
         } else if (DISPLAY_MODE == DISPLAY_CANVAS) {
            canvas = display.createCanvas(settings.getWidth(), settings.getHeight());
         }
         camera = display.getRenderer().createCamera(display.getWidth(), display.getHeight());
         display.getRenderer().setBackgroundColor(backgroundColor);

         // Setup Vertical Sync if enabled
         display.setVSyncEnabled(settings.isVerticalSync());

         // Configure Camera
         cameraPerspective();
         cameraFrame();
         camera.update();
         display.getRenderer().setCamera(camera);

         if ((settings.isMusic()) || (settings.isSFX())) {
                initSound();
         }
      } else {
         display = null;
      }
   }

   /**
    * The java.awt.Canvas if DISPLAY_CANVAS is the DISPLAY_MODE
    *
    * @return
    *       Canvas
    */
   public Canvas getCanvas() {
      return canvas;
   }
   
    protected void initSound() {
       if (type == GameType.GRAPHICAL) {
           AudioSystem.getSystem().getEar().trackOrientation(camera);
           AudioSystem.getSystem().getEar().trackPosition(camera);
       }
    }

   private void displayMins() {
      display.setMinDepthBits(settings.getDepthBits());
      display.setMinStencilBits(settings.getStencilBits());
      display.setMinAlphaBits(settings.getAlphaBits());
      display.setMinSamples(settings.getSamples());
   }

   private void cameraPerspective() {
      camera.setFrustumPerspective(45.0f, (float)display.getWidth() / (float)display.getHeight(), 1.0f, 1000.0f);
      camera.setParallelProjection(false);
      camera.update();
   }

   private void cameraFrame() {
      Vector3f loc = new Vector3f(0.0f, 0.0f, 25.0f);
      Vector3f left = new Vector3f(-1.0f, 0.0f, 0.0f);
      Vector3f up = new Vector3f(0.0f, 1.0f, 0.0f);
      Vector3f dir = new Vector3f(0.0f, 0.0f, -1.0f);
      camera.setFrame(loc, left, up, dir);
   }
   
   public void resetCamera() {
      cameraFrame();
   }

   protected void initGame() {
      // Create the GameStateManager
      if (isMultipleGames()){
         gameStateManager = new GameStateNode<GameState>("GameStateManager for " + gameName);
      }else{
         GameStateManager.create();
         gameStateManager = GameStateManager.getInstance();
      }
   }

   protected void update(float interpolation) {
      // Open the lock up for just a brief second
      unlock();
      lock();

      // Execute updateQueue item
      GameTaskQueueManager.getManager().getQueue(GameTaskQueue.UPDATE).execute();

      // Update the GameStates
      gameStateManager.update(interpolation);

      if (type == GameType.GRAPHICAL) {

         // Update music/sound
         if ((settings.isMusic()) || (settings.isSFX())) {
                AudioSystem.getSystem().update();
         }
      }
   }

   protected void render(float interpolation) {
      if (type == GameType.GRAPHICAL) {
         display.getRenderer().clearStatistics();
         display.getRenderer().clearBuffers();
   
         // Execute renderQueue item
         GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER).execute();
   
         // Render the GameStates
         gameStateManager.render(interpolation);
      }
   }
   
   public void reinit() {
      reinitAudio();
      reinitVideo();
   }
   
   public void reinitAudio() {
      if (AudioSystem.isCreated() && type == GameType.GRAPHICAL) {
            AudioSystem.getSystem().cleanup();
        }
   }
   
   public void reinitVideo() {
      if (type == GameType.GRAPHICAL) {
         displayMins();
          
         display.recreateWindow(settings.getWidth(), settings.getHeight(), settings.getDepth(), settings.getFrequency(),
                     settings.isFullscreen());
         camera = display.getRenderer().createCamera(display.getWidth(), display.getHeight());
         display.getRenderer().setBackgroundColor(backgroundColor);
         if ((settings.isMusic()) || (settings.isSFX())) {
               initSound();
         }
      }
   }

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

   protected void cleanup() {
      gameStateManager.cleanup();
      
      if (type == GameType.GRAPHICAL) {
         DisplaySystem.getDisplaySystem().getRenderer().cleanup();
         TextureManager.doTextureCleanup();
         TextureManager.clearCache();
      
         JoystickInput.destroyIfInitalized();
           if (AudioSystem.isCreated()) {
               AudioSystem.getSystem().cleanup();
           }
      }
   }

   protected void quit() {
      if (display != null) {
         display.reset();
         display.close();
      }
   }

   /**
    * The internally used <code>DisplaySystem</code> for this instance
    * of <code>StandardGame</code>
    *
    * @return
    *      DisplaySystem
    *
    * @see DisplaySystem
    */
   public DisplaySystem getDisplay() {
      return display;
   }

   /**
    * The internally used <code>Camera</code> for this instance of
    * <code>StandardGame</code>.
    *
    * @return
    *      Camera
    *     
    * @see Camera
    */
   public Camera getCamera() {
      return camera;
   }

   /**
    * The <code>GameSettings</code> implementation being utilized in
    * this instance of <code>StandardGame</code>.
    *
    * @return
    *      GameSettings
    *     
    * @see GameSettings
    */
   public GameSettings getSettings() {
      return settings;
   }
   
   /**
    * Returns the <code>GameStateNode</code> that acts as the GameStateManager
    * for this instance of <code>StandardGame</code>. If the game was started
    * with the variable multipleGames set to TRUE, this returned GameStateNode
    * is connected only the current StandardGame. If the game was started with
    * the variable multipleGames set to FALSE (default) then this method returns
    * the same GameStateNode as GameStateManager.getInstance() would.
    *
    * @return
    *       GameStateNode
    */
   public GameStateNode<GameState> getGameStateManager() {
      return gameStateManager;
   }

   /**
    * Override the background color defined for this game. The reinit() method
    * must be invoked if the game is currently running before this will take effect.
    *
    * @param backgroundColor
    */
   public void setBackgroundColor(ColorRGBA backgroundColor) {
      this.backgroundColor = backgroundColor;
   }

   /**
    * Gracefully shutdown the main game loop thread. This is a synonym
    * for the finish() method but just sounds better.
    *
    * @see #finish()
    */
   public void shutdown() {
      finish();
   }

   /**
    * Will return true if within the main game loop. This is particularly
    * useful to determine if the game has finished the initialization but
    * will also return false if the game has been terminated.
    *
    * @return
    *      boolean
    */
   public boolean isStarted() {
      return started;
   }

   /**
    * Specify the UncaughtExceptionHandler for circumstances where an exception in the
    * OpenGL thread is not captured properly.
    *
    * @param exceptionHandler
    */
   public void setUncaughtExceptionHandler(UncaughtExceptionHandler exceptionHandler) {
      this.exceptionHandler = exceptionHandler;
      gameThread.setUncaughtExceptionHandler(this.exceptionHandler);
   }

   /**
    * Causes the current thread to wait for an update to occur in the OpenGL thread.
    * This can be beneficial if there is work that has to be done in the OpenGL thread
    * that needs to be completed before continuing in another thread.
    *
    * You can chain invocations of this together in order to wait for multiple updates.
    *
    * @throws InterruptedException
    * @throws ExecutionException
    */
   public void delayForUpdate() throws InterruptedException, ExecutionException {
      Future<Object> f = GameTaskQueueManager.getManager().update(new Callable<Object>() {
         public Object call() throws Exception {
            return null;
         }
      });
      f.get();
   }

   /**
    * Convenience method to let you know if the thread you're in is the OpenGL thread
    *
    * @return
    *       true if, and only if, the current thread is the OpenGL thread
    */
   public boolean inGLThread() {
      if (Thread.currentThread() == gameThread) {
         return true;
      }
      return false;
   }

   /**
    * Convenience method that will make sure <code>callable</code> is executed in the
    * OpenGL thread. If it is already in the OpenGL thread when this method is invoked
    * it will be executed and returned immediately. Otherwise, it will be put into the
    * GameTaskQueue and executed in the next update. This is a blocking method and will
    * wait for the successful return of <code>callable</code> before returning.
    *
    * @param <T>
    * @param callable
    * @return result of callable.get()
    * @throws Exception
    */
   public <T> T executeInGL(Callable<T> callable) throws Exception {
      if (inGLThread()) {
         return callable.call();
      }
      Future<T> future = GameTaskQueueManager.getManager().update(callable);
      return future.get();
   }

   /**
    * Will wait for a lock at the beginning of the OpenGL update method. Once this method returns the
    * OpenGL thread is blocked until the lock is released (via unlock()). If another thread currently
    * has a lock or it is currently in the process of an update the calling thread will be blocked until
    * the lock is successfully established.
    */
   public void lock() {
      updateLock.lock();
   }

   /**
    * Used in conjunction with lock() in order to release a previously assigned lock on the OpenGL thread.
    * This <b>MUST</b> be executed within the same thread that called lock() in the first place or the lock
    * will not be released.
    */
   public void unlock() {
      updateLock.unlock();
   }

    public void setIcons( Image[] icons) {
       this.icons = icons;
    }

   public boolean isMultipleGames() {
      return multipleGames;
   }

   
   public void setMultipleGames(boolean multipleGames) {
      if (!isStarted()){
         this.multipleGames = multipleGames;
      }else{
         logger.log(Level.SEVERE, "Setting cannot be changed as game has already been started");
      }
   }
}

class DefaultUncaughtExceptionHandler implements UncaughtExceptionHandler {
    private static final Logger logger = Logger
            .getLogger(DefaultUncaughtExceptionHandler.class.getName());
   
   private StandardGame game;

   public DefaultUncaughtExceptionHandler(StandardGame game) {
      this.game = game;
   }

   public void uncaughtException(Thread t, Throwable e) {
      logger.log(Level.SEVERE, "Main game loop broken by uncaught exception", e);
      game.shutdown();
      game.cleanup();
      game.quit();
   }

Great…I'll try to patch 1.0 soon…you might want to submit a patch for 2.0 as well if you're willing.