Sunday 20 November 2016

Interfaces vs Concrete Wrapper Classes

Software development can often be very rewarding, with interesting problems to solve and little pockets of endorphin releases as a reward.  It can also be a pretty tedious drudgery of box ticking with little to no fanfare.  The last few weeks have mostly been the latter.

Previously, when working on game scripts, a developer would be using C# interfaces defined within a standard interface library (XAGE.Interface.dll), which is how the scripts are de-coupled from the engine runtime.  The .NET naming convention for interfaces is to use an 'I' prefix e.g. ICharacter.

However, it's not possible to define a static method or property as part of an interface (which, when you think about it, wouldn't make sense anyway), so these were instead implemented as part of a second class e.g. Character.  This leads to some confusing and inconsistent typing in game scripts, e.g.:

ICharacter myCharacter = Character.GetAtScreenXY(mouse.x, mouse.y);

To work around this, I've created a concrete wrapper class for each interface:

// Instance of interface injected into concrete wrapper class
private ICharacter engineCharacter;

// Properties
public int x
{
    get
    {
        return engineCharacter.x;
    }
    set
    {
        engineCharacter.x = value;
    }
}

// Methods
public void SayAt(int x, int y, int width, string message)
{
    engineCharacter.SayAt(x, y, width, message);
}

Creating these wrapper classes has not been fun, even though it was partially automated, as there were of hundreds and methods and properties to wrap.  It does however have the following benefits:

  • The developer only needs to know about the single concrete class instead of the interface.  The newly styled documentation is also more straightforward as a result.  This will be fleshed out in future with examples and annotations.
  • Any discrepancies between the interface class and AGS scripts can be hidden as part of the wrapper class.  The main one being how AGS primarily uses integer IDs compared to XAGE's string IDs.
  • Automatic AGS conversions now no longer need clumsy string swaps for certain types.

There are some downsides however.  There is a tiny performance overhead in some instances, and some additional complexity when maintaining lists of objects in both the script and the engine.  These are outweighed by the benefits however.

So the last few weeks of spare evenings have been pretty dull, much like this blog post, but it's another small step in the right direction.  Next up:  Re-wiring and profiling.