jME3 Application/AppState sample

Is there any way we can get a quick sample that uses Application and AppState (preferably with nifty thrown in)… It doesn't have to DO anything, just set up the environment, show a box and a button… nothing fancy. More or less what you would put in a netbeans template for a new App/AppState project.

Its not too hard, just implement AppState, fix the missing methods with NetBeans and then attach it via application.getStateManager().attachState(myState);.



Cheers,

Normen

I've never done jME before, so that is just a bit vague :slight_smile: (Axiom/silverlight/balder/wpf3d = yes, jME=no, and its been over 5 years since I seriously did any work with Java)



What are you suggesting ?? setup the state like the constructors in TRussell's jME2 method from the wiki ?? (I need to create a root node, etc ??)



If so – is there any particular reason why the root node any anything else commonly needed by an AppState is not already a part of AbstractAppState ??



hmmm creating a node and trying to add a box to it didnt work… a box isnt a spacial…

Hm, why do you need an AppState when you dont know about jme?  :?



To get a heads up for jme3 and coding with it, I suggest you go through the jme3 tutorials, there you will get an overview of things.

Create a java class, let it implement com.jme3.app.state.AppState and based on the method names you will have to implement you will see what happens where (update() call, render() call etc.).



Cheers,

Normen

Because except for a very few programatically created meshes/geometries contained in 3 states, my app is all about the GUI. I will need ONE texture asset excepting what is needed by the GUI. No Physics, no Effects, no Particles, no Collision, no Animations, no Audio. My game could have been 2D except for 2 display requirements that just cannot be handled by a 2D system. I've already got proof of concept code written in C# for Silverlight and WPF that handles most of those 2 display modes, but due to the limitations of those environments, I had to abandon them. XNA does not support the platforms I need, so here I am !! :slight_smile:



So I was looking for tutorials for states and nifty, which I don't see on that link you just gave me. I found the nifty test code and got a fair handle on that, so that leaves the states. Getting a proof of concept for how I need the GUI to work is my top priority… I already know that jME can handle the 3D aspects that I need, so that can wait just a bit.



I've been sitting in the IRC channel for a couple of days – drop in and say HI !!

It took me long enough tracing through the application initialization :slight_smile:



Step 1 to getting a state based app working (I think !!):



public class Main extends Application
{
    public static void main(String[] args)
   {
        Main app = new Main();
        app.start();
    }

    @Override
    public void initialize()
    {
        // initialize the standard environment first
        super.initialize();
        // then bring up our own elements
        Loader ldr = new Loader(); // Loader extends AbstractAppState
        stateManager.attach(ldr);
    }
}



Can someone give me a sanity check on that while I work on getting the Loader class fleshed out ?? :)

Looking good! :slight_smile:

