JME needs a lot of time to start

hi there,



my application needs some time to start, because i do a lot of sql querys and indexing data and after that i draw some hundrets meshes. thats why if i start the application in fullscreen the screen is black for almost 30 seconds ore more. is there a possible to draw something like a progress bar during initalising? or a splashscreen? because i think some people might think that the application doesn0t work at all if the screen stays black for such a long time.

If you are using GameStates, have a look into com.jmex.game.state.load.LoadingGameState, that may help.

unfortunately i dont use gamestates

You can do your own, just create whatever sort of visualization you want and have your loading thread send it updates from time to time. Or if you just want a splash screen, just display it before you start loading.

hmm then it's necessary to have threads in any case?

Not really, unless you want a really smooth loading screen (many games don't go for that).



Beware if you use threads: for many purposes, you cannot update the scenegraph or access the Renderer from several threads. Especially since you will be loading textures, render states…



Though the threaded approach would be nice, it is usually simpler to have a loader screen with 9 steps, and perform loading on those. Between each step, you can load part of your resources.



Even if you don't use GameStates, look into TestLoadingGameState code: it contains an example and may help you.

hmm ok i think i can create a progress bar. but i don't know when i have to init it and how i can notify the progress bar that the loading has finished. that's the real problem.

You cannot do all your loading in initGame if you want anything to show up before.

To avoid threads you could write a custom Controller which will only run once and add that to rootNode.

Then you can do all your intensive loading in that Controller'S update() method.

Note that the game will still not repaint when minimized then maximized or anything similar, and you won't be able to update a progress bar either with this method - so it's really just to display some static "please wait" message possibly with/in an image.

ok thx a lot. this helps a lot.



but then i need a second window to show the please wait image? i can't make a second window with display.createWindow();

Alright, here's an example of my simplistic loading screen approach. See code comments for details.


public class LoadingScreenDemo extends SimpleGame{

    public static void main(String[] args) {
        new LoadingScreenDemo().start();
    }

    @Override
    protected void simpleInitGame() {
        //note that "loading" could be a textured Quad in ortho mode or anything else just as well!
        final Text loading = Text.createDefaultTextLabel("loading", "loading, please wait");
        loading.getLocalTranslation().x = (display.getWidth()-loading.getWidth())/2;
        loading.getLocalTranslation().y = (display.getHeight()-loading.getHeight())/2;
        rootNode.attachChild(loading);

        //now, to display the scene before our long initialization routine starts, we'll have to
        //return from simpleInitGame() before starting the init.
        //we achieve this by using a Controller which will perform the init in the second frame.
        Controller longInitTask = new Controller(){
           
            //we actually have to give the engine two frames in advance to show our loading screen
            int hasFirstFrameDisplayed = -2;
           
            @Override
            public void update(float time) {
                if(hasFirstFrameDisplayed >= 0){
                    //this is where all your scengraph initialization goes
                    performLongInitializationHere();

                    //remove controller, it has now finished it's initialization
                    rootNode.removeController(this);
                    rootNode.detachChild(loading);
                }else{
                    hasFirstFrameDisplayed++;
                }
            }
        };
       
        rootNode.addController(longInitTask);
    }

    private void performLongInitializationHere() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            Logger.getLogger(LoadingScreenDemo.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

thx. know i can see the loading text but only half a second when my quads are also already drawn

Sebastian23 said:

thx. know i can see the loading text but only half a second when my quads are also already drawn

I didn't understand that.

your text: loading pls wait is displayed in my application, but not during loading but rather after the loading has finished, the text is displayed for half a second ore something like that before it vanishes

ok now i understand :smiley: the Thread.sleep() was just there to simulate the loading :smiley: ok thx.



but there is another problem.



package src.controller;

import java.net.URL;
import java.util.HashMap;
import java.util.Vector;

import src.database.CP_DataBase;
import src.handler.CP_MouseInputListener;
import src.handler.FengJMEInputHandler;
import src.view.CP_Boxes;
import src.view.CP_Connector;
import src.view.CP_Menu;
import src.view.CP_PackageTagging;
import src.view.CP_Tagging;
import src.view.CP_ToolTip;
import src.handler.CP_Logger;

import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.image.Texture;
import com.jme.input.ChaseCamera;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;

import com.jme.input.ThirdPersonHandler;
import com.jme.input.action.InputActionEvent;
import com.jme.input.action.KeyScreenShotAction;
import com.jme.light.PointLight;
import com.jme.math.FastMath;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.state.CullState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.MaterialState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;

import static src.constants.CP_Constants.FIELD_X;
import static src.constants.CP_Constants.FIELD_Z;
import static src.constants.CP_Constants.TERRAIN_X;
import static src.constants.CP_Constants.TERRAIN_Y;
import static src.constants.CP_Constants.TERRAIN_Z;

public class CP_Controller extends SimpleGame {
   
   private CP_Controller controller;
   private Node m_character;
   private ChaseCamera chaser;
   private PointLight light;
   private Node boxNode;
   private org.fenggui.Display dispGUI;
   private FengJMEInputHandler inputGUI;
   private CP_Menu menu;
   private CP_Connector connect;
   private CP_Boxes boxes;
   private Node floorNode;
   private CP_Tagging tagging;
   private int screenshotCounter = 1;
   private Vector<Box> terrainBoxes = new Vector<Box>();
   private boolean lock = false;
   private boolean updateCam = false;
   private boolean initalizationDone = false;
   
   /**
    * Starts the game and sets the splash screen
    * @param controller
    */
   public void init(CP_Controller controller) {
      this.controller = controller;
      controller.setDialogBehaviour(ALWAYS_SHOW_PROPS_DIALOG, CP_Controller.class.getClassLoader().getResource("data/splashscreen.png"));
      controller.start();
   }
   
   /**
    * Sets the lights and starts all other methods
    */
   @Override
   protected void simpleInitGame() {
      display.setTitle("ChangePrism");
      
       final Text loading = Text.createDefaultTextLabel("loading", "loading, please wait");
           loading.getLocalTranslation().x = (display.getWidth()-loading.getWidth())/2;
           loading.getLocalTranslation().y = (display.getHeight()-loading.getHeight())/2;
           rootNode.attachChild(loading);

           Controller longInitTask = new Controller(){
              
               /**
             *
             */
            private static final long serialVersionUID = -1325130365657023501L;
            int hasFirstFrameDisplayed = -2;
              
               @Override
               public void update(float time) {
                   if(hasFirstFrameDisplayed >= 0){
                   
                       initialize();

                       rootNode.removeController(this);
                       rootNode.detachChild(loading);
                   }else{
                       hasFirstFrameDisplayed++;
                   }
               }
           };
          
           rootNode.addController(longInitTask);
      
      
   }
   
   /**
    * updates the camera position after every frame
    */
   protected void simpleUpdate() {
      if (initalizationDone == true) {
      if (lock == false) {
         chaser.update(tpf);
         float camMinHeight = 2f;
           if (!Float.isInfinite(camMinHeight) && !Float.isNaN(camMinHeight)
                   && cam.getLocation().y <= camMinHeight) {
               cam.getLocation().y = camMinHeight;
               cam.update();
           }

           float characterMinHeight = 2f;
           if (!Float.isInfinite(characterMinHeight) && !Float.isNaN(characterMinHeight)) {
               m_character.getLocalTranslation().y = characterMinHeight;
           }
      }
        if (menu.onFocus() == false) {
           input.update(tpf);
        } else {
           inputGUI.update(tpf);
        }
       
        if(!inputGUI.wasKeyHandled()) {
          
         if (KeyBindingManager.getKeyBindingManager().isValidCommand("quit")) {
            CP_Logger.getLogger().write("Application exit");
            finish();
         }
         
         if (KeyBindingManager.getKeyBindingManager().isValidCommand("screenshot", false)) {
            CP_Logger.getLogger().write("Screenshot started");
            KeyScreenShotAction screen = new KeyScreenShotAction("Screenshot - ".concat(Integer.toString(screenshotCounter)));
            screen.performAction(new InputActionEvent());
            screenshotCounter++;
         }
         
         if (KeyBindingManager.getKeyBindingManager().isValidCommand("tooltip", false)) {
            CP_Logger.getLogger().write("Tooltip started");
            CP_ToolTip tooltip = new CP_ToolTip(boxNode, boxes, menu, tagging);
            tooltip.findResults(chaser);
         }
         
         if (KeyBindingManager.getKeyBindingManager().isValidCommand("tagging", false)) {
            CP_Logger.getLogger().write("Tagging started");
            tagging.findResult(chaser, boxNode, menu, boxes);
         }
         
         if (KeyBindingManager.getKeyBindingManager().isValidCommand("camera_position", false)) {
            CP_Logger.getLogger().write("Update Camera position started");
            updateCameraPosition();
         }
         
         if (KeyBindingManager.getKeyBindingManager().isValidCommand("package", false)) {
            CP_Logger.getLogger().write("Package Tooltip started");
            CP_PackageTagging packageTagging = new CP_PackageTagging();
            packageTagging.findResult(chaser, floorNode, this, boxes, menu);
         }
      }
      }
   }
   
   /**
    * This method updates the camera Position
    */
   private void updateCameraPosition() {
      if (updateCam == false) {
         chaser.getCamera().setLocation(new Vector3f(300,900,300));
         chaser.getCamera().lookAt(new Vector3f(300,0,300), new Vector3f(0,0,1));
         lock = true;
         updateCam = true;
      } else if (updateCam == true) {
         lock = false;
         updateCam = false;
      }
   }

   /**
    * setups the input handlers
    */
   private void setupInput() {
      HashMap<String, Object> handlerProps = new HashMap<String, Object>();
        handlerProps.put(ThirdPersonHandler.PROP_DOGRADUAL, "true");
        handlerProps.put(ThirdPersonHandler.PROP_TURNSPEED, ""+(1.0f * FastMath.PI));
        handlerProps.put(ThirdPersonHandler.PROP_LOCKBACKWARDS, "false");
        handlerProps.put(ThirdPersonHandler.PROP_CAMERAALIGNEDMOVE, "true");
        input = new ThirdPersonHandler(m_character, cam, handlerProps);
        input.setActionSpeed(100f);
        input.setEnabled(false);
       
        /**
         * remove all unused keybindings from the parent class
         */
        KeyBindingManager.getKeyBindingManager().remove("toggle_wire");
        KeyBindingManager.getKeyBindingManager().remove("toggle_pause");
        KeyBindingManager.getKeyBindingManager().remove("step");
        KeyBindingManager.getKeyBindingManager().remove("toggle_lights");
        KeyBindingManager.getKeyBindingManager().remove("toogle_bounds");
        KeyBindingManager.getKeyBindingManager().remove("toggle_normals");
        KeyBindingManager.getKeyBindingManager().remove("camera_out");
        KeyBindingManager.getKeyBindingManager().remove("screen_shot");
        KeyBindingManager.getKeyBindingManager().remove("parallel_projection");
        KeyBindingManager.getKeyBindingManager().remove("toggle_depth");
        KeyBindingManager.getKeyBindingManager().remove("mem_report");
        KeyBindingManager.getKeyBindingManager().remove("exit");
       
        /**
         * set the new keybindings
         */
       
        KeyBindingManager.getKeyBindingManager().set("screenshot", KeyInput.KEY_F1);
      KeyBindingManager.getKeyBindingManager().set("tooltip", KeyInput.KEY_F2);
      KeyBindingManager.getKeyBindingManager().set("tagging", KeyInput.KEY_F3);
      KeyBindingManager.getKeyBindingManager().set("quit", KeyInput.KEY_ESCAPE);
      KeyBindingManager.getKeyBindingManager().set("camera_position", KeyInput.KEY_F4);
      KeyBindingManager.getKeyBindingManager().set("package", KeyInput.KEY_F5);
   }
   
   /**
    * generates a new camera which follows the graphics
    */
   private void setupChaseCamera() {
      Vector3f targetOffset = new Vector3f();
        targetOffset.y = (((BoundingBox) m_character.getWorldBound()).yExtent+10) * 1.5f;
        targetOffset.x = (((BoundingBox) m_character.getWorldBound()).xExtent+10) * 1.5f;
        targetOffset.z = (((BoundingBox) m_character.getWorldBound()).zExtent+10) * 1.5f;
        cam.setFrustumFar(1800.0f);
        chaser = new ChaseCamera(cam, m_character);
        chaser.getMouseLook().setMinRollOut(1.0f);
      chaser.getMouseLook().setMaxRollOut(1000.0f);
      chaser.getMouseLook().setMinAscent(0.0f);
      chaser.getMouseLook().setMaxAscent(80.0f);
        chaser.setTargetOffset(targetOffset);
   }
   
   /**
    * Setups the coordinate system
    */
   private void setupTerrain() {
      fpsNode.setRenderQueueMode(Renderer.QUEUE_ORTHO);
      display.getRenderer().setBackgroundColor(ColorRGBA.black);
      floorNode = new Node("Floor Node");
      
      MaterialState ms2 = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      ms2.setColorMaterial(MaterialState.CM_AMBIENT_AND_DIFFUSE);
      
      CullState cs = display.getRenderer().createCullState();
      cs.setCullMode(CullState.CS_BACK);
      cs.setEnabled(true);
      floorNode.setRenderState(cs);
      
      URL monkeyLoc;
      monkeyLoc = CP_Controller.class.getClassLoader().getResource("data/Grid.png");
      
      TextureState ts = display.getRenderer().createTextureState();
   
      Texture t = TextureManager.loadTexture(monkeyLoc, Texture.MM_LINEAR, Texture.FM_LINEAR);
      ts.setTexture(t);
      
      for (int i = 0; i < TERRAIN_X/FIELD_X; i++) {
         for (int j = 0; j < TERRAIN_Z/FIELD_Z; j++) {
            int xName = (int) ((i+1) * FIELD_X);
            int zName = (int) ((j+1) * FIELD_Z);
            Box box = new Box("Box - ".concat(Integer.toString(xName).concat(" - ").concat(Integer.toString(zName))), new Vector3f(((2*i)+1)*FIELD_X, 0, ((2*j)+1)*FIELD_Z), FIELD_X, 0.01f, FIELD_Z);
            box.setModelBound(new BoundingBox());
            box.updateModelBound();
            box.setRenderState(ts);
            box.setRenderState(ms2);
            terrainBoxes.add(box);
            floorNode.attachChild(box);
         }
      }
      
      MaterialState ms = DisplaySystem.getDisplaySystem().getRenderer().createMaterialState();
      ms.setColorMaterial(MaterialState.CM_AMBIENT_AND_DIFFUSE);
      
      Box y = new Box("y" ,new Vector3f(0,0,0), 0.5f, TERRAIN_Y+100, 0.5f);
      Box x = new Box("x", new Vector3f(0,0,0), TERRAIN_X+100, 0.5f, 0.5f);
      Box z = new Box("z", new Vector3f(0,0,0), 0.5f, 0.5f, TERRAIN_Z+100);
      
      x.setLocalTranslation(TERRAIN_X + 50, 0, 0);
      y.setLocalTranslation(0, TERRAIN_Y + 50, 0);
      z.setLocalTranslation(0,0,TERRAIN_Z + 50);
      
      x.setRenderState(ms);
      y.setRenderState(ms);
      z.setRenderState(ms);
      
      x.setDefaultColor(ColorRGBA.green);
      y.setDefaultColor(ColorRGBA.red);
      z.setDefaultColor(ColorRGBA.white);
      
      floorNode.attachChild(x);
      floorNode.attachChild(y);
      floorNode.attachChild(z);
   }

   /**
    * setups a invisible object. It's necessary because the camera follows this object
    */
   private void setupCharacter() {
      Box b = new Box("b", new Vector3f(), 0.01f,0.01f,0.01f);
      b.setModelBound(new BoundingBox());
      b.updateModelBound();
      m_character = new Node("char Node");
      rootNode.attachChild(m_character);
      m_character.attachChild(b);
      m_character.updateWorldBound();
   }
   
   /**
    * Overriden method. this method is called every frame and renders the graphic
    */
   @Override
   protected void render(float interpolation) {
      Renderer r = display.getRenderer();
      r.clearStatistics();
      r.clearBuffers();
      r.draw(rootNode);
      r.clearZBuffer();
      if (initalizationDone == true) {
         dispGUI.display();
      }
   }

   /**
    * Shut down method. Flushes all handlers and closes the application.
    */
   public void shutDown() {
      cleanup();
      System.exit(0);
   }
   
   /**
    * This method gets the Node which contains all connecting lines. The node is added to the root Node and
    * the root node is rendered again.
    */
   public void updateLines() {
      Vector<Node> connectingLines = new Vector<Node>();
      connectingLines = connect.getNode();
      for (int i = 0; i < connectingLines.size(); i++) {
         rootNode.attachChild(connectingLines.get(i));
      }
      rootNode.updateRenderState();
   }

   
   /**
    * This method updates the drawing of the boxes
    */
   public void updateBoxes() {
      rootNode.detachChildNamed("boxNode");
      boxNode.detachAllChildren();
      boxNode = boxes.returnBoxNode();
      rootNode.attachChild(boxNode);
      rootNode.updateRenderState();
   }
   
   /**
    * This method disables the input of the main window. Necessary if the mouse focus is on the menu window
    */
   public void disableInput() {
      input.setEnabled(false);
   }
   
   /**
    * Enables the input for the main window
    */
   public void enableInput() {
      input.setEnabled(true);
   }
   
   /**
    * This method updates the root node
    */
   public void updateRootNode() {
      rootNode.updateRenderState();
   }
   
   /**
    * This method sets the color of every specific terrain box
    * @param startZ
    * @param endZ
    * @param startX
    * @param endX
    * @param color
    */
   public void setTerrainColor(int startZ, int endZ, int startX, int endX, ColorRGBA color) {
      for (int i = 0; i < terrainBoxes.size(); i++) {
         String name = terrainBoxes.elementAt(i).getName();
         int separatorX = name.lastIndexOf("-");
         String x = name.substring(separatorX + 2, name.length());
         String buffer = name.substring(0, separatorX - 1);
         int separatorZ = buffer.lastIndexOf("-");
         String z = buffer.substring(separatorZ + 2, buffer.length());
         int xValue = Integer.valueOf(x).intValue();
         int zValue = Integer.valueOf(z).intValue();
         if (startZ < zValue && endZ >= zValue && startX < xValue && endX >= xValue) {
            terrainBoxes.elementAt(i).setDefaultColor(color);
            CP_Logger.getLogger().write("Terrain Box at Position " + i + " colored with " + color);
         }
      }
   }
   
   /**
    * This method sets the Color of the whole Terrain to white
    */
   public void setTerrainColorToWhite() {
      for (int i = 0; i < terrainBoxes.size(); i++) {
         terrainBoxes.elementAt(i).setDefaultColor(ColorRGBA.lightGray);
      }
   }

   /**
    * This method returns the Connector object fur further use
    * @return
    */
   public CP_Connector getConnector() {
      return connect;
   }

   /**
    * This method returns the menu object for further use
    * @return
    */
   public CP_Menu getMenu() {
      return menu;
   }
   
   /**
    * This method invokes the garbage Collector
    */
   public void clearMemory() {
      System.gc();
      System.runFinalization();
      System.gc();
      CP_Logger.getLogger().write("Garbage Collector invoked");
   }
   
   private void initialize() {
      
      ZBufferState buf = display.getRenderer().createZBufferState();
      buf.setFunction(ZBufferState.CF_LEQUAL);
      buf.setEnabled(true);
      rootNode.setRenderState(buf);
      
      light = new PointLight();
      light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
      light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f));
      light.setLocation(new Vector3f(100f,100f,100f));
      light.setEnabled(true);
      
      LightState ls = display.getRenderer().createLightState();
      ls.attach(light);
      lightState.detachAll();
      boxNode = new Node("BoxNode");
         
      setupCharacter();
      setupTerrain();
      setupChaseCamera();
      setupInput();
      initalizationDone = true;
      CP_DataBase db = new CP_DataBase();
      db.buildDataBase();
      boxes = new CP_Boxes(db, controller);
      boxes.getData();
      boxes.buildData();
      
      connect = new CP_Connector(boxes.getBoxes(), boxes.getAuthors());
      tagging = new CP_Tagging();
      
      menu = new CP_Menu(dispGUI, display, controller, db, connect, boxes, tagging);
      menu.drawMenu();
      inputGUI = menu.getMenuHandler();
      dispGUI = menu.getFDisplay();
      
      boxNode = boxes.returnBoxNode();
      rootNode.setRenderState(ls);
      rootNode.attachChild(floorNode);
      rootNode.attachChild(boxNode);
      
      MouseInput.get().addListener( new CP_MouseInputListener(input, chaser, inputGUI));
      
      Vector3f position = new Vector3f();
      position = display.getWorldCoordinates(new Vector2f(10,10), 0);
      
      rootNode.setLocalTranslation(position);
      
      rootNode.updateRenderState();
      
      CP_Logger.getLogger().write("Application started!");
   }
}



i had to declare a variable initalizion done, because the update and render method would have cause an nullpointer. but know i see my boxes only after i lciked and moved the mouse once. why that?

You need to make sure render() gets called at least once after you loading text is put in place and before you actually start loading you queries and data. This is essentially what hevee has given you. The loading doesn't start until after the first frame.

i think the problem is not the jme itselfs needs a lot of time to start but the sql querys and indexing things. and thats why the loading screen is showed after the sql query and indexing things. how to change that?

for testing i used the exact copy of heeve's code but neverthelss it doens't worked.



for example in the following simple code. first the 100000 ints are printed  out and after that the loading screen is displayed. but i want it at the same time:


import java.util.logging.Level;
import java.util.logging.Logger;

import com.jme.app.SimpleGame;
import com.jme.scene.Controller;
import com.jme.scene.Text;

public class LoadingScreenDemo extends SimpleGame{

    public static void main(String[] args) {
        new LoadingScreenDemo().start();
    }

    protected void simpleInitGame() {
        final Text loading = Text.createDefaultTextLabel("loading", "loading, please wait");
        loading.getLocalTranslation().x = (display.getWidth()-loading.getWidth())/2;
        loading.getLocalTranslation().y = (display.getHeight()-loading.getHeight())/2;
        rootNode.attachChild(loading);

        Controller longInitTask = new Controller(){
           
            int hasFirstFrameDisplayed = -2;
           
            @Override
            public void update(float time) {
                if(hasFirstFrameDisplayed >= 0){
                
                    performLongInitializationHere();

                    rootNode.removeController(this);
                    rootNode.detachChild(loading);
                }else{
                    hasFirstFrameDisplayed++;
                }
            }
        };
       
        rootNode.addController(longInitTask);
        rootNode.updateRenderState();
       
        goOn();
    }

    private void goOn() {
      for (int i = 0; i < 100000; i++) {
         System.out.println(i);
      }
   }

   private void performLongInitializationHere() {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            Logger.getLogger(LoadingScreenDemo.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
}

ok now i really found my problem.



my route is the following:


  1. i create the display. i see a black window
  2. in background there is a big sql query and some indexing work
  3. the loading text is shown in the display
  4. the normal application stuff is shown on the display.



    now i want to change, that first the loading text is shown and during that, the indexing and sql query stuff is done in the background. but i can't reach this even if i define the loading thing in the first row of my code. can anybody help pls?

simpleInitGame is called in the GL thread so your goOn method halts it, causing a black screen until all ints are printed. You should make the call to goOn() in the performLongInitializationHere() method.

Try to narrow down your problem to a small test case that others can read easily and preferably run.

I don't think many people here have the time to read through your 500+ lines of code and spot the error for you.

Plus narrowing down to the very point where your problem is will teach you exactly that - what and where your problem is, so you'll probably be able to fix it yourself then.