Tutorial The Struct Tutorial


You can change this now in User CP.
The Structs Tutorial​

This tutorial is directed to the ones who learned jass, except structs. This is your final step for mastering the Jass programming language.
A.1: What is a struct:​
A.2: Basic Struct Declaration:​
A.3: Uncomplicating memory issues:​
A.4: Struct's functions, methods I:​
A.5: Struct's Encapsulation:​
A.6: Static/Non-Static members:​
A.7: Struct's methods II:​
A.8: onDestroy/onInit methods:​
B.1: Moving from scopes to structs:​
B.2: Writing a whole spell inside a struct:​
B.3: Extensions:​
B.4: Interfaces:​
B.5: Modules:​

<A.1> What is a struct:

A struct is a data structure like a table (array/s) that can hold different types of data which a table can't do.

As you know a table is a serialized group of variables that holds contiguous locations in your memory, so if you have an array variable of name intVar the very first position is intVar[0] then the next is intVar[1] and so on.
In your memory (RAM most of the times) these variables positions are next to another.
A struct does not follow this architecture and you have to manually “create” memory locations to place your data inside it.

Knowing now the differences with the array variables and structs, it will be easier to understand them in depth during this tutorial.

<A.2> Basic Struct Declaration:

To declare a struct you have to follow this format:
struct (1.name)
{2.list of variables}

1: The name of the struct must follow the rules of naming a variable, no numbers as first letters and no special symbols except “_”.
2: You declare the variables you want as you do inside the global/endglobal keywords, these variables are called members of struct.
Let's see now a very basic example of a struct operation:

struct MyData //A proper way to write a struct name
    integer intVar //A proper way to write member names

function foo takes nothing returns nothing
    local MyData instance = MyData.create() *
    set instance.intVar = 5 **

*: As a variable is declared, the same way a struct is declared. The type of the variable is the struct’s name if you noticed. Then we create a location in our memory for usage and we apply it to our variable named instance. Now this variable can be used for anything you can do with it.

**: To access an instance's member you have to write (instanceName).(member). We set now the integer member with the value 5.
If we didn’t create a memory for usage the assign command (= 5) could not be executed.

<A.3> Uncomplicating memory issues:

scope Struct initializer OnInit //makes function OnInit run on map initialization
    struct MyData
        integer int
        real array float[50] //you have to declare how many arrays you need to ensure compiler 
    endstruct                //how much memory it will get for this variable on instance's creation

    function OnInit takes nothing returns nothing
        local MyData inst = MyData.create() //inst stands for instance
        local MyData inst2 //that's another instance with the same properties as inst

        set inst.int = 3
        set inst.float[0] = 3.14

        set inst2.float[49] = 1 //ERROR: Computer: “Hey ! Where is inst2 in memory ?”

        set inst2 = MyData.create() //Now we create the instance for usage

        call BJDebugMsg(I2S(inst2.float[49])) //It will show null instead of 1, because when you tried to set it 1 the variable didn't exist

        set inst2.float[49] = 2.27 //Computer: “That's o.k, I know where to put the 2.27”

        set inst2 = inst

At the end of the function the memory location of inst is passed to inst2, so now they are the same thing.
The inst2.int will be now 3 and inst2.float[0] will be 3.14, the inst2.float[49] is now null because we've changed the instance's memory location/position.
Question: So what about the previous inst2.float[49] ?
Answer: It still exists in memory and can't be accessed from anywhere, and this is what we call a leak.

Any change to the inst and inst2 member variables will be applied to each other together, so now if I set "inst.int = 2" then the "inst2.int" will be 2 and the cause is that they share the same position in memory.
Remember that every variable has a pointer which points its position in computer's memory. And computers take that position to read and edit variables.

<A.4> Struct's functions, methods I:

We already know that structs are like variable arrays that hold different types of data's, but know we learn that they can have functions too. It may sound weird, as you imagine something like myArray[2] to be a function.
The struct's functions are called methods. They are declared exactly like the functions without any special exceptions.
Let's go to a simple example:
scope LearnSomeMethods initializer OnInit
    struct Printer
        method printMsg takes nothing returns nothing
            call BJDebugMsg(&quot;Chicken Chocolates&quot;)

    function OnInit takes nothing returns nothing
        local Printer var = Printer.create() //That's a method too!
        call var.printMsg() //It will show the above message

Question: Can I use variable members inside methods?
Answer: Yes and you can use them by writing this.(member name)
struct Incr
    integer int
    method increase takes integer increment returns nothing
        set this.int = this.int + increment

