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();
}
}