📜 ⬆️ ⬇️

Zapulit on simple. Java pool manager

In developing the game, you have to go to all sorts of tricks to improve performance. In fact, if the scene slows down with each shot or generation of enemies, then the user can give up and find a faster game (the market is overloaded by them now). There are a lot of bottlenecks in the game application, but I would like to tell you about the optimization from the code side.

While testing one of my mobile games, I was unpleasantly hit by the brakes of the system during shots or explosions. It is clear that on the stationary computer the same game worked without problems. By certain tricks was found the culprit - the script responsible for the generation of explosions. It was then that I had to find a pool manager in C #, which later moved from project to project. However, for JavaScript, I did not have such.


And what is this beast?


Many games are dynamic - something explodes, someone shoots, crowds of warriors run around somewhere. I will not reveal a secret if I say that some game objects are used repeatedly. The only question is how the clone control mechanism is implemented.
')
Take a textbook example - shooting a weapon. It is clear that the bullets are one and the same object. The easiest way is to create a dynamic bullet clone, the so-called instancing. Simple, cheap and cheerful, but there are some limitations. Usually the cloning function is time consuming: the engine needs to allocate space in the memory for a new object, initialize it, etc. If the object is simple, then you can copy. But imagine that instead of a bullet, the hero shoots rockets and each of them with its own logic, is hung with particle systems, textures. Now the cloning procedure will take much more time. Do not forget about the fact that after using a clone, it must be removed from memory. All this will have a negative impact on performance in browser and mobile games.

The pool manager works on a different principle. Imagine that the desired object is preloaded into the computer's memory. At the same time, it is practically not visible for the game process, i.e. disabled. At the moment of the shot, the bullet “wakes up”, jumps to the required coordinates and performs its functions. But when she worked, for example, hit the target, she was not destroyed, but again she was put into sleep mode. And so in a circle: initialization, action, hibernation, initialization, action, hibernation ...

In this case, the program does not waste time loading the object into the scene, because it is already there, only disabled. True, the question arises, what happens if the shooting goes in bursts and the first bullet simply does not have time to work out? This is where the managerial qualities of the pool manager are manifested. For this purpose, the necessary number of copies of the object is created in advance in the memory of the machine. When requesting a new bullet, the manager looks at his list of clones in search of a free one and, if there is one, gives it to work. At the same time, spent objects are returned back to the pool. The work takes place on the principle of a stack - first went, last went.

Such an approach significantly relieves the system, allows you to quickly prepare the object, but the RAM is wasted. In principle, the level designer needs to correctly determine the number of clones in order to balance the memory consumption and the requirements of the task.

In real conditions


I needed a pool manager for a browser project using the Blend4Web engine. Therefore, everything that is further written refers to this framework. However, the main code is very simple and can be easily adapted for any JavaScript-based engine.

When there was a need for a pool of objects, I first rushed to scour the Internet. What was my surprise when it turned out that there is no suitable code in nature. Probably, this is a simple task and no one bothered with sharing the code. Only after a long search, I managed to find on one bourgeois site a primitive workpiece, which, after reworking and fitting for Blend4Web, I suggest you to use.

The main requirements for the manager were set as follows:


As a result, work with the manager is built literally on two teams:

//     "New pool"   object  size poolmanager.create ("New pool", size, object); //       "New pool"   coord poolmanager.spawn ("New pool",coord); 


By tradition, I try not to mix everything in one script. My pool manager concept consists of:


