JASS: Method Interfaces: The Basics & Type IDs

Tutorial By Magentix

0. Index

Foreword
General Agreements
The Interface
[LIST=a]
• Definition
• Example of use: Making a marble roll
• Set up

The Method Interface
[LIST=a]
• Definition
• Example of use: Expanding the marble
• Set up
[/LIST]
Type IDs
[LIST=a]
• Definition
• Example of use: A bag of marbles
• Set up
[/LIST]
Advanced Cases
[/LIST]


1. Foreword
This tutorial is completely in JASS, meaning any GUI user can stop reading here.
Secondly, this tutorial requires you to have a basic knowledge of what structs and methods are.
If you don't know what they are, the advantage of using interfaces probably won't be clear to you anyway.
:)



2. General Agreements
Throughout this tutorial, we will continuously use the same example and expand it to suit our needs.
To keep everything clean and easy to look at, we will put everything in a library.



3. The Interface
a. Definition
A Basic Interface in vJASS is a structure that allows you to refer to multiple related objects as one object-type.
Difficult to understand at first, huh?
No problem, let's just move on to our example.

b. Example of use: Making a marble roll
Suppose we have a nice, round marble.
To describe the marble even more, we create a struct for it that describes the marble's mass, volume, color, etc...

Now, to let that marble roll, we have a function RollMarble that does all the rolling dynamics.

However, since every marble struct will have a different name, we can't write one function that will roll just any marble.
So as you probably already know, we'll have to create a "roll" method in every marble struct.
(The foreword did mention you need a basic knowledge of methods)

Example:
[SPOILER]
library Marbles

    private struct RedMarble
        string color    = "FF0000"
        
        method roll takes nothing returns nothing
        endmethod
    endstruct

    private struct GreenMarble
        string color    = "00FF00"

        method roll takes nothing returns nothing
        endmethod
    endstruct
    
endlibrary
[/SPOILER]

Now that we have our methods in every marble struct, we still have one problem:
How do we make different marbles "interact" in a function?

Suppose we want to take two marbles and only roll the heaviest?
This would be impossible since the function RollHeaviest wouldn't know what to require as arguments.
(All marble structs have different names, remember?)

This is where interfaces come in.

c. Set up
As I mentioned above, we need a function to take any two types of marble, and roll only the heaviest.
We can achieve this by giving ALL marbles the same reference name through an interface:

library Marbles

    interface Marble
        // This is how you declare an obliged struct member
        // for all structs extending Marble
        real weight
        
        // This is how you declare an obliged struct method
        // for all structs extending Marble
        method roll takes nothing returns nothing
    endinterface

    private struct RedMarble extends Marble
        string color    = "FF0000"
        real weight     = 0.
        
        method roll takes nothing returns nothing
        endmethod
    endstruct

    private struct GreenMarble extends Marble
        string color    = "00FF00"
        real weight     = 0.

        method roll takes nothing returns nothing
        endmethod
    endstruct
    
    function RollHeaviest takes Marble A, Marble B returns nothing
        if A.weight > B.weight then
            call A.roll()
        else
            call B.roll()
        endif
    endfunction
    
endlibrary

Notice how I used extends Marble on all of our marble structs?
This tells your vJASS compiler (JassHelper) that they can be referred to as "Marble".

However, every method that you use of a Marble inside an external function, must be set in the interface declaration.

In this case you notice that the function RollHeaviest uses the .roll() method and the .weight struct member, but it does not use .color in any way.
Well then, we only have to declare the method "roll" and the real "weight" in the interface, but we're not obliged to declare "color",
since no method or function outside of a specific Marble uses it.

One more thing:
Once you declare a method inside an interface, anything that extends that interface must have it as well!
So not having the method "roll", would make a struct extending Marble cause a syntax error.
Variables, however, can be defaulted inside the interface and any struct that then extends that interface,
will automaticly have that variable declared and defaulted, but may still overwrite it with its own default.

Example:
[SPOILER]
library Marbles

    interface Marble
        // Defaults weight to 10.
        // Any struct that extends Marble will automaticly
        // have "weight" declared and defaulted to 10.
        real weight     = 10.
        
        method roll takes nothing returns nothing
    endinterface

    // This will NOT cause a syntax error, since BlueMarble
    // does have all the required methods, and the interface
    // Marble defaults the "weight" variable for BlueMarble.
    private struct BlueMarble extends Marble
        string color    = "0000FF"

        method roll takes nothing returns nothing
        endmethod
    endstruct

    // This will cause a syntax error, since Redmarble
    // does not have the method roll, while the interface
    // it extends (Marble) does.
    private struct RedMarble extends Marble
        string color    = "FF0000"
    endstruct

    // Redeclaring weight is allowed and causes all
    // instances of GreenMarble to have 20. as their
    // weight default, instead of the interface defaulted 10.
    private struct GreenMarble extends Marble
        string color    = "00FF00"
        real weight     = 20.

        method roll takes nothing returns nothing
        endmethod
    endstruct
    
