JASS: Method Interfaces: The Basics & Type IDs

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
0. Index
  1. Foreword
  2. General Agreements
  3. The Interface
    • Definition
    • Example of use: Making a marble roll
    • Set up
  4. The Method Interface
    • Definition
    • Example of use: Expanding the marble
    • Set up
  5. Type IDs
    • Definition
    • Example of use: A bag of marbles
    • Set up
  6. Advanced Cases


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:
JASS:
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

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:

JASS:
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:
JASS:
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

So our previous example would still be valid written like this:
JASS:
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
JASS:
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:
JASS:
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

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
JASS:
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:
Code:
INTERFACE WITH DEFAULT VARS
                |
                |
                V
STRUCT WITH DEFAULT METHODS
                |
                |
                V
SEVERAL STRUCTS

You could have:

Code:
                    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 :)
 

Flare

Stops copies me!
Reaction score
662
Nice tutorial :D Always wondered what methods were and how to use them.

For the method interface, the names are kinda confusing me. You declare the interface, a 'parent' struct which is an extension of the interface, then 'child' structs which are extensions of the 'parent' struct? Then, you can call any methods that were declared in the interface/parent struct using one of the child structs?

And with that typeID example you have, that's allowing you to create a random struct-type that is, in some way, an extension of Marble? Wouldn't it be able to create a MarbleMethods instead of a Red/Green/BlueMarble, or does it exclude 'paren't structs/direct extensions on the interface?
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
Then, you can call any methods that were declared in the interface/parent struct using one of the child structs?

Indeed.
However, make sure the methods are defaulted in the "parent struct" (what I like to call Method interface). Since you can't default methods in an interface, only declare them.

Variables can be both declared and defaulted in both interface and "method interface".
But to be safe, ALWAYS declare them in the interface, since then you allow them to be used outside the whole structure as well.
(Like I did with RollHeaviest)

If you declare them in the Method interface, only the attached structs (Childs) can call for it.
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
That would be overkill, since the "method" isn't specific to one struct.
You'd call this:

JASS:
local RedMarble r = RedMarble.create()
local BlueMarble b = Bluemarble.create()
call RedMarble.RollHeaviest(r,b)


While with a function, all you'd need to do is this:
JASS:
local RedMarble r = RedMarble.create()
local BlueMarble b = Bluemarble.create()
call RollHeaviest(r,b)


In this scenario, there's no need for a method, since it needs 2 different instances of a Marble object anyhow.
(But like the intro says: You need at least some knowledge of methods to understand this tutorial...)

Both scenarios would work, but cramming your Method Interface (parent) up with methods
that may just as well be functions, will cause severe damage to the readability of your code.
 

Flare

Stops copies me!
Reaction score
662
So, the best option is to have members in the interface as well, so you can use them in other functions?

(But like the intro says: You need at least some knowledge of methods to understand this tutorial...)

I have very basic knowledge of methods (create method, onDestroy method) :p Never actually used a method that wasn't either of those :D
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
So, the best option is to have members in the interface as well, so you can use them in other functions?

Exactly, you don't do any severe damage by putting all your vars in the Interface and the effect stays the same as if you'd put them in the Method Interface (parent).
However, putting them in the interface has the benefit of global usability, while the Method Interface doesn't give you this perk.

So why limit yourself by putting it in the Method Interface? :)
Just declare & default all your vars in the interface.
 

Hatebreeder

So many apples
Reaction score
381
Hmmmm... I don't quite understand... Is an Interface somthing like a struct, that holds structs? xD
a few more details please =)

BTW:
JASS:
    interface Marble        
        real weight     = 0.
        string color    = &quot;0000FF&quot;
       
        method roll takes nothing returns nothing
    endinterface

is there supposed to be an endmethod? :S
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
It allows you to use a group of structs and their methods under the same interface name.
JASS:
struct A
endstruct

struct B
endstruct

function DoSomething takes ??? /*Oh shit, it must be able to take either A or B*/ returns nothing
endfunction


-->

JASS:
interface X
endinterface

struct A extends X
endstruct

struct B extends X
endstruct

function DoSomething takes X returns nothing
    // This func can be given both an A as a B as its argument now
endfunction


is there supposed to be an endmethod
Not when declaring it in the interface.
(Which is why you need to use Method Interface (or parent struct...) to actually default the methods.)
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
It is useful for:
- making random stuff like my marble example.
- making AI based on timers/events
- making physics engines
- making complicated spells that use more than one particle

But most importantly:
- sharing a lot of methods/variables with a bunch of structs and being able to call them without knowing what type of struct you're calling it from
 

Trollvottel

never aging title
Reaction score
262
though i knew about this stuff, i think its a very good tutorial and will be helpful for many users :thup:
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
Post your code and the error.

Make sure you do the INTERFACENAME.create(CHILDSTRUCTNAME.typeid)
 
