Getting to know AssetManager

One of the main advantages of using AssetManager is that resources will be reference counted. This means that they will be allocated in memory just once; it doesn't matter how many times you use it in code. This also includes safe dependency disposal; for instance, if an asset X depends directly on another asset Y, the latter will not be deleted until the former has been disposed of.

In terms of organization and clarity, it is a good point to gather all resources in the same place, so it is a recommended practice to always include AssetManager.

Obvious, but not less important, is the fact that AssetManager allows us to load resources whenever we need them, neither before nor after. We have to bear in mind that Libgdx's target platform can include mobile devices, not only desktop, so we must be aware of the resources and not deliberately waste them.

Getting ready

The sample projects are required to follow this recipe; make sure you already have them in your Eclipse workspace. Right-click on the Package Explorer panel, select Import, and then click on Gradle Project. They are under the [cookbook]/samples folder. If you have any problems, feel free to reread Chapter 1, Diving into Libgdx.

How to do it…

The code for this recipe can be found in the AssetManagerSample class. By default, Libgdx comes with an out-of-the-box AssetManager class that we need to declare:

AssetManager manager;

We place its initialization on the create() method, followed by an asset loading:

public void create() {	
   ...
manager =  new AssetManager();
manager.load("data/loading_screen/background.png", Texture.class);
   ...
}

When calling the constructor of AssetManager, its instance is set up, which is capable of loading any kind of built-in Libgdx asset type, such as Texture, BitmapFont, TextureAtlas, Music, Sound, Skin, and ParticleEffect. Once the manager is initialized, we enqueue the asset load by calling load(), which doesn't perform the actual load operation yet, the background texture asset to be brought from disk to memory by calling load(). Each type of AssetLoader implements the two methods loadAsync(...) and loadSync(...). The difference between them is the thread where the load is performed. Do not panic, we will dig into this later.

Note

A thread is an execution context for a set of instructions.

In the way that is written in the previous code, we can load any other type of resource, for instance:

manager.load("data/font.fnt", BitmapFont.class);
manager.load("data/music.mp3", Music.class);
manager.load("data/pack", TextureAtlas.class);
manager.load("data/skin.json", Skin.class);
manager.load("data/particle.p", ParticleEffect.class);

Alternatively, extra parameters, related to the type itself, can be passed to Texture, BitmapFont, TextureAtlas, Skin, and ParticleEffect loaders:

BitmapFontParameter param = new BitmapFontParameter();
param.flip = true; // To turn over the font image
param.magFilter = TextureFilter.Linear; // See Texture filters in Chapter 2
manager.load("data/font.fnt", BitmapFont.class, param);

There are several cases, such as loading or splash screens, where you will need to make sure that all queued assets have been properly loaded into memory. To achieve this, we can call:

manager.finishLoading();

Note

Note that the finishLoading() method blocks as it runs on the thread it is being called from. So, the program execution will wait until all the resources are allocated from the disk. This is okay, since we're only loading a few resources in the previous example, and they will not take a long time to be allocated.

In the rest of the cases, you must use update(...) to tick and query whether the load is done or not in the context of the rendering thread. In the next recipe, we will provide a proper example of this.

As a Libgdx program does not live by loads alone, we need to be able to retrieve the instance of any asset anywhere in the code. To carry this out, we make use of the get(...) method:

Texture background = manager.get("data/loading_screen/background.png", Texture.class);

We can also simply use the following:

Texture background = manager.get("data/loading_screen/background.png");

Note

Be aware that while not supplying the class parameter is more convenient, there is a performance penalty due to the need to look for the asset in a larger number of resource containers.

Be aware of the existence of the resource that you are trying to retrieve because it can lead you to a GdxRuntimeException if it is not stored in memory.

In order for you not to be in danger and ensure that the asset has been loaded, you can use the following:

if(manager.isLoaded("data/background.png")) {
   // Let's have some fun with the asset
   background = manager.get("data/loading_screen/background.png", Texture.class);
}

To check the asset, you can use the following:

if(manager.contains(background)) {
   // Let's have some fun with the asset
}

Once the asset is not needed anymore, you must free it to avoid memory leaks. Here is where you say: "oh yes! dispose()! I have read about it ten times already!" I am sorry to inform you that when using AssetManager, it is slightly different:

manager.unload("data/loading_screen/background.png");

The reason why we use AssetManager to unload resources is the famous reference count, which gets into action, making the former unload ineffectively until the reference count for the asset is set to zero or, in other words, it is not directly or indirectly used anywhere else in the program at the moment. Consequently, it is totally advised not to dispose of assets manually while using AssetManager.

It is interesting to point out that we might want to free all assets, no matter whether queued or loaded, at once, instead of doing it one by one. It is as easy as shown in the following code:

manager.clear();

Nevertheless, by using the clear() method, AssetManager is still alive. What about getting rid of all resources plus the manager itself?

manager.dispose();

Writing the preceding line of code in the dispose() method of your container class is usually a good idea.

You should now be able to run the sample, play around with it, and check for yourself.

How it works…

Assets follow a simple life cycle. There are two possible locations for the assets: memory and disk. The transition between the two states relies on dependencies and referencing counting. The following figure illustrates this:

How it works…

In a Libgdx program, you can load or unload assets through AssetManager. These operations will always modify the reference count. Sometimes, it might require loading other linked assets. This happens, for instance, with map files that usually depend on different images.

However, the actual transition of the asset from disk to memory is only effective for the first time load is called, since the reference count's value is one. Likewise, the asset is freed up from memory whenever the reference count stores a zero.

At this point, you have a decent understanding of what you are capable to do with AssetManager. However, there is still some hidden black magic that has not shown up on these pages.

Is it not kind of weird that we only supply a String object and we get the asset? Where does Libgdx search for the file? As you will know from the The first approach to file handling in Libgdx recipe of Chapter 5, Audio and File I/O, the FileHandleResolver interface is responsible for this. More precisely, the resolve(String file) method returns FileHandle that represents the specified file.

As you already understand, we can set an instance from a class that implements the FileHandleResolver interface as the default way to go for asset loaders:

AssetManager manager = new AssetManager(new LocalFileHandleResolver());

There's more…

Undoubtedly, you liked the reference count feature of the built-in AssetManager class, but sometimes, you might have oversights unloading resources at some part of the code, so the reference count is never zeroed and, consequently, your tasty memory bytes are occupied.

A quick and simple solution to this problem can be:

int refCount = manager.getReferenceCount("data/myfont.fnt");

Easy! We can compare how many references there should be and the actual references at a certain part of the code, but what if myfont.fnt is not the direct leaker? Maybe it depends on some other file:

Array<String> dependencies = manager.getDependencies("data/myfont.fnt");

Finally, it might be that we do not have a clue of what asset is causing the leak, so we need a general overview:

String diagnostics = manager.getDiagnostics();

The diagnostics string will now contain the reference count and dependency information for all assets in the manager.

See also

  • After a complete overview of the AssetManager class, it is a good idea to carry on and keep reading the Asynchronous asset loading recipe in order understand how to interact with the player while AssetManager is processing.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.54.245