endlibrary
[/SPOILER]

So our previous example would still be valid written like this:
library Marbles

    interface Marble
        // Declared and defaulted to 0. for all extending structs
        real weight = 0.

        method roll takes nothing returns nothing
    endinterface

    private struct RedMarble extends Marble
        string color    = "FF0000"
        
        method roll takes nothing returns nothing
        endmethod
    endstruct

    private struct GreenMarble extends Marble
        string color    = "00FF00"

        method roll takes nothing returns nothing
        endmethod
    endstruct
    
    function RollHeaviest takes Marble A, Marble B returns nothing
        if A.weight > B.weight then
            call A.roll()
        else
            call B.roll()
        endif
    endfunction
    
endlibrary




4. The Method Interface
a. Definition
A Method Interface is how I like to call a combination of a Basic Interface with a "parent" struct.

b. Example of use: Expanding the marble
While explaining how and why we should use an interface, I used 2 marbles to show what I meant.
Now suppose we have a few hundred marbles.

All round marbles may have the exact way of rolling (in a straight line for example), but still be different structs because they have a different color, mass, etc.

Having to copy-paste the same "roll" method in every marble of that type, along with all the details would suck, wouldn't it?

This is where Method Interfaces come in handy.

c. Set up
library Marbles

    interface Marble        
        real weight     = 10.
        string color     = "000000"
       
        method getColor takes nothing returns nothing
        method roll takes nothing returns nothing
    endinterface

    private struct MarbleMethods extends Marble
        // Notice you may default both variables
        // and methods inside a method interface.
        // Here, color overwrites the color defaulted
        // by the interface
        
        string color    = "FFFFFF"
        
        // You may call this function on anything that
        // extends MarbleMethods and it will still call
        // the correct color, or "FFFFFF" if the extending
        // struct didn\'t specify any color.
        method getColor takes nothing returns nothing
            call BJDebugMsg("Hi, my color is: " + .color)
        endmethod
        
        method roll takes nothing returns nothing
            // Some normal rolling stuff
        endmethod
    endstruct

    private struct RedMarble extends MarbleMethods     
        real weight     = 20.
        
        // This constructor will set the default color
        // for any RedMarble. If you don\'t understand this:
        // Read the note at the end of this section!
        static method create takes nothing returns RedMarble
            local RedMarble rm = RedMarble.allocate()
            set rm.color       = "FF0000"
            return rm
        endmethod
        
        // Notice I no longer need to set "roll" here,
        // since MarbleMethods created a default for RedMarble.
        // You may, however, overwrite "roll" like
        // SquareWhiteMarble does.
    endstruct

    private struct SquareWhiteMarble extends MarbleMethods
        // Done\'t need to set color nor weight,
        // since I take the default color of MarbleMethods
        // and the default weight of Marble.

        method roll takes nothing returns nothing
            // Some square rolling stuff
        endmethod
    endstruct
    
endlibrary


Wow, that's a lot of information!
Let's break it down:

As you can see here, RedMarble no longer extends Marble directly, but uses a small detour over MarbleMethods.
This way, you can default some methods that hardly ever change inside MarbleMethods and still overwrite them if necessary inside a marble struct.
(Just like you can set/overwrite variables in the interface)

Note:
Unlike a struct that is a direct extension of an interface, you may not redeclare variables in a child struct of a Method Interface.
You may still use it and set its value to something else in a constructor, but redeclaring it will cause a syntax error.

Example:
[SPOILER]
library Marbles

    interface Marble
        real weight
       
        method roll takes nothing returns nothing
    endinterface

    struct MarbleMethods extends Marble
        // weight got declared in MarbleMethods!
        // This means Redmarble and GreenMarble can still
        // "set" their value, but they may not redeclare it.
        real weight     = 0.
        
        method roll takes nothing returns nothing
        endmethod
    endstruct

    // This is a legit way, since it doesn\'t re-declare any
    // variables from MarbleMethods.
    private struct RedMarble extends MarbleMethods
        string color    = "FF0000"
    endstruct

    // This will cause syntax errors, since "weight"
    // already got declared in MarbleMethods.
    private struct GreenMarble extends MarbleMethods
        real weight     = 10.
        string color    = "00FF00"
    endstruct
    
    // This will NOT cause syntax errors, since
    // we don\'t redeclare weight, we just change its
    // value upon construction
    private struct BlueMarble extends MarbleMethods
        string color    = "0000FF"
        
        // This constructor will set the default weight for any BlueMarble
        static method create takes nothing returns BlueMarble
            local BlueMarble bm = BlueMarble.allocate()
            set bm.weight       = 100.
            return bm
        endmethod
    endstruct
    
