Pooling with init : let’s throw ImpactJs classes in the pool.

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 :

    // always take the latest version from gitHub, do not copy paste this code.

    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 (!initialPoolSize || !((+initialPoolSize) !== initialPoolSize )) throw('setupPool takes a size > 0 as argument.'); 
    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(); 
            }                              
};

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 :

 MonkeyEntity.setupInitPool(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();
EntityMonkey.setupInitPool (20); 
EntityBadGorilla.setupInitPool( 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/second with no Pooling, and 0.04 GC/second 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.

Memory usage when not pooling / pooling.

Memory usage when not pooling / pooling.

Let me know if you happen to use all this in your game (and are happy with it !).

Happy coding.

Advertisements
This entry was posted in Uncategorized and tagged , , , . Bookmark the permalink.

11 Responses to Pooling with init : let’s throw ImpactJs classes in the pool.

  1. Pingback: Tomi Korkalainen | Star Boy: Progressing slowly

  2. Dave Voyles says:

    I’m still slightly confused by this.

    Would you be able to illustrate an example of how you would use this with impact? I mean, I see in your github that you have the template for it, through ga.pooling, but could you show this with an actual example? ie – using a bullet or particle?

  3. vinceblue says:

    Hi, like i wrote, if you are willing to pool some Entities, then you just have to ensure 1) init-must deal with no argument and initialize all properties used in the object’s life.
    After that, you can handle your entities with pnew or pdispose …
    … OR **if** you only use spawnEntity and kill on your entities (and not new), just enable the auto-pooling of entities for your game, then setup the pool for each entity you want a pool for

    This can be done in the main.js for instance :

    ga.autoPoolEntities(); // now spawnEntity and kill will use an entity’s pool if available.

    EntityBullet.setupInitPool(50); // pool bullets. 50 bullets expected at the same time

    EntityEnemyBullet.setupInitPool( 200); // pool enemy bullets. 200 enemy bullets expected

    EntityMissile.setupInitPool( 20); // pool missile

    and you have nothing more to do : when spawned the pooled entities will be taken from their pool, and when killed they will be thrown back on their pool.

  4. Dave Voyles says:

    “* you only use spawnEntity and kill on your entities (and not new), just enable the auto-pooling of entities for your game, then setup the pool for each entity you want a pool for”

    Yes, I take this route. That clarifies things a bit for me right there.

    I see how those ‘ga’ functions are used now. I wasn’t sure if they belonged in the entity class for that specific entity, or in main, but now it makes more sense.

    Now all I need to do is include this ‘impact.gaPooling’ in main.js, and then add the functions listed above, correct?

    From there, spawn will pull from the pool, and kill will push it back into the pool, rather than remove the entity, correct?

    I’ll let you know how it goes!

  5. You scare me a bit : was my article so confusing ??? 🙂

    Anyway, yes, inject the pooling capability for entity into Impact by using ga.autoPoolEntities();
    Then setup a pool for the entity class you want pooled by calling ga.setupInitPool on them.

    I wondered, when writing the lib, if i just shouldn’t have had auto-pooling enabled for entities by default, and all entities pooled… and still wondering… But 1) for some the undying / rarely dying entities it makes no sense and 2) performances will be more steady if you setup each pool with the right size before the game starts…

    And yes, i am interested to see if you can feel a difference. It will obviously depend on the spawning rate, but since your shoot-them-up game is quite frantic, it might show, especially in Firefox which is not that good with memory handling.
    Look at the average fps, but also the rate of of the hangs the game suffers from.

  6. Dave Voyles says:

    I’m getting this error:

    “Uncaught TypeError: Object # has no method ‘setupInitPool’ ”

    This is from my main.js class:

    .requires(
    ….
    ‘impact.gaPooling’,
    ….
    )
    init: function () {
    ….
    ga.autoPoolEntities();
    ga.setupInitPool(EntityObjectParticles, 120);
    ga.setupInitPool(EntityObjectBullet, 120);
    ….
    },

    Is there something I need to put within my particles or bullet class?

  7. Sorry i mistaken for the signature : it is EntityObjectBullet. setupInitPool(120).
    I edited my comments and the article.

    • Dave Voyles says:

      Alright, think I’m almost there!

      So for EntityObjectBullet, do I need to make a reference to it at all?

      For Example:

      this.entityBullet = this.getEntitiesByType(EntityObjectBullet?);

      and then I can call this.EntityObjectBullet.setupInitPool(120), or does your pooling file automatically know what EntityObjectBullet is?

      Right now it looks like this for me:

      ga.autoPoolEntities();
      EntityObjectParticles.setupInitPool(EntityObjectParticles, 120);

      And throwing no errors, but if I try to throw a log in my gaPooling.js class, I’m not seeing any pooling:

      function pnew() {
      var pnewObj = null;
      if (this.poolSize !== 0) { // the pool contains objects : grab one
      this.poolSize–;
      pnewObj = this.pool[this.poolSize];
      this.pool[this.poolSize] = null;
      console.log(‘grabbing from pool’);
      } else {
      pnewObj = new this(); // the pool is empty : create new object
      }
      this.apply(pnewObj, arguments); // initialize object
      console.log(‘making new’);
      return pnewObj;
      }

  8. you got wrong with the syntax : it’s not
    EntityObjectParticles.setupInitPool(EntityObjectParticles, 120);
    but rather :
    EntityObjectParticles.setupInitPool(120);
    As told earlier, auto-pooling + setting up the pool on desired entity class + having init initializing every properties even with no args is all you have to do when you let Impact handle entities with spawnEntity and kill. All behaviours remain the same.

  9. Dave Voyles says:

    Ok I think I’m getting closer

    I got past one hurdle:

    When I try to use “EntityObjectParticles.setupInitPool( 120);” with only one argument,
    the class throws an error:

    if (arguments.length < 2) {
    throw ('setupPool takes two arguments');
    }

    so I had to comment that out.

    The particles were a bad example for me to use, because I have different kinds of them.
    Let's use PlayerBullet instead, because I'm not having to pass in any parameters for it.

    I'm looking at your Banana example, but I'm confused by it. I'm not sure of what it is.
    Is the class actually called BananaTracker?

    I'm spawning bullets now, but still not seeing them being pooled. Do you have the source code
    for the image you use above? That way I can see exactly how you're doing it.

  10. Pingback: A better way of object pooling with impact.js (HTML5) | Dave Voyles

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s