Houston, we have a problem. (or I'm going blind and completely missed on how to use AppState from chewing on the code. If that is the case, please explain and disregard the rest of this)



AppState.initialize is called right before the first call to AppState.update, which means that AppStateManager is calling stateAttached and stateDetached before the state is initialized. That makes it sort of hard for the state to attach/detach its root/gui nodes from the viewports unless it checks/calls its own initialization state when attached, which is not possible since stateAttached  and stateDetached don't get the Application instance when called.



IMO the correct place for state initialization test/call is when the state is attached to the state manager. Also, Application doesn't expose the root and gui viewport instances, so state instances don't have a way get into the rendering system. It might also help to have the AppStateManager pass the Application instance to stateAttach/Detach since that is the logical place to handle updating the viewports for states that may be added/removed from the rendering system multiple times during their lifetime. If the Application instance is passed to attach and detach, then it really doesn't need to be passed to initialize, and the state does not need to keep a reference to the Application.



Proposed changes:

  1. move initialization check/call from AppStateManager.update to AppStateManager.attach.
  2. remove the Application parameter from the AppState.initialize call
  3. add an Application parameter to AppState.stateAttached and stateDetached
  4. add getters for viewPort and guiViewPort to Application.



    I hope this is helpful. I'll be hanging in IRC if any of this is not clear or if someone wants to show me the error of my ways :slight_smile:

The initialize on first run thing is there so the attaching can happen from another thread but the render thread and the init of the state is called on the render thread. Also, an AppState would attach its own rootNode to the ViewPort so access to the existing ones is not necessary.



The attach() and detach() callbacks as you want them are in fact the initalize() and cleanup() calls. You attach the state from any thread and it will initialize, create its root node and run until its detached and its cleanup() is called from the opengl thread.



I was just recently starting to use it for jMP but I think it works fine already. Momoko has just very recently put in the AppState system to get the real coding going so if you want to improve it you're very welcome, just explain what you want to happen and post some diffs.



Cheers,

Normen

OK – what I was getting at with what I suggested was the ability to initialize once, then attach and detach many times. For instance – I have a mapping system that has a fairly expensive initial setup, but once set up, loading a different map is more or less trivial. There are 2 other display states the user may be in, and one of them is used to select a mapped object to be displayed on the map page, so I will frequently be flopping back and forth between those two states as the user looks at a map, gives some orders for things to be done, then backs out to the overview, and selects a different map. I don't want to have to recreate those states each time I want to attach them.



I guess I need a better understanding of what AppState methods get called on what thread.



Just paste up the diffs here as code blocks ?? Just got finished checking out and getting a clean build of the current svn.

Well, the AppState system is meant for a wider scope of tasks than just showing or hiding UI elements. The physics for example will probably moved to an appstate.

About the (re)loading… you could check in your initialized() method if you have already loaded your assets and don't do it again… And yes, diffs as code blocks :slight_smile:

And I can see the possibility that you will want to change physics in and out much like UI or 3D elements… player is walking/running, gets into car and drives (which switches up input handlers, physics etc), gets out of the car, gets in a helicopter, takes off (another physics model, etc)…



so a vehicle instance may have all this information stored in a master state, with portions (ui changes, input handlers, physics, etc) in child states to keep the top end logic clean. I would expect then you would have an instance of this state for each vehicle of a type, storing the current state of the specific vehicle, with the child state containing the external view of the vehicle always attached. When the player mounts the vehicle, the external view can be detached, an internal view attached, and all the physics and IO attached… leaving the player ready to drive the vehicle.



when they leave the vehicle, everything detaches and the external view is reattached and ready for the next use (even by another player in a multiplayer scenario, where there may be a state that gets attached that handles the network packets representing a remote player controlling the vehicle, passing off the movement updates to the vehicle physics system instead of the body physics system for the "state" that represents the remote player)



Is that a reasonable description of one of the more complicated use-cases we will be dealing with ?? (I think most of the other use-cases will be subsets of this scenario)

Yes, sounds reasonable. However the logic for that would be implemented in the game and not in the AppStates. The game would have to care about its states being in order. Parent/child combinations sound definitely interesting but this could also be a use-case for a logic system I proposed for jme3… However let them diffs roll :slight_smile:

Of course the logic that activates a series of events needs to be in the main game logic, but I think it is overloading the app logic to have it deal with all the details beyond "the player clicked on the vehicle, the vehicle is not owned by an enemy, they are close enough for it not to be a target selection, tell the car to attach to the player" where upon the car figures out what seat they are closest to and reconfigures the player to be in that seat.



Think halo and the atv you get to drive around, where you could be the driver, a passenger, or in the back manning the 50… one vehicle, 3 different possible states, each with different physics and input handling.



Extending the scenario to the various multiple seat vehicles available in Halo is reasonably simple at that point… you end up with an interface, base class, and a class for each specific vehicle. when you get packets from the server telling you to populate a scene with vehicles, its simple enough to have a factory create correct instances, then hook them up to the scene at the correct locations. (hmmm are we designing a vehicle subsystem to include in the engine ??)



I'm into the code – just about done with the suggested changes, then I need to crank out a test harness.

Yes, definitely. But AppState is atm nothing more than an interface to some update calls, you can implement the class around it and although jme brings lots of things with it its still supposed to be open enough to create any kind of specific game engine, really. Nothing against what you say, I fully support your suggested changes and use scenarios, just to give a heads up on what we're dealing with here.

Cheers,

Normen

I couldn't get the GUI to work like I wanted, but it is changing states without breaking anything.



# This patch file was generated by NetBeans IDE
# Following Index: paths are relative to: C:UsersJohnjMonkeyProjectsjME3
# This patch can be applied using context Tools: Patch action on respective folder.
# It uses platform neutral UTF-8 encoding and n newlines.
# Above lines and this line are ignored by the patching process.
Index: src/core/com/jme3/app/Application.java
--- src/core/com/jme3/app/Application.java Base (BASE)
+++ src/core/com/jme3/app/Application.java Locally Modified (Based On LOCAL)
@@ -239,6 +239,22 @@
         return cam;
     }
    
