Long story: I am trying to make a loading screen in LibGDX, not only will I need to load assets like textures and audio, I will need to create world objects (more than 10 million objects) - I might want to look into decreasing amount of objects in the future, but my question still applies whether it's one object or trillions.
During the object initialization, the GDX render()
method stops, due to lag I presume. It will lag that much the whole application goes into "Not responding" for a few seconds.
I have been researching for months without much to find. 90% of the topics I find or people I ask, always say the same thing; use an AssetManager. I've tried this, but it seems to only support assets, not heavy processing world objects. Someone told me it could support custom classes, however I never got this to work due to lack of documentation.
The BEST topic which is most similar to mine, is this one which gave me an idea to use Gdx.app.postRunnable() inside a Gdx.app.postRunnable(). Result was this:
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {
// do some heavy processing
// set loading screen percent to x%
Gdx.app.postRunnable(new Runnable() {
@Override
public void run() {
// do some more heavy processing
// set loading screen percent to y%
}
});
}
});
The solution worked great. It did heavy processing, set the percent of loading screen and then drawed it. So for displaying sake, this solution solved my problem where the percent was never drawed. However, this solution still turn the application into "Not responding" between the heavy processes; this freezes eventual music playing.
By adding enough postRunnables inside of postRunnables, there will be no lag, as the heavy processes no longer exist - instead it is built upon heavy processes split into mini ones, solving the "Not responding" state. Although, this many postRunnables are not very practical for "clean code", as it requires more than 30 postRunnables, and the code above is just 2. It's very easy for the code to turn ugly, therefore I seek an alternative.
This post was quite interesting as well, explaining the exact same problem I am facing, however the result did not work out.
I achieved this in Java Swing by having two threads; one main and a loadingScreen thread (LCThread). When entering the loadingScreen, the LCThread started to draw the loadingScreen while the main thread did the heavy processing. When finished, the main thread used the objects it previously processed. Sadly I cannot convert this into LibGDX as two threads cannot draw seperately.
Short story: I need to code a loading screen which loads heavy background processing (initializing a lot of objects) while drawing progress (handled in the render()
method) and playing music while displaying the loading screen, all without lagging the application that much it enters "Not responding".
Do you have any advice?
You could break up your GameObject so that its frees up the processor. Hopefully this loads in chunks and keep the render loop free to play music. Basicly all the loading and creating assets goes through the AssetManager and in the render loop its check to see what state the game is in and acts accordingly. Custom GameObject loader. GameObject is just a generic class apply this to your project specifecs.
In the AssetManger update( int millis ) method it yields the CPU to a specified number of milliseconds. If you break up all the processing and put it in their own AssetLoaders the AssetManager updates for that time and doesnt block the cpu.
public class GameObjectLoader extends SynchronousAssetLoader<GameObject, GameObjectLoader.GameObjectParameters> {
public GameObjectLoader( FileHandleResolver resolver ) {
super( resolver );
}
@Override
public GameObject load( AssetManager assetManager, String fileName, FileHandle file, GameObjectParameters parameter ) {
TextureAtlas atlas = assetManager.get( parameter.src, TextureAtlas.class );
ShaderProgram shaderProgram = assetManager.get( parameter.shaderSrc, ShaderProgram.class );
JsonValue json = assetManager.get( parameter.jsonSrc, JsonValue.class );
Calculation calculation = assetManager.get( parameter.id, Calculation.class );
GameObject gameObject = new GameObject(
atlas.findRegion( parameter.name ),
shaderProgram,
json,
calculation
);
assetManager.unload( parameter.id ); // unload it otherwise it stays in memory
return gameObject;
}
@Override
public Array<AssetDescriptor> getDependencies( String fileName, FileHandle file, GameObjectParameters parameter ) {
Array<AssetDescriptor> dependencies = new Array<AssetDescriptor>();
dependencies.add( new AssetDescriptor<TextureAtlas>( parameter.src, TextureAtlas.class ) );
dependencies.add( new AssetDescriptor<ShaderProgram>( parameter.shaderSrc, ShaderProgram.class, parameter.shaderParameter ) );
dependencies.add( new AssetDescriptor<JsonValue>( parameter.jsonSrc, JsonValue.class ) );
dependencies.add( new AssetDescriptor<Calculation>( parameter.id, Calculation.class ) );
return dependencies;
}
public static class GameObjectParameters extends AssetLoaderParameters<GameObject> {
// maybe you have a lot of game logic and dont need to load everything from disk make a custom loader for that too
public String id = "";
public String src = "";
public String name = "";
public String jsonSrc = "";
public String shaderSrc = "";
public ShaderProgramLoader.ShaderProgramParameter shaderParameter = null;
}
}
AssetLoaders doesnt need to have file to work with it still works with out one.
class CalculationLoader extends SynchronousAssetLoader<Calculation, AssetLoaderParameters<Calculation>> {
public CalculationLoader( FileHandleResolver resolver ) {
super( resolver );
}
@Override
public Calculation load( AssetManager assetManager, String fileName, FileHandle file, AssetLoaderParameters<Calculation> parameter ) {
// this is the heavy processing
// the AssetManager dictates how many of these per cycle will be calculated
return new Calculation();
}
@Override
public Array<AssetDescriptor> getDependencies( String fileName, FileHandle file, AssetLoaderParameters<Calculation> parameter ) {
return null;
}
public static class CalculationParameters extends AssetLoaderParameters<Calculation> {
}
}