Tutorial The Struct Tutorial

Discussion in 'Graveyard' started by NoobImbaPro, Nov 19, 2011.

  1. NoobImbaPro

    NoobImbaPro You can change this now in User CP.

    Ratings:
    +60 / 0 / -0
    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.
    Contents:
    Basics:
    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:​
    Intermediate:
    B.1: Moving from scopes to structs:​
    B.2: Writing a whole spell inside a struct:​
    Inheritance:
    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:
    JASS:
    struct (1.name)
    {2.list of variables}
    endstruct

    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:

    JASS:
    struct MyData //A proper way to write a struct name
        integer intVar //A proper way to write member names
    endstruct
    
    function foo takes nothing returns nothing
        local MyData instance = MyData.create() *
        set instance.intVar = 5 **
    endfunction


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

    Example:
    JASS:
    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
        endfunction
    endscope


    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:
    JASS:
    scope LearnSomeMethods initializer OnInit
        struct Printer
            method printMsg takes nothing returns nothing
                call BJDebugMsg("Chicken Chocolates")
            endmethod
        endstruct
    
        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
        endfunction
    endscope


    Question: Can I use variable members inside methods?
    Answer: Yes and you can use them by writing this.(member name)
    Example:
    JASS:
    struct Incr
        integer int
        method increase takes integer increment returns nothing
            set this.int = this.int + increment
        endmethod
    endstruct
    
    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 "this" inside the method is replaced with the instance name "my" during the call
    endfunction


    <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.
    JASS:
    struct Animal
        string array skin color
        string eye color
        boolean hasTail
        .....
    endstruct


    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.
    JASS:
    struct doublearray
        integer x
        integer y
        integer value
    endstruct

    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.

    Example:
    JASS:
    
    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)
        endmethod
    endstruct
    
    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
    endfunction


    <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.
    JASS:
    library StaticDeclarations
    
        globals
            integer j
        endglobals
    
        struct
            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
            endmethod
        endstruct
    endlibrary


    <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:
    JASS:
    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:
    JASS:
    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:
    JASS:
    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
        endmethod
        static method create takes unit ut returns thistype
            local thistype this = thistype.allocate() //Because this function is static the "this" keyword 
                                             //is only a variable name, we name the variable "this" because we want to make it 
            set this.u = ut                //look like the non-static methods.
    
            return this
        endmethod
    endstruct
    
    //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 "u"
        call TriggerSleepAction(0.5)
        call ss.destroy() //This will kill the unit and destroy the instance
    endfunction


    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.
    JASS:
    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:
    JASS:
    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.
    JASS:
    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 "thistype." because we use no globals
        endmethod                                              //As you see we also put the dot "." 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
        endmethod
    
        private static method create takes unit caster returns thistype
            local thistype this = thistype.allocate() //We get memory for usage
    
            set this.caster = GetTriggerUnit() //The "this" is a name here we can't avoid it !
            
            return this //Our instance named "this" is ready now for return
        endmethod
        
        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
        endmethod
            
        
        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
        endmethod
    endstruct


    <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.
    JASS:
    struct Vector1D
        integer x
    endstruct
    
    struct Vector2D extends Vector1D
      //integer x (is passed from first struct)
        integer y
    endstruct
    
    struct Vector3D extends Vector2D
      //integer x (is passed from 2DVector and NOT from first)
      //integer y (is passed from 2DVector)
        integer z
    endstruct


    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.
     

Share This Page