Today I want to talk about one of the components of modern games, or rather game engines. It is not discussed by gamers, they do not write about it in advertising brochures and reviews, but without it it is impossible to create a modern graphic engine. This composer is a resource manager.
The resource manager does not directly affect the quality of the soundtrack of the game, nor its “beautifulness”; however, without a good resource manager, there will be neither a variety of soundtracks nor the riotousness of colors of the heaviest textures.
In this article I will try to describe several manager implementations. Each implementation is suitable for a project of its level, each has both advantages and disadvantages.
Basic concepts.
To talk about a resource manager, you must first enter a definition of a resource. The managers that I will consider are dealing only with virtual renewable resources. Next, I will adhere to this definition:
A resource is some object that can be accessed by reference. This object can be created, used, deleted.
In games, resources are practically all that we see and hear (models, textures, sounds, and even, sometimes, code).
The implementation is zero (no implementation)
This implementation is that each module of the future game engine loads and stores all its data itself. When creating a new game object (when loading a game), the disk is accessed; everything that needs to be loaded and saved somewhere inside the game object class. This functionality is quickly implemented, the game is loaded and works on test textures. However, with the appearance in the game of a large number of heavy textures, it starts loading and unloading for a very long time and takes up a huge amount of RAM. The reason lies in the fact that each super-beautiful, but super heavyweight texture from your designers is loaded and stored separately for each game object. That is, if there are 25 identical cubes in a game, then 25 copies of maps will be in memory, and when loading, the texture will be loaded 25 times from the disk. Not bad, right?
Advantages of this approach:
Fast implementation on small projects.
')
Cons of this approach:
Re-loading and storing identical data.
The first implementation (protomanager. Shared memory)
The texture of the cube is not unique, so it is quite possible to store one copy of it, and then just tell where this copy is to all the cubes-objects. We allocate memory for texurs, we load them into it, and when we create an object, we simply tell it where its texture lies. The main advantage of this method is the speed of implementation. The main disadvantage is non-expandability. For each new object, it is very difficult to write code loading its textures, properly transferring references to them, and monitoring the release of all memory areas with textures. Also, since we do not know the time when the object was created, the entire load load falls on the moment the game or level is loaded. Also a critical moment - the end of the game, when a huge amount of various resources will be unloaded from memory.
Advantages of this approach:
Texture is loaded only once.
Minuses:
The need for hands to prescribe where the texture is.
Large load when loading and unloading the game (level).
The implementation of the second (Full functionality)
At this stage, you can already select the resource manager as a separate unit.
A few words about how it looks:
1. The manager works with the abstract concept of "resource", which he can create, give to use or destroy. As a result, we will manage it all that we see and hear in the game. (This item is optional, but if it works only with textures, it will be a texture manager, if models are models, etc.).
2. Each resource has its own identifier that uniquely identifies this resource. In essence, this is the path to the file for this resource.
3. Work with the manager is reduced to two appeals from the outside: I want_to receive_resource (identifier) ​​and I_more_number_necessary_resource (identifier). Whether this resource has already been uploaded is the business of the manager and it does not concern anyone anymore.
Advantages of this approach:
A resource is loaded only once and only when its first clone is needed.
Relative ease of implementation.
A new block appears, which begins to eat processor cycles, but cycles are spent only with the requirements and exemptions of the resource.
Minuses:
Data has to be packaged into a resource interface.
Before the appearance of the first copy of a heavy resource, there will be a delay in loading (lag). Most of these resources will turn to load, but some will remain.
The implementation of the third. (Complicated)
In order for the drawing of a new resource not to be delayed, the notion of “unloaded resource” is introduced. That is, as soon as a request comes in, I want a resource like this, and this resource is not loaded, the manager immediately responds with a pointer to the memory where the resource will be located, but the resource itself is not there, but the flag is coded that it is not loaded. Then there are two common options:
The game continues to be played just without a resource. This option is often acceptable. For example, if you have a shooter and on the horizon (the border of the drawing) a building appeared for a couple of hundred thousand polikov, then until you reach it and it can make any contribution to the gameplay, it will be fully loaded.
First, the load resource is loaded, which weighs very little. (You go in a shooter, and a cube with a grid texture appears in the distance. In the meantime, you come up with a model with windows and a porous concrete or brick texture.)
Note: Naturally, if you threw a grenade, and you do not have an explosion animation in memory, this approach will not help you.
As for the download itself, then it is necessary to allocate either a separate stream for downloading, or to implement the update method in the manager and from time to time to call it.
Advantages of this approach:
Quite discreet loading of heavy resources.
All the advantages of an ordinary manager.
Minuses:
It is necessary to create additional threads.
Complicated resource interface.
The complexity of the manager interface (The need to create methods: issue a resource immediately).
The appearance of additional threads (or the update method), and in consequence of the increased consumption of cycles.
What's next?
Then we can distinguish two approaches to resource management.
1. This control all links to resources. As soon as no one refers to the resource, then unload it. Resource forgotten. But this technology is already the Garbadge collector.
2. Application of strategies to resources. Suppose it is necessary to visualize the process of falling drops from a tap: a drop appears (loads), flies, falls into a bowl (unloaded) and after a second the drop appears again. If you apply the unloading strategy not after everything has been forgotten about the resource, but only after some time, then you can save great time on reloading / unloading. But the strategy will greatly increase the number of spent cycles.
Instead of a conclusion.
This gradation of managers was invented by me and does not pretend to absolute completeness and accuracy. Many questions remained unsolved, for example, storage of resources and methods for their loading, but I hope that it shows the need to take into account and develop not only graphics / physics / voice acting in their projects, but also a whole layer of serving code.