Impact

This forum is read only and just serves as an archive. If you have any questions, please post them on github.com/phoboslab/impact

1 decade ago by vincentpiel

Rq 1) There's a much 'cleaner' way of implementing staticInstantiate than the
one given in the documentation :
http://impactjs.com/documentation/class-reference/class#staticinstantiate-function

In the code i suggest below :
- no code needed after the class extension to initialize the _staticInstance member.
( MySingleton.instance = null;)
- the _staticInstance member is not enumerable, so it does not 'pollute' the object.
(by default, Object.defineProperty set the property as non enumerable).
- But most important : the init() is not affected by the class being static or not.

var MySingleton= ig.Class.extend({
    someProperty: 'someValue',
    staticInstantiate: function ()  {
              if (MySingleton._staticInstance) return MySingleton._staticInstance;
              this.init.apply(this,arguments);
              Object.defineProperty(MySingleton,'_staticInstance', { value : this});
              return this;
              },    
    init: function( newValue ) {
              this.someProperty = newValue;  // standard init code
              }
});

Rq 1.1 : this code requires Object.defineProperty to work, but any browser
implementing Canvas does also implement defineProperty.
Rq 1.2 : for those who don't care about 'hiding' the property, you can use
this simpler code :

    staticInstantiate: function ()  {
              if (MySingleton._staticInstance) return MySingleton._staticInstance;
              this.init.apply(this,arguments);
              return   (MySingleton._staticInstance = this);
              },    

Rq 2) This staticInstantiate function is now be the same for all classes, so it
should stand inside ig.Class, and we'd better use another convention for static
classes. The clearest convention i can think of is : static object should
make use of a staticInit() that replaces the init().
So 1) the coder/code reader does not get fooled by an init() function that will
in fact only get executed once,
and 2) the object is not 'polluted' with a property (staticInstantiate) that in fact is a meta-property.

This would give this code, for a static class :

var staticClass = ig.Class.extend
      ({
          prop1 : 0,
          staticInit (value1) {
                this.prop1 = value1;           }
      })

...cannot be simpler...

and when a class has both init() and staticInit() set, ig.Class.extend should throw an exception.

Backward compatibility is easy to have. To write it short,
if (this.staticInstantiate) / consider that init is staticInit /

So it would break only the dirty tricks done in staticInstantiate, like the
one done in ig.Game :

	staticInstantiate: function() {
		this.sortBy = this.sortBy || ig.Game.SORT.Z_INDEX;
		 ig.game = this;
	     return null;
	},

Notice that it always return null !!! and that creating a game changes
sorting options (?), and sets a global variable... nothing anyone could claim as
a good design that should be kept as is. (with all due respect to Impact) )

1 decade ago by dominic

Thanks for your writeup! Your code certainly is a cleaner way to implement singleton classes, however, it doesn&039;t solve the use-case I initially wanted to solve with #staticInstantiate.

Have a look at lib/impact/image.js, around line 17:
staticInstantiate: function( path ) {
	return ig.Image.cache[path] || null;
},

The idea is, that if I call the constructor for ig.Image twice with the same path, only one instance gets created behind the scenes. See the ig.Image docs. So staticInstantiate was intended to solves cases where we may or may not need to create a new instance of a class. It can be used to build singleton classes, but that was not its exclusive goal.

In hindsight, I probably just should&039;ve returned a new instance, but cache the actual HTMLImage (.data#) somewhere. So this will probably get cleaned up (or removed entirely?) for Impact 2.0, but is unlikely to change for 1.x.

And yes, the way staticInstantiate is used for the Game class is quite... odd, but it solved a problem a lot of people had (e.g. see this thread): in older Impact versions, ig.game would only be defined after the Game's init method was done.

The old code is still present in lib/impac/system.js, around line 70:
ig.game = new (gameClass)();

Without the staticInstatiate of the Game class, you had to take extra care not ever to refer to ig.game in your Game&039;s #init(), nor in any init() of your entities or any other method that relies on ig.game being defined and is called when the game is created.

The .sortBy property had a similar problem. I wanted to have a default for the .sortBy property, but couldn&039;t set when the class was defined, but only afterwards when #ig.Game.SORT was defined. I could&039;ve also done it with #ig.Game.prototype.sortBy = ig.Game.SORT.Z_INDEX, but we had the staticInstatiate already anyway...

So, again, the intention of staticInstantiate for the Game class was not to make it a singleton, but to solve other issues. It worked, but it's not a clean solution and I'm not proud of it :/

1 decade ago by amadeus

I should probably double check before posting, but I am being lazy here.

I believe you cannot use the arguments var with apply. I believe you must convert first to an array and then pass to apply:

I could be wrong though.

1 decade ago by amadeus

Nevermind, I think it's actually ok :) proceed.

1 decade ago by vincentpiel

well i will not be lazy, amadeus, ;-) and give you the technical references :
for apply :
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/apply
and arguments :
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments

to say that you're wrong (amadeus) : arguments is an array, so exactly what's needed for apply.


@Dominic : for the 'ig.game not set' bug this is the time to remember that the 'new' operator does not have same meaning as, for instance, in C++.
So in Javascript (ecmascript >1.85 but anyway Impact requires a Canvas...) we have :
var myNewObject = new AFunction(arg1, arg2);
is equivalent to :
var newObject = {} ;
var args=[]; args.push(arg1); args.push(arg2);
var myNewObject = AFunction.apply(newObject,args);

so in your setGameNow method you should make use of this to
1) first create an object
2) set ig.game = this object
3) apply the class function to this object with arguments as array.

and you will have the wanted behaviour ( :: ig.game is set during init) without the staticInstantiate hack.

tell me if i'm wrong/unclear (or even right ! :-) ).

1 decade ago by amadeus

Right, that's why I corrected myself.

However, to be super technical, arguments is not ACTUALLY an array, it's simply an array-like object.

You can easily get an array from it by doing:

var args = Array.prototype.slice.call(arguments);
Page 1 of 1
« first « previous next › last »