Hello,
I explained a few times ago a quite simple way of recycling the objects created with true Javscript classes. (here).
Still there are other classes scheme, and impact -and probably other framework as well-, uses a variant of John Resig’s inheritance scheme (here).
Thoses classes rely on an init() member, that will be in charge of … -you guessed- initializing the object.
I’ll explain here this scheme, and then apply the Pooling strategy to those objects.
( You can find the pooling code here :
https://github.com/gamealchemist/Javascript-Pooling )
How does those classes using the init() pattern work ?
Impact classes are all inherited from a base class, ‘Class’, using extend.
ig.Entity = ig.Class.extend({
pos : {x: 0, y:0} , // property
id : 0 , // property
...
init : function (x,y,settings) {
this.pos.x = x;
...
},
update : function() {
this.pos.x += ....
this.pos.y += ...
},
id: 0,
settings: {},
offset: {x: 0, y: 0}
} );
Extend must be provided with an object, which will be used to add properties and methods to the original Class.
All the properties and methods will be set on the protoype of the new Class (which is, in fact, a function as you know).
Then, when you create an object using new (), it will be the constructor’s responsability to copy the properties of the prototype into the new object. This copy is required because otherwise, if we take the Entity example, all entities would share the same pos / velocity / … which would probably lead to a boring game.
It is important to notice that all objects do not get copied : look at copy() (Impact.js, line 125) and you’ll see that HTMLElement or ig.Class properties won’t be copied. So they are shared amongst all instances. That is why you cannot set the animations in the extend object : each Entity needs its own animation.
But how to create those per-instance objects ? You do not have any control other the constructor function (handled by the class system), and obviously the object provided to extend the Class doesn’t know anything about the ‘this’ of the objects you’ll later instanciate…
That’s when the init() comes into play : it is called at the end of the constructor, and allows you to perform an initialization on the new instance, for all properties that needs their own instance of an ig.Class ( ex : animation), or a specific new value (x,y).
How could we recycle such objects ?
Since -to state things in a simple way-, it is now init() which initialising just like the constructor function does it for true js classes. So, to pool those classes, we just have to call init instead of the constructor to have the object as clean as new when we retrieve it from the pool.
The constraints we had on the constructor function for true js classes to work with pooling now apply on the init() function :
- init should accept any number of arguments (including none).
- at the end of an init EVERY properties should be set either to its default value OR to a value provided in the arguments of init.
So if you plan on using pooling with a Class, you must do a quick review of all properties you are using in your code, an reset or set them to the provided values in the init().
init (x,y,settings) {
x = (!x) : x : 0;
y = (!y) : x : 0;
this.bananaCount = ( settings && settings.bananaCount ) ?
settings.bananaCount : 0;
this.eatenBananas = 0;
this.bananaRobbers = [];
this.bananaTracker = new BananaTracker(this.bananaCount);
}
Now you are sure that any call to init will set your object as shining as new.
But.
But…
Wait !
The goal of all this is to avoid garbage creation, and the init() above will create objects on each call : So any time you can, try to re-use the property objects of the previous life of your object as well :
init (x,y,settings) {
var noArgs = ( arguments.length == 0 );
x = (!x) : x : 0;
y = (!y) : x : 0;
this.bananaCount = ( settings && settings.bananaCount ) ?
settings.bananaCount : 0;
this.eatenBananas = 0;
if (this.bananaRobbers) {
this.bananaRobbers.length=0
} else {
this.bananaRobbers = [];
};
if (this.bananaTracker) {
// reset the bananaTracker in some way
} else {
this.bananaTracker = new bananaTracker(...);
}
BananaTracker.pnew(this.bananaCount);
}
Rq : there are too many cases for me to explain here, but remember that when allocating an object for the pool, init will be called with no arguments. Then later, it will be called with your arguments (and *maybe* this second call is also performed with no arguments), and then again and again init will be called on each reuse. So think well about the best way to allocate the properties that uses objects. Do most work while initializing the pool if possible, and try to reset the properties in a fast and clean way.
Rq : you might use pooled object in the properties of your pooled object. It might be pooled ig.Classes or pooled true js classes. Just watch out for their disposal.
Another thing to keep in mind when using a Framework : the Framework might change some properties for you, properties that you should reset in the init.
Expl : For the Entity class, calling kill() will make this._killed to true, and make the object collides with nothing. So you have to unkill the object and restore its default collision settings…
OR … inject the framework to have it doing this for you, which i explain later.
Show us some code !!
Ok let us see the code for pooling Impact Classes :
var Initpnew = function() {
var pnewObj = null ;
if (this.poolSize>0 ) {
// the pool contains objects -> grab one
this.poolSize-- ;
var pnewObj = this.pool[this.poolSize];
this.pool[this.poolSize] = null ; // **
} else {
// the pool is empty -> create new object
pnewObj = new this();
this.builtCount++;
}
if (pnewObj.init) {
pnewObj.init.apply(pnewObj, arguments); };
return pnewObj;
};
var Initpdispose = function() {
var thisCttr = this.constructor ;
// throw the object back in the pool
thisCttr.pool[thisCttr.poolSize++] = this ;
};
ga.setupInitPool = function(func, initialPoolSize) {
if (arguments.length<2) {
throw ('setupPool takes two arguments'); }
func.pool = [] ;
func.poolSize = 0 ;
func.pnew = Initpnew ;
func.builtCount = initialPoolSize ;
func.prototype.pdispose = Initpdispose ;
// pre-fill the pool.
for (var i=0; i<initialPoolSize; i++) {
var newObj = (new func());
newObj.pdispose();
}
};
Rq : We can see that, when the pool is empty, init is called twice on the newly created object : once for the object creation (with empty arguments), and then we initialize it with provided arguments. But… well.. this shouldn’t happen ! The builtCount property is here to measure how many objects were built : at the end of a game, look at this figure and the intialPoolsize should be >= to this number.
How to use ?
1. Ensure the init performs a full initialisation of *all*
your object’s property, even when called with no arguments.
2. pool the class :
ga.setupInitPool( MonkeyEntity, 100);
3. Instead of the new operator, use pnew :
MonkeyEntity.pnew(...)
4. don’t forget to pdispose() instances that you won’t use any more. :
aMonkeyInstance.pdispose()
!!! Do not keep any pointer to a disposed entity. !!!
Helper for Entity classes.
Impact can handle the creation and disposal for you : if you use the standard function spawnEntity and entity.kill(), you might just as well have them handle the pooling. And while we are here, we can also have spawnEntity to restore some default values for you : _killed and the collisions settings.
I wrote a small function that injects the pooling for the Entities, so to have pooled entities, just write (in main.js for instance, where you must ‘requires’ your entities and also gaPooling.js ) :
ga.autoPoolEntities(); ga.setupInitPool(EntityMonkey, 20); ga.setupInitPool(EntityBadGorilla, 50);
And that’s all !
Take care of setting YOUR properties in the inits, use only the standard spawnEntity and kill functions with them (not ‘new’), and your monkeys and gorillas will swim in your pool.
Are you sure this can be usefull ?
Well, for, say, the hero entity used only once, i guess not. For monsters, it will depend on their number and spawning rate. For bullets, it should pretty much always useful.
You can see in my other article on Pooling some performance counter : they measure in fact a mix of the creation time and garbage collection time.
Since Javascript does not allow to know the memory use or when it performs a garbage collection, it is difficult to be accurate.
But i had this other idea : what about looking at the memory usage (in Chrome / Timeline), and measure the Garbage Collection rate by hand ? After all we know a G.C. occured when memory usage suddenly drops.
So i made a quick demo throwing quite a lot of bullets (100/s) and measured ‘by hand’ the number of G.C. performed.
As you can see, there are 0.26 GC/s with no Pooling, and 0.04 GC/s with pooling, so we have a X6 improvement. !!
We can see also that the pooled memory usage stays 2/3 MB or so below the non-pooled usage.
Let me know if you happen to use all this in your game (and are happy with it !).
Happy coding.