+    /**
+     * @returns the root viewport, or null if not started yet
+     */
+    public ViewPort getViewPort()
+    {
+        return viewPort;
+    }
+
+    /**
+     * @returns the gui viewport, or null if not started yet
+     */
+    public ViewPort getGuiViewPort()
+    {
+        return guiViewPort;
+    }
+
     public void start(){
         start(JmeContext.Type.Display);
     }
Index: src/core/com/jme3/app/state/AbstractAppState.java
--- src/core/com/jme3/app/state/AbstractAppState.java Base (BASE)
+++ src/core/com/jme3/app/state/AbstractAppState.java Locally Modified (Based On LOCAL)
@@ -20,10 +20,10 @@
         return initialized;
     }
 
-    public void stateAttached(AppStateManager stateManager) {
+    public void stateAttached(AppStateManager stateManager, Application app) {
     }
 
-    public void stateDetached(AppStateManager stateManager) {
+    public void stateDetached(AppStateManager stateManager, Application app) {
     }
 
     public void update(float tpf) {
Index: src/core/com/jme3/app/state/AppState.java
--- src/core/com/jme3/app/state/AppState.java Base (BASE)
+++ src/core/com/jme3/app/state/AppState.java Locally Modified (Based On LOCAL)
@@ -29,14 +29,14 @@
      *
      * @param stateManager State manager to which the state was attached to.
      */
-    public void stateAttached(AppStateManager stateManager);
+    public void stateAttached(AppStateManager stateManager, Application app);
 
    /**
     * Called when the state was detached.
     *
     * @param stateManager The state manager from which the state was detached from.
     */
-    public void stateDetached(AppStateManager stateManager);
+    public void stateDetached(AppStateManager stateManager, Application app);
 
     /**
      * Called to update the state.
Index: src/core/com/jme3/app/state/AppStateManager.java
--- src/core/com/jme3/app/state/AppStateManager.java Base (BASE)
+++ src/core/com/jme3/app/state/AppStateManager.java Locally Modified (Based On LOCAL)
@@ -22,9 +22,12 @@
      * was already attached.
      */
     public boolean attach(AppState state){
+        if (!state.isInitialized())
+            state.initialize(this, app);
+
         synchronized (states){
             if (!states.contains(state)){
-                state.stateAttached(this);
+                state.stateAttached(this, app);
                 states.add(state);
                 return true;
             }else{
@@ -43,7 +46,7 @@
     public boolean detach(AppState state){
         synchronized (states){
             if (states.contains(state)){
-                state.stateDetached(this);
+                state.stateDetached(this, app);
                 states.remove(state);
                 return true;
             }else{
@@ -94,9 +97,6 @@
             int num = states.size();
             for (int i = 0; i < num; i++){
                 AppState state = states.get(i);
-                if (!state.isInitialized())
-                    state.initialize(this, app);
-
                 state.update(tpf);
             }
         }
Index: src/test/jme3test/app/TestAppState.java
--- src/test/jme3test/app/TestAppState.java Locally New
+++ src/test/jme3test/app/TestAppState.java Locally New
@@ -0,0 +1,191 @@
+/*
+ * To change this template, choose Tools | Templates
+ * and open the template in the editor.
+ */
+
+package jme3test.app;
+
+import com.jme3.app.*;
+import com.jme3.app.state.*;
+import com.jme3.font.BitmapFont;
+import com.jme3.font.BitmapText;
+import com.jme3.font.Rectangle;
+import com.jme3.input.KeyInput;
+import com.jme3.input.controls.ActionListener;
+import com.jme3.input.controls.KeyTrigger;
+import com.jme3.input.controls.MouseButtonTrigger;
+import com.jme3.light.PointLight;
+import com.jme3.material.Material;
+import com.jme3.math.ColorRGBA;
+import com.jme3.math.Vector3f;
+import com.jme3.renderer.RenderManager;
+import com.jme3.scene.Geometry;
+import com.jme3.scene.Node;
+import com.jme3.scene.shape.Box;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ *
+ * @author Ascendion
+ */
+public class TestAppState extends Application {
+
+    private static final Logger logger = Logger.getLogger(Application.class.getName());
+
+    public static void main(String[] args) {
+        TestAppState app = new TestAppState();
+        app.start();
+    }
+
+    public class TestState extends AbstractAppState {
+
+        private String text;
+        private ColorRGBA color;
+        private TestState selfState;
+        private TestState nextState;
+
+        private Node guiNode;
+        private Node rootNode;
+
+        private Geometry boxGeometry;
+
+        protected BitmapFont guiFont;
+        protected BitmapText clickText;
+        protected BitmapText stateText;
+        protected Geometry mark;
+
+        private PointLight light;
+        private Application app;
+        private AppStateManager stateManager;
+
+        public TestState(String text, ColorRGBA color)
+        {
+            this.text = text;
+            this.color = color;
+            this.selfState = this;
+        }
+
+        public void setNextState(TestState next)
+        {
+            this.nextState = next;
+        }
+
+        private ActionListener actionListener = new ActionListener() {
+            @Override
+            public void onAction(String name, boolean keyPressed, float tpf) {
+                stateManager.detach(selfState);
+                stateManager.attach(nextState);
+            }
+        };
+
+        @Override
+        public void initialize(AppStateManager stateManager, Application app) {
+            this.app = app;
+            this.stateManager = stateManager;
+            logger.log(Level.INFO, text + " initializing");
+            super.initialize(stateManager, app);
+
+            // set up nodes to hold our content
+            guiNode = new Node("Test State GUI - " + text);
+            rootNode = new Node("Test State Root - " + text);
+            // set up material for texturing our cube (with custom color)
+            Material mat = new Material(assetManager, "Common/MatDefs/Misc/ColoredTextured.j3md");
+            mat.setTexture("m_ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png"));
+            mat.setColor("m_Color", color);
+            // create tue cube and apply material
+            boxGeometry = new Geometry("Box", new Box(Vector3f.ZERO, 2, 2, 2));
+            boxGeometry.setMaterial(mat);
+            // add it to our scene
+            rootNode.attachChild(boxGeometry);
+
+            // set up text and click displays on the gui
+            guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
+
+            stateText = new BitmapText(guiFont, false);
+            stateText.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight()));
+            stateText.setSize(guiFont.getPreferredSize() * 2f);
+            stateText.setText(text);
+            stateText.setLocalTranslation(0, stateText.getLineHeight(), 0);
+            //stateText.setColor(color);
+            guiNode.attachChild(stateText);
+
+            clickText = new BitmapText(guiFont, false);
+            clickText.setSize(guiFont.getPreferredSize() * 2f);
+            clickText.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight()));
+            clickText.setText(text);
+            clickText.setLocalTranslation(0, clickText.getLineHeight() * 2, 0);
+            clickText.setName("Button");
+            guiNode.attachChild(clickText);
+
+        }
+
+        @Override
+        public void stateAttached(AppStateManager stateManager, Application app) {
+            logger.log(Level.INFO, text + " attached");
+            app.getViewPort().attachScene(rootNode);
+            app.getGuiViewPort().attachScene(guiNode);
+            app.getInputManager().addMapping("Click",
+                    new KeyTrigger(KeyInput.KEY_RETURN), // trigger 1: spacebar
+                    new MouseButtonTrigger(0));         // trigger 2: left-button click
+            app.getInputManager().addListener(actionListener, "Click");
+
+        }
+
+        @Override
+        public void stateDetached(AppStateManager stateManager, Application app) {
+            logger.log(Level.INFO, text + " detached");
+            app.getViewPort().detachScene(rootNode);
+            app.getGuiViewPort().detachScene(guiNode);
+            app.getInputManager().deleteMapping("Click");
+            app.getInputManager().removeListener(actionListener);
+        }
+
+        @Override
+        public void update(float tpf)
+        {
+            rootNode.updateLogicalState(tpf);
+            rootNode.updateGeometricState();
+        }
+
+        @Override
+        public void render(RenderManager rm) {
+        }
+
+        @Override
+        public void cleanup() {
+            super.cleanup();
+        }
+    }
+
+    private TestState ts1;
+    private TestState ts2;
+    private TestState ts3;
+
+    @Override
+    public void initialize()
+    {
+        // make sure we have a starting environment
+        super.initialize();
+        // then bring up our own elements
+        // 3 states that have a colored rotating box and a button to go to the next state
+        ts1 = new TestState("State 1", ColorRGBA.Red);
+        ts2 = new TestState("State 2", ColorRGBA.Green);
+        ts3 = new TestState("State 3", ColorRGBA.Blue);
+        ts1.setNextState(ts2);
+        ts2.setNextState(ts3);
+        ts3.setNextState(ts1);
+        stateManager.attach(ts1);
+    }
+
+    @Override
+    public void update() {
+        if (speed == 0 || paused)
+            return;
+
+        super.update();
+        float tpf = timer.getTimePerFrame() * speed;
+        stateManager.update(tpf);
+        renderManager.render(tpf);
+    }
+}

Guys, I am considering doing a rather significant change: remove the ViewPort lists inside RenderManager, and make rendering a bit more manual.



Everything works fine if the user has full control over the application, he just adds and manipulates the view-ports in the render manager as intended, but when you add an encapsulation mechanism like AppStates it all becomes chaos since now someone has to manage attaching those ViewPorts, and in what order they are rendered, how they are cleared, etc. That means AppStates have too much control.



With the new change, instead you will have to render the viewport manually, using RenderManager.renderViewPort(). So AppStates could have their own ViewPort object which they can manipulate, attach scenes into, etc, and then in their render() callback just call renderManager.renderViewPort(myViewPort).



What do you think? Is this going in the right direction?

If we introduce a system to prioritize viewports we should extend it further for the AppStates. As said, a physics AppState for example has no interest in the rendering part but needs to be inserted at the right update position or other states wont have the physics info applied to the spatials. The "control" that momoko means is that when so much happens with AppStates, simply hoping for the user to keep the update order clean will probably fail. I also dont think we should move everything that happens internally or is used right now (like the render() calls etc.) should be moved to AppState yet.

About AppState not being used instead of Apps now in the demos, thats simply because its new in the engine.

I agree with the idea of prioritizing states – makes a lot of sense to insure that updates creating prerequisite data get fired off early. Perhaps instead of a single priority list, a map/dictionary of simple lists keyed by an enum. For example:



enum RenderPriority {

First = 0, Background = 1, Middle = 2, Foreground = 3, GUI = 4, Last = 5, Default = 2

}



and similarly for states:



enum StatePriority {

First = 0, Input = 1, Physics = 2, Middle = 3, Geometry = 4, Output = 5, Last = 6, Default = 3

}



(all name are tenative. You guys will have a much better idea what is needed than I will at this point.)



This eliminates the need to sort the priority list, or to manage the list order when adding elements. For elements at a given priority, it should never matter what order the elements are processed in.



If you can see a reason for named states similar to what I suggested for named viewports, then I'm starting to see a need for a common element naming mechanism (type+instance ID) and a class to create those names (NamedElementID<Type> ??). Given a common naming system, we can encapsulate the named list manager as a class that AppStateManager and RenderManager base off of (NamedPriorityManager<Type> ??).



Normen: Your bit about moving things around was a little confusing. I'm looking towards a place where AppState might be better described as an Updatable type that is concerned with nothing but processing recurring game logic and handling events from the sources it has "chosen" to listen to.



Off Topic: Naming Conventions: Its been a while since I've done Java, but somewhere along the line from some language, I picked up the idea that abstract classes that implement the bare bones of an interface should be named [InterfaceName]Base… is the Java standard to use Abstract[InterfaceName] ?? To me that is about as bad as the Microsoft convention of prefixing classes with C in C++ projects, or the I prefix for interfaces in C#. I much prefer having related classes show up next to each other in a class lists popup, and I hate having to type a long prefix when there are dozens of classes using the prefix.

Ascendion said:

[InterfaceName]Base... is the Java standard to use Abstract[InterfaceName] ??

Well kind of, yes. Normally you have the MyClassType interface with all info and the AbstractMyClassType abstract class.