[SOLVED] Switch AppState

Hello!
I have two appstates that are two menus. The first is the main menu and the second is another menu.
Now the main menu works very good, but when i try to initialize the second I have some white flashes and I see the elements appear and disappear.
So, I want to initialize the second menu, but how?
I used this:

@Override
protected void initNext(long ms) {
    scheduler.schedule(new TimerTask(){
            @Override
            public void run() {
                setEnabled(false);
                PlayInitMenu nextPIM = new PlayInitMenu((SimpleApplication)app, guiFont, settings);
                stateManager.attach(nextPIM);
                nextPIM.setEnabled(true);
        }
    }, ms);
}

Where PlayInitMenu is the second menu. This method is in the MainMenu class.

The two menus are extended to an another class called MenuAppState, that contains the menu main logic.

Thanks.

None of that code is the problem. I suspect you have bad sharing going on between your two app states.

Why do you initialize the second one at a delay? It’s not the problem but I suspect the answer will be illuminating.

I delay the initialization because there is an animation when you switch the menu, and if I switch before the end of the animation I don’t see it.

Can you describe your process in a bit more detail? If animation timing is affecting it then you have deeper issues. What are all the code paths that could result in the second menu being removed?

Edit: P.S. - if that timer stuff is using JDK timers, be very, very careful with what thread is doing what - I don’t think that’s the issue here but it could cause other problems.

The timer isn’t the issue.

I can almost guarantee the cleanup of one of his app states is nuking the stuff created in the other one. But that’s code we can’t see so for now it will be up to OP to figure it out on their own.

The current code posted is completely unrelated to the issue. Obviously, attaching app states will work just fine in normal cases… and not affect the other app states.

No, I use java.util.Timer

I have now tried to attach only PlayInitMenu at game and it works. So the issue isn’t in PlayInitMenu.

I have also noticed that when I press ESC to close the game, I still have the process in life. Why? And this happens always. So I think that the problem is in MenuAppState class. I now paste the code:

package mygame.appstates.main;

import com.jme3.app.Application;
import com.jme3.app.SimpleApplication;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;

import com.jme3.asset.AssetManager;

import com.jme3.audio.AudioNode;

import com.jme3.font.BitmapFont;
import com.jme3.font.Rectangle;

import com.jme3.input.InputManager;
import com.jme3.math.ColorRGBA;

import com.jme3.post.FilterPostProcessor;
import com.jme3.post.filters.FogFilter;

import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;

import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

import com.jme3.system.AppSettings;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Timer;

/**
 *
 * @author Sollazzi
 */
public abstract class MenuAppState extends AbstractAppState{
    protected final Timer scheduler = new Timer();
    
    protected final Node rootNode;
    protected final Node localRootNode;
    protected final Node guiNode;
    protected final Node localGuiNode;
    
    protected final InputManager inputManager;
    protected final AssetManager assetManager;
    
    protected final ViewPort viewPort;
    
    protected BitmapFont guiFont;
    
    protected AppSettings settings;
    
    protected Camera camera;
    
    protected RenderManager renderManager;
    
    protected AppStateManager stateManager;
    
    protected Application app;
    
    public MenuAppState(SimpleApplication app0, String localRootNodeName, BitmapFont guiFont0, AppSettings settings0){
        rootNode      = app0.getRootNode();
        localRootNode = new Node(localRootNodeName);
        localGuiNode  = new Node(localRootNodeName + ":GUI");
        inputManager  = app0.getInputManager();
        assetManager  = app0.getAssetManager();
        viewPort      = app0.getViewPort();
        guiNode       = app0.getGuiNode();
        guiFont       = guiFont0;
        settings      = settings0;
        camera        = app0.getCamera();
        renderManager = app0.getRenderManager();
        stateManager  = app0.getStateManager();
        app = app0;
        
        screenWidth  = settings.getWidth();
        screenHeight = settings.getHeight();
    }
    
    protected int screenWidth;
    protected int screenHeight;
    