First consider the setting of the object. There is a special template that is necessary for the proper functioning of the manager. It is there that the initial initialization of the object, its call and “destruction” is performed by a command from the manager.

 exports.my_object = function() { this.inUse = false; //   this.init = function() { }; //  this.spawn = function (coord) { this.inUse = true; } //       this.despawn = function() { this.inUse = false; }; } 


So, the key point is the inUse variable, which is responsible for the object's activity flag. By default, the clone is turned off and calmly “rests” in a secluded corner of the scene. But if the manager pays attention to the “sonya” (free objects are determined when inUse = false), this is followed by his awakening using the internal function this.spawn. Coordinates are transferred to it to transfer the object in the scene. There may also be a service code, for example, motion activation.

After the completion of the work, the clone itself calls the internal function this.despawn. It takes all the necessary actions to reverse the transition of the object to sleep. As you can see, the workpiece is very simple and now we will fill it with additional variables.

 ... this.fileName = "bullet1.json"; this.name = "bullet1"; this.id; this.init = function(data_id) { this.id = data_id; }; … 


The object template must store unique information about itself so that the manager can work with it, because it is in the manager that the physical loading of resources occurs, the call to initialization functions and spawn.



Due to the peculiarities of the Blend4Web boot system, objects with the same name may be in memory, but only if they are located in different layers of the scene. You can learn more about the b4w file system in this article .

The function this.spawn is called by the manager to activate the clone and transfer it to the right place in the scene. And note that this is where the object's renderer is enabled using the show_object () function. Since then, the clone becomes a full participant in the gameplay.

 this.spawn = function (coord) { this.inUse = true; var obj = m_scene.get_object_by_name(this.name,this.id); m_trans.set_translation_v(obj, coord); m_scene.show_object(obj); console.log("Spawn!"); } 


The last important function is this.despawn. In this example, I simply disable the object renderer using the hide_object () function.

 this.despawn = function() { this.inUse = false; var obj = m_scene.get_object_by_name(this.name,this.id); m_scene.hide_object(obj); console.log("Despawn!"); }; 


Now consider the actual pool manager. Everything is wrapped around using arrays. In total, the program has two global arrays. The first _pools [] is an associative list of the names of pools created by the user and references to arrays with clones. The second _pools_size [] is responsible for storing the sizes of local arrays.

 //    var _pools = {}; var _pools_size = {}; 


At the moment, the manager has two external functions: create () and spawn (). By name, it is clear which one is responsible for what. Please note that after loading the three-dimensional object turns off and becomes invisible. The final step in loading is to invoke the initial clone function. Everything, after this operation in memory there is a “new” pool filled with objects.

 //--------------------------------------------------- //       //name -  //size -  //obj -   exports.create = function(name, size, obj) { var temp_pool = new Array(); _pools[name]=temp_pool; _pools_size [name] = size; for (var i = 0; i < size; i++) { var clone_obj = new obj(); var data_id = m_data.load(APP_ASSETS_PATH + clone_obj.fileName,null,null,false,true); temp_pool[i] = clone_obj; clone_obj.init (data_id); } 


The second function is responsible for activating the first free object in the scene. This is the state of the inUse variable. If discarded, the clone is free. After calling the spawn () function, the object is activated.

 //--------------------------------------------------- //    //pool_name -   //coord -     exports.spawn = function(pool_name,coord) { var pool = _pools [pool_name]; var size = _pools_size [pool_name]; if(!pool[size - 1].inUse) { pool[size - 1].spawn(coord); pool.unshift(pool.pop()); } //      for (var i = 0; i < size; i++) { if (!pool[i].inUse) { pool.push((pool.splice(i,1))[0]); } } }; 

What is the result


Traditionally, at the end of the lesson, I express my thoughts about working with Blend4Web. The framework is developing rapidly and what I found previously uncomfortable, much has been fixed in subsequent versions. It is gratifying when the developers of the engine listen to the opinions of users and quickly correct the bottlenecks. Of course, if it is really necessary.

You probably noticed that when I initialize the clones, I use not the copying, but the normal loading of objects with the load () command. The Blend4Web API has a copy () function. However, in this case, it is preferable to use the load. Instancing does not always work correctly with complex objects and does not take into account communications during cloning.

Note that uploading new resources takes time. Therefore, the initialization of objects is better to hide the inter-level screen. It is also desirable to pre-load the single object so that further ones are taken from the browser’s cache.

Total. The pool works, the shells fly out and hit the enemy ships, no brakes. All objects that are repeatedly used in the game are now called through the pool manager. At least this part of the code is already optimized.

Full listings ready to use with b4w.

“Pool Manager” /g_pool_manager.js

 "use strict" b4w.register("g_pool_manager", function(exports, require) { // import modules used by the app var m_app = require("app"); var m_cfg = require("config"); var m_data = require("data"); // automatically detect assets path var APP_ASSETS_PATH = m_cfg.get_std_assets_path() + "Danger Space/"; //    var _pools = {}; var _pools_size = {}; //--------------------------------------------------- //       //name -  //size -  //obj -   exports.create = function(name, size, obj) { var temp_pool = new Array(); _pools[name]=temp_pool; _pools_size [name] = size; for (var i = 0; i < size; i++) { var clone_obj = new obj(); var data_id = m_data.load(APP_ASSETS_PATH + clone_obj.fileName,null,null,false,true); temp_pool[i] = clone_obj; clone_obj.init (data_id); } //--------------------------------------------------- //    //pool_name -   //coord -     exports.spawn = function(pool_name,coord) { var pool = _pools [pool_name]; var size = _pools_size [pool_name]; if(!pool[size - 1].inUse) { pool[size - 1].spawn(coord); pool.unshift(pool.pop()); } //      for (var i = 0; i < size; i++) { if (!pool[i].inUse) { pool.push((pool.splice(i,1))[0]); } } }; //--------------------------------------------------- //    //pool_name -   //obj -  exports.despawn = function(pool_name, obj) { }; } }); 


“Object” /g_bullet.js

 "use strict" b4w.register("g_bullet", function(exports, require) { // import modules used by the app var m_cfg = require("config"); var m_data = require("data"); var m_ctl = b4w.require("controls"); var m_trans = b4w.require("transform"); var m_scene = b4w.require("scenes"); var m_vec3 = require("vec3"); var m_obj = b4w.require("objects"); var m_poolmanager = b4w.require("g_pool_manager"); var m_phy = require("physics"); var m_vars = require("g_vars"); //------------------------------------------------------------------------------ exports.Bullet = function() { this.inUse = false; this.fileName = "bullet1.json"; this.name = "bullet1"; this.id; this.init = function(data_id) { this.id = data_id; }; this.object = function(data_id) { return m_scene.get_object_by_name(this.name,this.id); }; this.spawn = function (coord) { this.inUse = true; var obj = m_scene.get_object_by_name(this.name,this.id); m_trans.set_translation_v(obj, coord); m_scene.show_object(obj); console.log("Spawn!"); //      (2 ) var timer_bulet1 = m_ctl.create_timer_sensor(2,false); m_ctl.create_sensor_manifold(this, "TIMER_B", m_ctl.CT_SHOT, [timer_bulet1], null, timerB_cb); } //       this.despawn = function() { this.inUse = false; var obj = m_scene.get_object_by_name(this.name,this.id); m_scene.hide_object(obj); console.log("Despawn!"); }; } function timerB_cb (obj, id) { obj.despawn(); } }); 


Usage example

 "use strict" b4w.register("g_player", function(exports, require) { // import modules used by the app var m_trans = b4w.require("transform"); var m_poolmanager = b4w.require("g_pool_manager"); var m_bullet1 = b4w.require("g_bullet"); exports.init = function() { //     "Bullet1", 10  m_bullet.Bullet m_poolmanager.create ("Bullet1", 10, m_bullet.Bullet); //     var gun1_coord = m_trans.get_translation(_gun1); m_poolmanager.spawn ("Bullet1",gun1_coord); //     "Bullet2", 20  m_bullet.Bullet m_poolmanager.create ("Bullet2", 20, m_bullet.Bullet); //     var gun1_coord = m_trans.get_translation(_gun1); m_poolmanager.spawn ("Bullet1",gun1_coord); m_poolmanager.spawn ("Bullet1",gun1_coord); m_poolmanager.spawn ("Bullet1",gun1_coord); } } }); 

Source: https://habr.com/ru/post/275307/


All Articles