Engine port complete

The engine port is now fully complete (tagged as version 0.1.0 in the repository) and so the next stage of my Devember can commence.

The solution to the issues I was looking at yesterday ended up being simpler than I expected it to be (fresh day, fresh head and all that jazz) but still not quite as ideal as I would have liked. There may still be a better overall solution, but this one seems like a good compromise.

The technique that I was using in the JavaScript version does not carry across very well to TypeScript without some severe restrictions on the way you lay out your code, but at the same time it was also less than ideal because it kept a bunch of objects around that it didn’t need to.

Just as a refresher, the Entity class has a field named properties which is a simple table and which contains run time properties of that entity. The goal is to have subclasses of Entity also be able to extend the properties as appropriate. At the same time we want to allow for properties that have a default value if you don’t specify them directly, so that you only need to specify non-default values.

In JavaScript, my code looked somewhat like this:

nurdz.game.Entity = function (name, stage, x, y, width, height, properties, zOrder, debugColor)
{
    // Ensure default properties has a value
    this.defaultProperties = this.defaultProperties || {};

    // Copy any defaults that don't exist into our properties
    this.properties = nurdz.copyProperties (properties || {}, this.defaultProperties);

    // Call the super class constructor, then validate our properties.
    nurdz.game.Actor.call (this, name, stage, x, y, width, height, zOrder, debugColor);
    this.validateProperties ();
};

nurdz.sneak.ChronoEntity = function (name, stage, x, y, properties, zOrder, debugColor)
{
    // The size of tiles in the game, so that we can use it for our dimensions.
    var tSize = nurdz.game.TILE_SIZE;

    // Modify any default properties that might already be set to include a our own properties and defaults.
    this.defaultProperties = nurdz.copyProperties (this.defaultProperties || {}, {
        visible: true,
        facing:  "right"
    });

    // Invoke the superclass constructor
    nurdz.game.Entity.call (this, name, stage, x * tSize, y * tSize, tSize, tSize, properties || {}, zOrder,
                            debugColor);
};

So in ChronoEntity, we first override the value of this.defaultProperties and then invoke the super constructor, which will apply those defaults for us. Of course along the way we also take care to handle the situation where no properties have been provided.

If you implement the above in TypeScript, you run into problems if you style your code a certain way. For example, the following is not allowed:

class Entity
{
    protected defaultProperties : any;

    constructor (paramteer : number)
    {

    }
}

class ChronoEntity extends Entity
{
    private someField : number = 12;

    constructor ()
    {
        // This is not allowed here because someField is initialized where it is declared.
        // In this situation, the call to super() has to be the first thing in the constructor.
        this.defaultProperties = { something: "string" };
        super (14);
    }
}

Due to the way the TypeScript compiler generates the resulting JavaScript code, the above is not allowed and the call to super() has to be the first thing in the method. You can get around this by assigning the default value of someField to be 12 inside the constructor instead of inside of its declaration. However that imposes a code style on me, which is annoying and prone to errors.

In the end I decided that instead of being a property, defaultProperties should be a parameter to the Entity constructor (with a default empty value). This allows subclasses to still pass in defaults without code restrictions, which is nice. As an added benefit, this means that every instance of every entity doesn’t carry a reference to an object that contributed default attributes.

The big problem I was having was the type safety of TypeScript kicking me in the nads. Something such as the following does not work:

interface EntityProperties
{
    id? : string;
}

class Entity
{
    protected properties : EntityProperties;

    constructor (properties : EntityProperties, defaults: EntityProperties)
    {
        this.properties = properties;
    }
}

interface ChronoProperties extends EntityProperties
{
    visible? : boolean;
    facing? : string;
}

class ChronoEntity extends Entity
{
    constructor (properties : ChronoProperties)
    {
        super (properties, {
            visible : true,
            facing: "left"
        })
    }
}

The compiler spits out the following error:

test.ts(27,13): error TS2345: Argument of type '{ visible: boolean; facing: string; }' is not assignable to parameter of type 'EntityProperties'.
 Object literal may only specify known properties, and 'visible' does not exist in type 'EntityProperties'.

The compiler doesn’t know that this object is a valid EntityProperties instance, so it gets all up in your grill about it.

My big mistake was assuming that there was some voodoo that would allow me to tell the compiler that instead of just EntityProperties it should allow anything that extends that class as well, as you might in a Java Generics type situation. My thought process was to help the compiler infer the type since it should totally be allowed to use a subclass in that case.

After some more research it turned out that in fact I was being a giant tool and in fact the error message is more rightly trying to tell you that it doesn’t know what the hell that object is at all. As such a simple typecast tells the compiler what the type is, e.g.:

class ChronoEntity extends Entity
{
    constructor (properties : ChronoProperties)
    {
        // Typecast the second parameter so the compilers knows what it is.
        super (properties, <ChronoProperties> {
            visible : true,
            facing: "left"
        })
    }
}

So, egg on my face on that one. Even after all of these years a developer (or maybe because of that?) it’s still easy to over think a problem.

The only “problem” with the above solution is that when you attempt to access this.properties in ChronoEntity the compiler gets mad at you when you try to use one of the extended properties, because as far as it knows, the type is EntityProperties. It turns out that you can resolve this situation by just re-declaring the variable with the new type, and the compiler is happy about it.

class Entity
{
    protected properties : EntityProperties;
}

class ChronoEntity extends Entity
{
    protected properties : ChronoProperties;
}

Ironically, I surprised myself by just doing that by reflex and it worked, requiring me to do even more research to make sure that it was supposed to work that way. Fortunately, it is, although it only works with protected properties, which thankfully I was already using.

This still forces a style on you by forcing you to redeclare the field as a new type before it’s used; however I think that’s actually a boon in this case as it sort of provides a local documentation about the field and its type.

In any case, with all issues resolved and the port complete, it’s now time to forge ahead with seeing how it is to actually work with the code instead of trying to port it. At this point I’m a full week ahead of the schedule I set for myself.