    @Override
    public void initialize(AppStateManager stateManager, Application app){
        super.initialize(stateManager, app); //superclass init
        
        setUpGraphics();
        setUpLight();
        setUpInput();
        setUpAudio();

        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);
        fog = new FogFilter();
        fog.setFogColor(new ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f));
        fog.setFogDistance(-1);
        fog.setFogDensity(0f);
        fpp.addFilter(fog);
        viewPort.addProcessor(fpp);

        rootNode.attachChild(localRootNode);
    }
    
    @Override
    public void cleanup(){
        rootNode.detachChild(localRootNode);
    }
    
    protected abstract void setUpGraphics();
    
    protected abstract void setUpLight();
    
    protected abstract void setUpInput();
    
    protected abstract void setUpAudio();
    
    protected abstract void initNext(long ms);
    
    private final float moveVel = 100f;
    
    protected final String BTN = "Interface/Pictures/Button.png";
    protected final String BTNP = "Interface/Pictures/ButtonPressed.png";
    protected final String BCKG = "Interface/Pictures/Background.png";
    
    private int xbtnpos = 0;
    
    private int b;
    
    @Override
    public void update(float tpf) {
        int a = 0;
        
        if(xbtnpos < screenWidth / 8 + screenWidth - screenWidth / 4){
            for(Spatial element : localRootNode.getChildren()) element.move(moveVel, 0f, 0f);
            for(Spatial element : guiNode.getChildren())       element.move(moveVel, 0f, 0f);
            for(Rectangle area : txtAreas){
                System.out.println(area.x);
                area = new Rectangle(area.x + moveVel, area.y, area.width, area.height);
                txtAreas.set(a, area);
                a++;
            }
            
            xbtnpos += moveVel;
        }
        
        if(changescreen){
            for(Spatial element : localRootNode.getChildren()){
                element.move(moveVel, 0f, 0f);
            } 
            for(Spatial element : guiNode.getChildren()){
                element.move(moveVel, 0f, 0f);
            }
            /*if(fogDensity <= 2f){
                fogDensity += 0.05f;
            
                fog.setFogDensity(fogDensity);
            }*/
            
            initNext(500);
        }
    }
    
    private float fogDensity = 0;
    
    private FogFilter fog;

    @Override
    public void render(RenderManager rm) {
    }
    
    protected Map<String, Spatial> menuMeshes = new HashMap<>();
    
    protected Map<String, AudioNode> audio = new HashMap<>();
    
    protected ArrayList<Rectangle> textRects = new ArrayList<>();
    
    protected void addMesh(String name, Spatial mesh){
        menuMeshes.put(name, mesh);
    }
    
    protected Spatial getMesh(String name){
        return menuMeshes.get(name);
    }
    
    protected void addAudio(String name, AudioNode aud){
        audio.put(name, aud);
    }
    
    protected AudioNode getAudio(String name){
        return audio.get(name);
    }
    
    public void playAudio(String name){
        audio.get(name).playInstance();
    }
    
    private boolean changescreen = false;
    
    protected void setChangeScreen(boolean truth){
        changescreen = truth;
    }
    
    protected boolean getChangeScreen(){
        return changescreen;
    }
    
    protected ArrayList<Rectangle> txtAreas = new ArrayList<>();
}

Thank you.

EDIT: the changescreen boolean is true after the button is clicked.

Note: you call this once per frame while changeScreen is true… I suspect that can’t be right.

As to your “why does my app keep running?”… it’s probably because of the Timer thread. You can see for sure if you run your app from the command line. When you exit, the command line should still be “hung” and you can hit Ctrl-Brk to get a thread dump.

There is really no reason to use Timer as you can simply count time in update and call whatever you want. Timer will be dangerous in a JME application because it will call your code from a non-render thread… which means you shouldn’t do anything at all with the scene graph.

So I have to call initNext without timer.

Ok, can I use System.currentTimeMillis() to do this?
And I have to call initNext one time, right? I can do this turning changescreen false after the 500ms.

What do you suppose tpf is?

What do you suppose would happen if you added tpf to some value every time update was called? Say, time += tpf;

I wonder how long it would take before time was the same as or more than 500 ms? (Hint: half a second… time >= 0.5f)

tpf is the time since the last update, right?

Thank you!!!

What do you suppose happens now when you are calling it 60 times a second or more?

Oh, a disaster! Now I’m understanding the issue.

I really really really really really suggest you do the tutorials since 90% of the first one million questions you are going to ask are covered there.

For example: https://jmonkeyengine.github.io/wiki/jme3/beginner/hello_main_event_loop.html

You really helped me. The problem is finally solved! I changed the code as you told me and it works.

Thank you!