function foo takes nothing returns nothing
    local Incr my = Incr.create()
    set my.int = 5
    call my.increase(5) //now my.int will become 10, the keyword &quot;this&quot; inside the method is replaced with the instance name &quot;my&quot; during the call

<A.5> Struct's Encapsulation:

First we must explain what is encapsulation and some basic things about Object Oriented Programming (OOP).
Encapsulation is a language mechanism for restricting access to some of the object's components.
When we refer to objects we mean the structs.
struct Animal
    string array skin color
    string eye color
    boolean hasTail

Animal is an object and the members are the components of the object.
But as I said considering it as an multiple array with different types of data is exactly the same thing.
Like this: integer array var[][] this is a double array integer which can be wrote like this.
struct doublearray
    integer x
    integer y
    integer value

Where x,y are the [][] of the variable var and the value is the number it holds in that position.
To make this struct as functional as the double array variable we will have to add some methods, which will be explained on advanced levels. After this parenthesis we go back to see the struct's encapsulation mechanism.

The keywords we can use inside a struct are:
private: It makes a variable/method be used only inside a struct (by methods)
public/(blank): The variable/method can be used and outside of struct
readonly: The variable can be edited inside the struct but can only be read outside of it.
static: Makes the variable/method global-like and doesn't need memory creation (allocation) to be used and can also be read by any instance of a struct type. To use a static variable/method you have to write (structName).(var/methodName).
public/private/readonly static: The combination of them.


struct MyData
    public integer a
    integer b
    private integer c
    readonly integer e
    method change takes nothing returns nothing //that's a public method
        set this.c = GetRandomInt(1,4)
        set this.e = GetRandomInt(1,7)

function foo takes nothing returns nothing
    local MyData my = MyData.create() //We can't use the keyword thistype here because it's outside of struct blocks.
    set my.a = 5
    set my.b = 6
    set my.c = 7        //Error, integer c is private you can't edit it from here, so you have to make a public method to edit it.
    set my.a = my.c + 3 //another error, because its private you can't read it also.
    set my.e = 3*3 //Error, integer e is readonly, you can't edit it.
    call my.change()
    set my.a = my.b*my.e

<A.6> Static/Non-Static members:

Here we will focus on what are static and non-static variables and methods.
As we saw on previous sessions we called the local variable we created an instance. An instance is non-static object because it can be created and destroyed. A static member is an irremovable component which lasts for the whole program execution (in our case Wacraft III) and can be seen by every instance of the same type. We can say without doubts that is a global-like variable that is referred into a struct. To declare a static member inside your struct you have to simply put the static keyword before the variable/method.
You must also know that when you have to encapsulate it you first put the data access keyword first and then the static one.
Let's see a simple example.
library StaticDeclarations

        integer j

        integer i
        private static integer k
        static integer j

        method moo takes nothing returns nothing
            set this.i = 1 //Valid
            set .i = 2     //Also valid, its faster and simpler to use .(member) instead of this.(member)
            set i = 3      //And that is valid too, but it is deprecated
            set j = 4 //Sets the global variable
            set thistype.j = 5 //Sets the static variable
            set k = 6 //Valid if there is no global variable with the same name

<A.7> Struct's methods II:

A struct instead of the method's you declare it has its own methods. Those help for memory manipulation and other stuff. Let's see the create method. As we saw you can write it like this: (structname).create()
Question: So how can we write it inside a struct with our current knowledge ?
Answer: As we see first we can use it out of struct, so it must be a public method, secondly to call the method we write the structname instead of the instance so it is a static one and finally we can assign the product to a instance so it returns the struct type.
Then it must be coded like this:
public static method create takes nothing returns thistype

If we have the create function, there must be a destroy one. It must take the instance we created to remove it now, so it's a non-static method and it returns nothing. So we write it like this:
method destroy takes nothing returns nothing

Question: But why do we need it ?
Answer: At <A.3> we saw a leak, so with this method we prevent leaks, by writing first inst2.destroy() and then assigning the inst to inst2.
The create/destroy methods have inside them the .allocate() and .deallocate() methods which take(create) and release(destroy) usable memory.
You can't overwrite these 2 methods as you can do with create/destroy methods.
Question: So how can we overwrite c/d methods?
Answer: Here is an example:
struct terminator
    private unit u
    method destroy takes nothing returns nothing //We set it public because we want it to be called outside of struct
        call KillUnit(this.u)
        set this.u = null //Prevent leak
        call this.deallocate() //Nullify this instance's value and release the memory it held for usage
    static method create takes unit ut returns thistype
        local thistype this = thistype.allocate() //Because this function is static the &quot;this&quot; keyword 
                                         //is only a variable name, we name the variable &quot;this&quot; because we want to make it 
        set this.u = ut                //look like the non-static methods.

        return this