Reaction score
456
I am talking about your code in the Type ID chapter.

Code:
Undeclared variable marbleMax
Undeclared function sc__Marble__factory
Cannot convert null to integer
Undeclared function sc__Marble__factory
Cannot convert null to integer
Undeclared function sc__Marble__factory
Cannot convert null to integer
Undeclared function sc__Marbles___RedMarble__allocate
Cannot convert null to integer
Undeclared function sc__Marbles___BlueMarble__allocate
Cannot convert null to integer
Undeclared function sc__Marbles___GreenMarble__allocate
Cannot convert null to integer
Undeclared variable marbleMax
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
Ah yes, that's what you get when you type a tutorial in Notepad :)

Fixed:

- Assigned structs directly instead of within a constructor
-> the syntax wasn't incorrect before, but by not assigning the marbles inside a constructor, the functions didn't get placed in the right order.

- Adding a fix of declarations: You can redeclare variables from Interface->MethodInterface, but not from MethodInterface->Struct
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
Double post for emphasis:

Section 4 now contains a warning (the note) concerning the redeclaration of variables in a child struct.
 

Viikuna

No Marlo no game.
Reaction score
265
Nice. When I started to use vJass I read about interfaces from manual, but I never really used them. Now I know how usefull they really are. :thup:
 

Flare

Stops copies me!
Reaction score
662
Would this be correct?

JASS:
interface Thing
method Kill takes nothing returns nothing
endinterface

struct Parent extends Thing

method Kill takes Thing data returns nothing
call KillUnit (data.u)
endmethod

endstruct

struct ExtA extends Parent
unit u
endstruct

function Something takes nothing returns nothing
local ExtA data = ExtA.create ()
set u = GetTriggerUnit ()
call data.Kill ()
endfunction


It is useful for:
- making random stuff like my marble example.
- making AI based on timers/events
- making physics engines
- making complicated spells that use more than one particle

So you could have something like (knowing me, it's probably horribly wrong ^^)
JASS:
interface Objects
real vel = 0.
method ChangeVelocity takes nothing returns nothing
endinterface

struct Parent extends Objects

method ChangeVelocity takes Obects data, real newVel returns nothing
set data.vel = newVel
endmethod

endstruct

struct Sphere extends Parent
unit object
real vel
endstruct

function AlterVel takes Objects data returns nothing
call data.ChangeVelocity (data, somerandomvalue)
endfunction
 

Magentix

if (OP.statement == false) postCount++;
Reaction score
107
Would this be correct?

JASS:
interface Thing
method Kill takes nothing returns nothing
endinterface

struct Parent extends Thing

method Kill takes Thing data returns nothing
call KillUnit (data.u)
endmethod

endstruct

struct ExtA extends Parent
unit u
endstruct

function Something takes nothing returns nothing
local ExtA data = ExtA.create ()
set u = GetTriggerUnit ()
call data.Kill ()
endfunction

No, you have several flaws.
Either you call data.Kill(data), which is way inefficient.
Or you could rewrite it like this:

JASS:
interface Thing
    method Kill takes nothing returns nothing
endinterface

struct Parent extends Thing
    method Kill takes nothing returns nothing
        call KillUnit (.u)
    endmethod
endstruct

struct ExtA extends Parent
    unit u
endstruct

function Something takes nothing returns nothing
    local ExtA data = ExtA.create()
    set data.u = GetTriggerUnit ()
    call data.Kill()
endfunction




So you could have something like (knowing me, it's probably horribly wrong ^^)
JASS:
interface Objects
real vel = 0.
method ChangeVelocity takes nothing returns nothing
endinterface

struct Parent extends Objects

method ChangeVelocity takes Obects data, real newVel returns nothing
set data.vel = newVel
endmethod

endstruct

struct Sphere extends Parent
unit object
real vel
endstruct

function AlterVel takes Objects data returns nothing
call data.ChangeVelocity (data, somerandomvalue)
endfunction

Again, inefficient:
JASS:
interface Objects
    real vel = 0.
    method ChangeVelocity takes nothing returns nothing
endinterface

struct Parent extends Objects
    method ChangeVelocity takes real newVel returns nothing
        set .vel = newVel
    endmethod
endstruct

// Left out vel here since you didn&#039;t default it.
// This way it just takes vel = 0. from Objects
//
// Remember: Should you ever declare+default vel in Parent,
// then you may no longer declare it in here.
//
// Defaulting it in here has to be done through use
// of a constructor then
struct Sphere extends Parent
    unit object
endstruct

function AlterVel takes Objects data returns nothing
    call data.ChangeVelocity(somerandomvalue)
endfunction


The object you keep asking as an argument can be called "just like that".
Since you're accessing the method within the struct, it knows what variables to use when you refer to it as .vel or this.vel
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top