endlibrary
[/SPOILER]

To avoid any incidents like this, it is best to always set your child struct's default variables in a constructor method.



5. Type IDs
a. Definition
TypeIDs are the pointers that tell vJASS where to store and find its data for a specific struct type.

b. Example of use: A bag of marbles
So far we learned how to create a standard for objects that allows us to both declare and default variables and methods for all the structs that extend it.
Now we will learn how to add a last, special element: randomness.

Suppose we have a collection of marbles. Let's say a bag of marbles.
Now, having the same marbles over and over again in a specific bag would be boring, wouldn't it?

Well, we are allowed to fill a bag with random marbles now, thanks to TypeIDs!

c. Set up
library Marbles initializer Init

    globals
        integer array marbleIDs
        integer marbleMax    = 0
    endglobals

    interface Marble        
        real weight     = 0.
        string color    = "0000FF"
       
        method roll takes nothing returns nothing
    endinterface

    struct MarbleMethods extends Marble
        method roll takes nothing returns nothing
        endmethod
    endstruct

    private struct RedMarble extends MarbleMethods
        string color    = "FF0000"
        // Some other stuff here
    endstruct

    private struct GreenMarble extends MarbleMethods
        string color    = "00FF00"
        // Some other stuff here
    endstruct
    
    private struct BlueMarble extends MarbleMethods
        // Some other stuff here
    endstruct
    
    
    private struct BoringBagOfMarbles
        Marble red
        Marble blue
        Marble green
        
        static method create takes nothing returns BoringBagOfMarbles
            local BoringBagOfMarbles bag = BoringBagOfMarbles.allocate()
            
            set bag.red     = RedMarble.create()
            set bag.blue    = BlueMarble.create()
            set bag.green   = GreenMarble.create()
            
            return bag
        endmethod
    endstruct
    
    private struct AmazingBagOfMarbles
        Marble random1
        Marble random2
        Marble random3
        
        static method create takes nothing returns AmazingBagOfMarbles
            local AmazingBagOfMarbles bag = AmazingBagOfMarbles.allocate()
            
            set bag.random1 = Marble.create(marbleIDs[GetRandomInt(0,marbleMax)])
            set bag.random2 = Marble.create(marbleIDs[GetRandomInt(0,marbleMax)])
            set bag.random3 = Marble.create(marbleIDs[GetRandomInt(0,marbleMax)])
            
            return bag
        endmethod
    endstruct
    
    
    // Initializer
    private function Init takes nothing returns nothing
        set marbleIDs[0] = RedMarble.typeid
        set marbleIDs[1] = GreenMarble.typeid
        set marbleIDs[2] = BlueMarble.typeid
        set marbleMax    = 2
    endfunction
    
endlibrary


Wow, another bunch of code!

As you can see, we could create a boring bag of marbles, containing one green, blue and red marble.
Or we could go wild and grab 3 random marbles!

How we achieve this is simple:
We assign an extending struct's typeIDs to a variable by using:
set SomeInteger = STRUCTname.typeid

When we want to use this typeid to create a Marble, we just use:
set SomeStruct = INTERFACEname.create(STRUCTname.typeid)
or, in this case:
set SomeStruct = INTERFACEname.create(SomeInteger)

Well, in this case we have 3 marbles to random from.
So we create an integer array and assign all 3 Marble's typeID to the array.
Instead of just calling <Marble struct name>.create(), we can now call Marble.create(Random value from the TypeID array).



6. Advanced Cases
To finish off, I'd like to point out that you can let your imagination run wild with this "system".

For example, instead of having:
INTERFACE WITH DEFAULT VARS | | V STRUCT WITH DEFAULT METHODS | | V SEVERAL STRUCTS

You could have:

INTERFACE WITH DEFAULT VARS | | | | V V STRUCT A W/ DEF. MET. STRUCT B W/ DEF. MET. | | | | V V SEVERAL STRUCTS W/ MET. SET A SEVERAL STRUCTS W/ MET. SET B

Or, as a "real example":

The Interface Marble, extended by Method interfaces:
- RoundMarble (has the physics for working marbles)
- BrokenMarble (has the physics for broken marbles)

Which are in turn extended by their marble variants




There, I hope this was interesting to read and inspired some people to use interfaces for their next projects :)

Click here to comment on this tutorial.
 
 
Blizzard Entertainment, Inc.
Silkroad Online Forums
Team Griffonrawl Trains Muay Thai and MMA fighters in Ohio.
Apex Steel Pipe - Buys and sells Steel Pipe.