//This function is called by a unit event
function foo takes nothing returns nothing
    local unit u = GetTriggerUnit()
    local terminator ss = terminator.create(u) //This method will create an instance and set the unit member of it with the &quot;u&quot;
    call TriggerSleepAction(0.5)
    call ss.destroy() //This will kill the unit and destroy the instance

The destroy method can be either private or public, but NOT static, as we need to destroy an instance.
It also must take and return nothing.

The create method must be static because it's a function that produces instances. We are also allowed to change its initial parameters from nothing to the ones we need (a unit for above example), the return must be the strut's name or thistype for faster and portable coding.
If you don't write the allocate() deallocate() methods your new create and destroy methods won't have any effect at all.

<A.8> OnDestroy/OnInit methods:

Every struct has except the create/destroy/allocate/deallocate method these 2 optional ones: The onDestroy and onInit methods.
Let's begin with the first one, the onDestroy method. Firstly we must see how it's written.
private method onDestroy takes nothing returns nothing

This method is called right before an instance is destroyed. Most people here at TheHelper don't recommend it as it can be done in the destroy method.
So when you call (instance).destroy() the onDestoy method is called and then the destroy. Fusing onDestroy and destroy methods save us from the slow function calls of the Jass language.
Now we move to the onInit method: This is a method that it is executed on map initialization without any custom calls done from events and scopes.
You write it exactly like this:
public static method onInit takes nothing returns nothing

This is a very helpful method for initiating struct instances and their members or anything else.

These 2 methods have a strict syntax and they are not like create method which can take any parameters you like.

<B.1> Moving from scopes to structs:

As you saw a struct also supports encapsulation as a scope does, therefore a struct has also a method that runs on map initialization as a scope can also do, so what about coding into pure structs? That sounds interesting when it comes to Warcraft III, well because you use less code for the same things in a more efficient way. Let's move to the next course when you code a simple spell inside a struct.

<B.2> Writing a whole spell inside a struct:

It's a very basic spell for being written solely into a struct, but it covers everything about the basics you leaned until now.
I know I can make it more optimized, but it won't seem like struct based spell.
In the main (static) method that is called by an event, as you see we create an instance with the data we need
Then we create methods that return us the preferred data and after the use we destroy the instance.
struct Fireball
    static integer abilID = 'A000' //A channel spell targets units
    static real init = 125
    static real mult = 25
    private unit caster //We want this variable to be used only in our struct, and no other code should interfere with it.
    private method getDamage takes nothing returns real
        return init + GetUnitAbilityLevel(.caster, abilID) * mult //We can avoid writing &quot;thistype.&quot; because we use no globals
    endmethod                                              //As you see we also put the dot &quot;.&quot; to declare a non-static component
    private method destroy takes nothing returns nothing
        set .caster = null  //We remove any leak possible
        call .deallocate() //We release the memory back to the system

    private static method create takes unit caster returns thistype
        local thistype this = thistype.allocate() //We get memory for usage

        set this.caster = GetTriggerUnit() //The &quot;this&quot; is a name here we can't avoid it !
        return this //Our instance named &quot;this&quot; is ready now for return
    private static method doDamage takes nothing returns boolean
        local thistype this = thistype.create(GetTriggerUnit()) //We crate an instance and insert a units to its data
        //Here we damage the target, we use the instance's unit and a method that returns us the prefered amount of damage we want to deal
        call UnitDamageTarget(this.caster, GetSpellTargetUnit(), this.getDamage(), true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WOOD_LIGHT_SLICE)
        call this.destroy()
        return false
    static method onInit takes nothing returns nothing
        local trigger tt = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(tt, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(tt, function thistype.doDamage) //As you see we can't use the keyword method because static methods are treated like functions.
        //Here we can't avoid the thistype because it's a method and not a variable.
        set tt = null

<B.3> Extensions/Interfaces:

The 2 previous sessions were a intro to the intermediate difficulty level tutorial. I suggest you not read this tutorial like a magazine as you will get confused every time, so read about 2 sessions per day and then the next day make a repetition for the previous sessions.
Now we will talk about extensions and interfaces. We will begin first from the extensions.
Let's say we want to create some structs that most of them have the same members, we want to create some abilities and they will always have as members the caster, the target, the special effect, the damage... so instead of writing them all you just extend your new struct to the prototype one. This example show how can we multiple extend our structs to create more complicated variable types.
struct Vector1D
    integer x

struct Vector2D extends Vector1D
  //integer x (is passed from first struct)
    integer y

struct Vector3D extends Vector2D
  //integer x (is passed from 2DVector and NOT from first)
  //integer y (is passed from 2DVector)
    integer z

As you saw to pass 2 struct's members into the struct we need we can't extend to one struct, comma, another struct and so on.
The extension must be linear and not a node-like one. These three structs have NOTHING in common regarding to memory allocation. So if you create an instance for Vector2D you won't automatically create for Vector1D, it's just a c/p of the extending struct's members. The create/destroy/onInit/onDestroy methods don't pass to the extended struct. These methods are not your members/components, but the others you declare are.
Every day I add/remove/edit some stuff, it will cover everything about structs. This tutorial is updated regularly, so don't rush to judge, I am very sorry although for my mistake of posting it too early.
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Varine Varine:
    Eh, whatever. Thanks for listening guys
  • jonas jonas:
    Sure :) Let us know how it ends
  • Varine Varine:
    All of these things will end happily, they're just stressful. And I still lack many good friends that I can go to, and the ones I can are preoccupied with similar things. Thus general chit chat, cuz for some reason TH and Ghan and Tom all actively keep it up.
  • Varine Varine:
    Just gotta keep Miss Mazie up through the week until her shock wears off and she realizes that she still has family all around her, and bossman will do whatever he's going to do and I'll respond appropriately when it happens. Thank you all for the support, I do very much appreciate everyone being here for me through the years
  • vypur85 vypur85:
    Best of luck Varine!
  • vypur85 vypur85:
    I just gotten myself an offer to work in China. The pay quadruples my current one. Damn.... Not really ready to start a new life there in China.
  • The Helper The Helper:
    I have heard that they pay pretty good to English teachers in China - you would be an expat
  • jonas jonas:
    Cool, what kind of job?
  • Accname Accname:
    I would be careful with jobs in China. They can be hit and miss depending on where in China you go. Places like hong kong / Shengzen / Beijing can be neat. Other places not so much.
  • Accname Accname:
    I would recommend searching for some first person experiences for the city you got the offer in. Especially now when the political situation in China is deteriorating.
  • jonas jonas:
    Accname, long time no see
  • jonas jonas:
    What have you been up to
  • tom_mai78101 tom_mai78101:
    Hey Accname, welcome back.
  • Accname Accname:
    Not much. Working in the Renewable Energy Sector as an IT Consultant. Its okay, but I think I preferred working at the university. It was more relaxed and you met all kinds of crazy people there.
  • vypur85 vypur85:
    I gotten a teaching position for Biology in a college in Wuhan (yes, there)... I suppose it should be fine there (I hope). Many of my ex colleagues are teaching in China as well currently (none in Wuhan though)
  • vypur85 vypur85:
    And I signed the contract already. I guess there's no turning back....
  • jonas jonas:
    @Accname how many hours do you work? I heard in some sectors IT consultants rack up insane hours
  • jonas jonas:
    @vypur85 sounds nice, have fun : )
  • Accname Accname:
    I am supposed to work 40 hrs a week, but I can work more if I like and I will be paid for those hours (as long as I don't go too far, there are laws and company policies, etc)
  • Accname Accname:
    In practice its basically work as much as you like, as long as the job gets done in time.
  • jonas jonas:
    Haha, my job is like that as well... that usually means I have a few 70-80 hours weeks a year, and lots of 20 hours weeks...
  • jonas jonas:
    a few weeks ago, one of my friends basically said "jonas, I received an invitation to submit something to conference X but I'm too lazy to do it and also the conference isn't advanced enough for my high level of research*, why don't you write something? Oh by the way, the deadline is in two weeks. Enjoy!" so I got two 80 hour weeks out of that kind offer. (*of course he didn't say those parts, but it's a better story this way)
  • jonas jonas:
    now I'll have next week off to make up for overtime :p and I'll play some good old gothic 2
  • The Helper The Helper:
    Hope you are enjoying that gothic 2~
  • jonas jonas:
    Heck yeah :cool: It's unfortunate that the game series wasn't generally well received outside of Europe, but it seems they want to remake Gothic 1 now. I'm very excited but also very scared at the same time. I hope they won't pull a reforged

    Members online

    No members online now.


    Hive Workshop NUON Dome