A Loading Screen Controller Class

Yeah, i wanted to show something, i am using right now.
While developing, i a currently changing things. There are things i learn by time and then it changes part of my code.
As i have to rewrite things to reuse, or have to change things, i am watching, which parts belong to what and try to encapsulate things usable.

As i made my first AppState, i implemented a class “AppState”, which is not really more than the tutorial about AppStates show.
It give me a quick scheme to add capability to work with the apps most important abilities.

Then i implemented am AbstractScreenController, implementing the ScreenController Interface correspondendly while inheriting here AppState is very useful. It’s like an AppState with added Nifty capabilities. Although i need to pass through some additional Information, so i implemented myself an ApplicationInterface here, since i now a better solution.

Then i moved over to an loaded screen controller, implementing the appqueue and so on.
This class, called “AbstractLoadingScreenController” inherits AbstractScreenController and adds the ThreadPool and the instance vars, needed to add loading functionality.

Then i had to implement myself a working GameStateManger as an AppState to manage now, what state i am in now.
This class loads a configuration of states, the loading capabilities and the solution (switches automatically ? switches to which state?) and then can be called to switch the states as wanted.

As i can use it, the actualState has a value “loading”, which says, if in this state, the load value is set to true, so the ScreenController will decide to load another portion of data, related to this node.

This threw out the “load” value and the rendering features, only leaving in the threaded part.
to use this, i implemented two abstract methods, my final implementation has to implement, to vary the behavior of this screen controller at a given time.

what this now can do is do several updateCalls and several loading options, one for each state, if the state is a loading state.

if my state autoswitches (after initializing start, it switches to menu. in menu we load thing, but when finished, we don’t want to switch to “login” state. This is done, if the user tries to login and the state is not loading at time), my implementation gets the actualState to decide, which behavior to choose next.

I hope this is enough text given to describe, what this shown class is doing:

/** Abstract Screen Controller
 * @author Yves Tanas
 */
public abstract class AbstractLoadingScreenController extends AbstractStateScreenController{
/** ThreadPoolExecutor for multithreading */
protected ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(10);
/** Future progress variable*/
protected Future loadFuture = null;
/** Shutdown of the ThreadPoolExecutor */
public void stop() {
    //the pool executor needs to be shut down so the application properly exits.
    exec.shutdown();
}
/** implementation of simpleUpdate to handle future value and progress
 * @param tpf float time per last frame
 */
public void simpleUpdate(float tpf) {
    if (load) {
        if (loadFuture == null) {
            //if we have not started loading yet, submit the Callable to the executor
            loadFuture = exec.submit(loadingCallable);
        }
        //check if the execution on the other thread is done
        if (loadFuture.isDone()) {
            // do sth. useful
            simpleUpdateCall(tpf);
            // switch the state
            switchLoadingValue();
        }
    }
}
/** this is called, when loadFuture in simpleUpdate(float tpf) isDone. */
public abstract void simpleUpdateCall(float tpf);
//this is the callable that contains the code that is run on the other thread.
//since the assetmananger is threadsafe, it can be used to load data from any thread
//we do *not* attach the objects to the rootNode here!
Callable<Void> loadingCallable = new Callable<Void>() {
    public Void call() {
        Element element = getLoadingScreen();
        // load callable content
        loadCallable();
        return null;
    }
};  
/** this is called during callable loading */
public abstract void loadCallable();
public void setProgress(final float progress, final String loadingText, final long time) {
    try{
        exec.submit(doInConstantTime(new Callable<Void>(){
            public Void call() { return null; }}, time, progress, loadingText));            
    } catch (Exception ex){}
}
<Void> Callable doInConstantTime(Callable<Void> task, long millis, float progress, String loadingText) throws Exception{
    Future<Void> future = exec.submit(task);
    Thread.sleep(millis);
    if (future.isDone()) {
        setProgress(progress, loadingText);
        return null;
    } else {
        future.cancel(false); // or true? 
        return null;
    }
}    
/** Constructor just gives over the app */
public AbstractLoadingScreenController(SimpleApplication app){ super(app);}
public AbstractLoadingScreenController(){ super();}    
}

The implementation of TestLoadingScreen now looks like this:

public class ManagedLoadingScreenController extends AbstractLoadingScreenController{

private TerrainQuad terrain;
private Material mat_terrain;

public ManagedLoadingScreenController(SimpleApplication app) {
    super(app);
}

public ManagedLoadingScreenController() {
}

@Override
public void simpleUpdateCall(float tpf) {
    System.out.println("doing result");
    //these calls have to be done on the update loop thread, 
    //especially attaching the terrain to the rootNode
    //after it is attached, it's managed by the update loop thread 
    // and may not be modified from any other thread anymore!
    nifty.gotoScreen("end");
    //nifty.exit();
   // guiViewPort.removeProcessor(niftyDisplay);
  //  flyCam.setEnabled(true);
   // flyCam.setMoveSpeed(50);
    rootNode.attachChild(terrain);
    load = false;
    actualState = app.getStateManager().getState(GameStateManager.class).onLoadComplete();
}

@Override
public void loadCallable() {
    Element element = nifty.getScreen("loadlevel").findElementByName("loadingtext");
    textRenderer = element.getRenderer(TextRenderer.class);

    mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
    mat_terrain.setTexture("Alpha", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png"));
    //setProgress is thread safe (see below)
    setProgress(0.2f, "Loading grass",3000);

    Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg");
    grass.setWrap(Texture.WrapMode.Repeat);
    mat_terrain.setTexture("Tex1", grass);
    mat_terrain.setFloat("Tex1Scale", 64f);
    setProgress(0.4f, "Loading dirt",3000);

    Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg");

    dirt.setWrap(Texture.WrapMode.Repeat);
    mat_terrain.setTexture("Tex2", dirt);
    mat_terrain.setFloat("Tex2Scale", 32f);
    setProgress(0.5f, "Loading rocks",3000);

    Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg");

    rock.setWrap(Texture.WrapMode.Repeat);

    mat_terrain.setTexture("Tex3", rock);
    mat_terrain.setFloat("Tex3Scale", 128f);
    setProgress(0.6f, "Creating terrain",3000);

    AbstractHeightMap heightmap = null;
    Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png");
    heightmap = new ImageBasedHeightMap(heightMapImage.getImage());

    heightmap.load();
    terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap());
    setProgress(0.8f, "Positioning terrain",3000);

    terrain.setMaterial(mat_terrain);

    terrain.setLocalTranslation(0, -100, 0);
    terrain.setLocalScale(2f, 1f, 2f);
    setProgress(0.9f, "Loading cameras",3000);

    List<Camera> cameras = new ArrayList<Camera>();
    cameras.add(app.getCamera());
    TerrainLodControl control = new TerrainLodControl(terrain, cameras);
    setProgress(1f, "Loading complete",3000);
}
 
public void showLoadingMenu() {
    nifty.gotoScreen("loadlevel");
    load = true;
}    

}

finally you can see, i added a small class to make the loading behavior a little bit … waiting, so that i really can see that this is working.

public class TestManagedLoadingScreenController  extends AbstractSimpleApplication {
 
   private NiftyJmeDisplay niftyDisplay;
    private Nifty nifty;

    public static void main(String[] args) {
        TestManagedLoadingScreenController app = new TestManagedLoadingScreenController();
        app.start();
    }
 
    @Override
    public void simpleInitApp() {
        
        registerJSONLoader();
        GameStateManager gm = new GameStateManager(this);
        gm.initialize(stateManager, this);
        stateManager.attach(gm);        
        
        flyCam.setEnabled(false);
        niftyDisplay = new NiftyJmeDisplay(assetManager,
                inputManager,
                audioRenderer,
                guiViewPort);
        nifty = niftyDisplay.getNifty();
        
        ManagedLoadingScreenController msc = new ManagedLoadingScreenController(this);
        msc.initialize(stateManager, this);
        stateManager.attach(msc); 
        
        GameScreenController gsc = new GameScreenController(this);
        gsc.initialize(stateManager, this);
        stateManager.attach(gsc);
        
        nifty.fromXml("Interface/nifty-managed-loading.xml", "start", msc);
        nifty.addXml("Interface/end.xml");        
 
        guiViewPort.addProcessor(niftyDisplay);
        getStateManager().getState(ManagedLoadingScreenController.class).showLoadingMenu();
    }
 
    @Override
    public void simpleUpdate(float tpf) {
        getStateManager().getState(ManagedLoadingScreenController.class).simpleUpdate(tpf);
    }
 
    @Override
    public void stop() {
        super.stop();
        getStateManager().getState(ManagedLoadingScreenController.class).stop();
    }
}
1 Like

Here another productional use case. This is, what i wanted to do with this:

@Override
public void simpleUpdateCall(float tpf) {
    if(actualState.getStateName().equalsIgnoreCase("START")){
        simpleStartUpdate(tpf);
    } else if(actualState.getStateName().equalsIgnoreCase("MENU")){
        simpleMenuUpdate(tpf);
    } if(actualState.getStateName().equalsIgnoreCase("LOGIN")){
        simpleLoginUpdate(tpf);
    }         
} 

@Override
public void loadCallable() {
    if(actualState.getStateName().equalsIgnoreCase("START")){
        loadMenu();        
    } else if(actualState.getStateName().equalsIgnoreCase("MENU")){
        loadMenu();
    } if(actualState.getStateName().equalsIgnoreCase("LOGIN")){
        loadLogin();
    } 
}

When you said “app states”, I thought you meant JME’s app states… which fits here. But your latest post where you are forking a simple update implies that you are not using JME app states… since they would already negate the need for that. (Even if that weren’t true, it seems like you want an enum and simpler code rather than the sprawling if/else chains.)

Anyway, might be good to look into JME’s concept of app states. It’s very powerful. Generally, you end up with almost no code in your application subclass and you won’t need code like your simpleUpdateCall() code above.

Oh yes, i am trying to learn about this and the controller itself is an appstate likewise my gamestatemanager is.
and yes, it is not working as wanted yet. it really is possible, that i am missing big points.
the last post only shows, how my screen controller decides, which work to do in the each section.
the loading bar runs through ones, then manipulates the rootNode and then starts another loading section. after finish it fades out the bar and waits for initialization of the next state. that’s what should be done ^^
somehow it only works for my first run yet… something is broke
everything happens on the same screen.

Just because they happen on the same screen doesn’t mean they need to be done in the same app state… though I guess nifty screen controllers do kind of tie it up that way. It’s a bit unfortunate.

The alternative is to treat the screen app state/controller like a service that the other app states use.

At any rate, the key will be making sure that your app state initialize() and terminate() are properly reversible.

i really wanted an instance, where i can put in all of that loading stuff. in fact this screen controller doesn’t do anything else, for this i think it even hadn’t to be a screen controller, but only a loaded app state, that could be possible. Because of that i also reduced the code of all that nifty stuff out of it into the upper class, so i only can make use of it.
i tried to achieve a nice possibility to load stuff and show, that he is doing that, while also be able to controll what happens on the screen.
it loads a list of videos my mediamanager can play with and show after loading is complete.
it loads my universe which can be used by the universe app when finished.
and since then it shows information on the screen, giving some options, which don’t collide with what this loader is trying to implement right now.
that somehow was my idea to make out of that.

So this is now a working one.
The only thing to do is set the “load=false” value, while being in the simpleUpdateCall to initiate the call of the next state. and if this is a loading one, the load begins again.
another hint is, that it only does this, when the actual state is one autoswitching to the next node.

following this i had to invent a new state, but now this construct is a working and robust one:

/** Abstract Loading Screen Controller
 * @author Yves Tanas
 */
public abstract class AbstractLoadingScreenController extends AbstractStateScreenController{
    
    /** ThreadPoolExecutor for multithreading */
    protected ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(10);
    /** Future progress variable*/
    protected Future loadFuture = null;
    
    /** Shutdown of the ThreadPoolExecutor */
    public void stop() {
        //the pool executor needs to be shut down so the application properly exits.
        exec.shutdown();
    }

    /** implementation of simpleUpdate to handle future value and progress
     * @param tpf float time per last frame
     */
    public void simpleUpdate(float tpf) {
        if (load) {
            if (loadFuture == null) {
                //if we have not started loading yet, submit the Callable to the executor
                loadFuture = exec.submit(loadingCallable);
            }
            //check if the execution on the other thread is done
            if (loadFuture.isDone()) {
                // do sth. useful
                simpleUpdateCall(tpf);

            }
        } else {
            if(actualState.isAutoSwitching())
            {
            loadFuture = null;
            switchLoadingValue();
            load = actualState.isLoading();

            }
        }    
    }

    /** this is called, when loadFuture in simpleUpdate(float tpf) isDone. */
    public abstract void simpleUpdateCall(float tpf);
    
    //this is the callable that contains the code that is run on the other thread.
    //since the assetmananger is threadsafe, it can be used to load data from any thread
    //we do *not* attach the objects to the rootNode here!
    Callable<Void> loadingCallable = new Callable<Void>() {
 
        public Void call() {
            Element element = getLoadingScreen();

            // load callable content
            loadCallable();
            
            return null;
        }
    };  
    
    /** this is called during callable loading */
    public abstract void loadCallable();
    
    public void setProgress(final float progress, final String loadingText, final long time) {
        try{
            exec.submit(doInConstantTime(new Callable<Void>(){
                public Void call() { return null; }}, time, progress, loadingText));            
        } catch (Exception ex){}
    }
    
    <Void> Callable doInConstantTime(Callable<Void> task, long millis, float progress, String loadingText) throws Exception{
        Future<Void> future = exec.submit(task);
        setProgress(progress, loadingText);
        Thread.sleep(millis);
        if (future.isDone()) {

            return null;
        } else {
            future.cancel(false); // or true? 
            return null;
        }
    }    
    
    /** Constructor just gives over the app */
    public AbstractLoadingScreenController(SimpleApplication app){ super(app);}
    public AbstractLoadingScreenController(){ super();}    
}

And a look into my setup file:

    "gameStates"    :   [
            { 
                "name"          : "START",
                "isLoading"     : "true",
                "loadingMessage": "Loading Videos",
                "autoswitches"  : "true",
                "switchesTo"    : "MENULOAD"
            },
            { 
                "name"          : "MENULOAD",
                "isLoading"     : "true",
                "loadingMessage": "Loading Standard Resources",
                "autoswitches"  : "true",
                "switchesTo"    : "MENU"
            },
            { 
                "name"          : "MENU",
                "isLoading"     : "false",
                "loadingMessage": "Menu",
                "autoswitches"  : "false",
                "switchesTo"    : "LOGIN"
            },
            { 
                "name"          : "LOGIN",
                "isLoading"     : "true",
                "loadingMessage": "Connecting to Game",
                "autoswitches"  : "true",
                "switchesTo"    : "GAME"
            }               
    ]