wc3campaigns
WC3C Homepage - www.wc3c.netUser Control Panel (Requires Log-In)Engage in discussions with other users and join contests in the WC3C forums!Read one of our many tutorials, ranging in difficulty from beginner to advanced!Show off your artistic talents in the WC3C Gallery!Download quality models, textures, spells (vJASS/JASS), systems, and scripts!Download maps that have passed through our rigorous approval process!

Go Back   Wc3C.net > Tutorials > JASS/AI scripts tutorials
User Name
Password
Register Rules Get Hosted! Chat Pastebin FAQ and Rules Members List Calendar



Reply
 
Thread Tools Search this Thread
Old 11-15-2008, 02:46 PM   #1
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,898

Submissions (37)

Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)

Hero Contest #3 - 2nd Place

Default vJass event handling I (function interfaces)

Code flexibility is a good thing, the usual thing to do is to send data between functions and the functions process the data somehow. But sometimes, you don't just need to pass data, you need to pass code itself! Variable behavior is necessary when you want to simplify something into smaller parts.

So, what does the last paragraph mean? I'll explain vJass function interfaces and their use, well, that's not true, I'll explain their use and then I'll explain vJass function interfaces, in other words, I'll present you problems and much later I'll show you how function interfaces solve them.

Random gameplay events

In an old map called "vexorian hero arena" there were random events, stuff that would happen randomly along the game, well, I think these are not so odd, let's say you want to add random events to your map. You need a system that randomly decides to run an event, and once the event ends it is able to randomly decide to start another event. Your system will require to know two things : What to do for an event and the duration of the event. However, duration is highly dependent on the event, and some complex events may have variable durations that depend on the number of players around an area or something like that.

The lame approach - (Normal Jass as a path to self-destruction.)
So, "this is easy!" you would say so you begin coding the system:

Collapse JASS:
// execute at map init:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, 2)

          if(p==0) then
                 // do stuff for event 0
                 // assign duration for event 0
                 set duration = 60.0


          elseif(p==1) then
                 // do stuff for event 1
                 // assign duration for event 1

          elseif(p==2) then
                 // do stuff for event 2
                 // assign duration for event 2

          endif
          call TriggerSleepAction(duration )

    endloop
endfunction

Easy! , However that's absurdly messy, your events would likely take much more than a few Jass lines, you might end with this giantic function in which tons of different event's codes are mixed with each other.

Level 2 Jass people will come and introduce the concept of functions, those things will add modularity to the code and make it less messy:

Collapse JASS:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, 2)

          if(p==0) then
                 set duration = GameEvent0()

          elseif(p==1) then
                 set duration = GameEvent1()

          elseif(p==2) then
                 set duration = GameEvent2()

          endif
          call TriggerSleepAction(duration )

    endloop
endfunction

So the events are now split in functions and each of those functions return the duration for the event. As you can see the code looks much better, level 2 coders are clever afterall.

However, this is still problematic, your map is a WIP you have no idea how much events you'll add, you might end up adding 25 different random events just for fun, but later you might remove 4 of them because of imbalance/etc, you might also replace them. Keeping this if-then-else picker does not seem like a great idea anymore, everytime you have to add an event you will have to modify the GetRandomInt line and also add a whole elseif(p==3) then block.

Level 3 coder will come and say that he can get rid of those issues, but this requires a whole system rewrite. The level 3 guy has seen a lot of evil things happen, and knows that the more repetitive code he has to type the more mistakes he will make and the more time he'll waste fixing them. He has also seen evil from a very close stand point, he knows that the only way to get rid of the if-then-else picker is to use a very odd concept, to make variables hold functions...

Yes, that's right, a good solution for this would be to store things like GameEvent0, GameEvent1, and GameEvent2 in an array, then make the picker randomly pick one of the elements in the array and run it. Something like this:

Collapse JASS:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p
 local ????? array V
 local integer n

    set V[0] = GameEvent0
    set V[1] = GameEvent1
    set V[2] = GameEvent2
    set n=3

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          set duration = V[p]()

          call TriggerSleepAction(duration )

    endloop
endfunction

As you can see, this is just a theoric solution to the problem, cause it doesn't compile yet, level 3 dude wants to somehow store the functions in an array and then call the functions from the array. However, he doesn't know how to do that yet. So, he investigates further and Jass actually has a way to call a function "from a variable" .

native ExecuteFunc takes string func returns nothing
Yes, ExecuteFunc is one of those magical natives, it calls a function you made but it takes a string argument, therefore you actually pass the NAME of the function to it, not the function itself, however, you can also pass it a string variable that holds the name, which will allow level-3 to implement the algorithm he thought in Jass:

Collapse JASS:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p
 local string array V
 local integer n

    set V[0] = "GameEvent0"
    set V[1] = "GameEvent1"  //names of functions.
    set V[2] = "GameEvent2"
    set n=3

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          call ExecuteFunc( V[p] ) 

          call TriggerSleepAction(duration )

    endloop
endfunction

Fine... However as you can see, level-3 is confused, he has forgot about the duration thing, when he notices, he can't find a way to get the return value of a function he runs through ExecuteFunc. He will go to forums. Level-4 dude will come and tell him "You noob, you cannot return values in a function you run through ExecuteFunc!" . So, Level-3 insists for help and level-4 will finally explain him how to solve this problem, Level-4 's solution looks like this:

Collapse JASS:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p
 local string array V
 local integer n

    set V[0] = "GameEvent0"
    set V[1] = "GameEvent1"  //names of functions.
    set V[2] = "GameEvent2"
    set n=3

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          call ExecuteFunc( V[p] )

          set duration = udg_returnedDuration

          call TriggerSleepAction(duration )

    endloop
endfunction

So, the GameEvent functions would look like this:
Collapse JASS:
function GameEvent0 takes nothing returns nothing
     // do stuff for event 0
     set udg_returnedDuration = 4.0 // the duration we want
endfunction

As you can see, the deal got messier, since it is now using a global variable to pass the "return value" which isn't a return value anymore, the function is just assigning a variable at the end of the execution. Either way, this is how vexorian hero arena did random events back in prehistory.

Level-3's mistake was to make a thread asking for help showing his code, cause now Level-5 saw the thread and points out that ExecuteFunc is not a good idea, because it uses the function names as strings, it will confuse things like the map optimizer lowering the optimization capabilities of that tool. And well, you could mistype a function name and have a crash, it is also a little slow, not like it matters in this case, we are not calling ExecuteFunc very often...

So, level-5 has dealt with this "call a function variable" a lot of time, so he already 'knows' how to do this 'correctly' he will use code

Collapse JASS:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p
 local code array V
 local integer n

    set V[0] = function GameEvent0
    set V[1] = function GameEvent1   // We are now using "code" values
    set V[2] = function GameEvent2
    set n=3

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          call ExecuteCode( V[p] )

          set duration = udg_returnedDuration

          call TriggerSleepAction(duration )

    endloop
endfunction

However, as you can see Level-5 really has no idea about heck, this won't even compile, first of all, you can't have arrays of type code, second there is no such thing as an ExecuteCode function.

Level-6 comes to the rescue and has a patch solution already:

Collapse JASS:
function makeTrig takes code cod returns trigger
 local trigger t=CreateTrigger()
    call TriggerAddAction(t, cod)
 return t
endfunction

function randomEvents takes nothing returns nothing
 local real duration
 local integer p
 local trigger array V
 local integer n

    set V[0] = makeTrig( function GameEvent0)
    set V[1] = makeTrig( function GameEvent1)   // We are now using trigger objects that are created by makeTrig.
    set V[2] = makeTrig( function GameEvent2)
    set n=3

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          call TriggerExecute( V[p] )

          set duration = udg_returnedDuration

          call TriggerSleepAction(duration )

    endloop
endfunction

Level-6 is smart he is just using that makeTrig function to convert the code values to a trigger that has that code as action, then he can use TriggerExecute to run them.

Improving it - (correlation between vJass and better code)

But actually, Level-6 is still in the void, thanks to the work of those guys from Level-3 to Level-6, not to mention the great contribution from limitations in the game. We now got a very horrible and messy piece of code.

First of all, now the way to input the event functions is makeTrig( function functionName) that's quite messy, there's also the small issue that function GameEvent0 , unlike ExecuteFunc, requires you to declare GameEvent0 above the part where you are using the code value.

And something everybody overlooked so far, that whole V[0] = , V[1] = , V[2] = , n=3 stuff doesn't really improve the situation that much from that initial if-then-else picker , it might be very easy to add a game event, but to remove you'll still have to do a lot of code modiffication.

Level-7 knows some little vJass and has seen enough clean code in his life to know how to deal with all those problems, what the system needs is better separation between the system itself, the part that adds a function to the system and the functions for the game events. Not to mention we should stop using that udg_ variable, it is lame. So, as a matter of fact Level-7 is much more advanced than the previous, and will improve the code drastically.

Collapse JASS:
library RandomEvents initializer init

    globals
     public  real    Duration = 0.0

     private integer n = 0 //now that n variable becomes a private global so we
                           //can use it in all functions in this system

     private trigger array V  //the same with the v trigger array
    endglobals

    function AddGameEvent takes code cod returns nothing
        set V[n] = CreateTrigger()
        call TriggerAddAction( V[n] , cod)
        set n=n+1
    endfunction


    private function init takes nothing returns nothing
     local real duration

       loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          call TriggerExecute( V[p] )

          set duration = RandomEvents_Duration //see the use of a public global here.

          call TriggerSleepAction(duration )
    endfunction


endlibrary


As you can see this was an extreme change, notice how makeTrigger and the part that adds functions have been merged into a single AddGameEvent function, which stores it in the array at the same time it converts to trigger, we are using some private globals to replace n and V, and a public global to replace udg_returnedDuration.

The justification for all of these is that the way to add a game event is to just make a trigger with a scope and something like this:

Collapse JASS:
scope GameEvent0 initializer init

    private function doEvent takes nothing returns nothing

       // do stuff for event 0
       set RandomEvents_Duration = 4.0 //the "return value"
    endfunction

    private function init takes nothing returns nothing
        call AddGameEvent( function doEvent )
    endfunction

endscope
The level of flexibility accomplished with all of this is much better, just think of it, we keep each event's things in their own 'trigger' and scope, so there is much better separation of code, and the best part of it is that to remove and event, we might just disable the trigger or comment the "AddGameEvent" line out. To add an event just create a new trigger+scope that does something like this. In fact this almost looks as if you made a custom event "Random Event is picked and you are doing things like when in those blizzard-made events you use to make code run when a spell is cast.

However, Level-7's code is not perfect, could it be because he is manually messing with triggers, or that he is requesting the user to manually do the variable assignment.

Level-7 will immediately say that no improvements are necessary, well the thing is that Level-7 knows so much that for him, the code is perfect as it is right now, and that globals to pass results is not a big deal, in fact, he thinks this is easier for everyone since it is more similar to blizzard's ways, as a matter of fact, I don't think I will convince Level-7 that the code is wrong, however, I will tell you what a theorical Level-8 would do, but first I'll explain what the problem with the variable stuff is.

We lost the abstraction - (and it wouldn't hurt to get it back.)

Could we turn back to level-3's original idea. That's actually very close to the ideal, instead of all this trigger, code, global variable, combo. What the algorithm was intended to do was calling a function from a variable and getting its return value. The implementation from that good design to what is still very normal Jass became into making the system create a trigger, and then make the trigger assign a global variable.

Problems with it? Our function is not returning values anymore, it is now assigning a global variable, the abstraction is the opposite to what we wanted to have, and the users of the system will suffer the consequences, they may now forget about assigning the variable, for example. What if the required function was more complicated? What if it required to take 3/4 arguments as well as returning values? That would mean more globals to pass between the system and the function, more abstraction loss.

The current implementation of the random event system is actually very good, but we could be looking for more, something that would make both the system and the user functions closer to what we wanted to have initially.

Towards more malleable functions - (Certain vJass functions basics)

There are a lot of things to know about Jass functions yet there are a couple of specific details about vJass functions as well, in vJass functions behave closer to objectsstructs, in that they have their own share of methods and members. This will appear off-topic, however it is required to understand these things before messing with function interfaces.

evaluate and execute
There were two small issues with Jass functions, one smaller than the other, first of all, you can only call a function from a line that's bellow its declaration, the problems caused by this were mostly dealt with by the implementation of libraries, however, there is still an small issue with mutually recursive functions, I am talking about functions that call each other, for a quick example:

Collapse JASS:
function A takes unit u returns nothing
 local integer i=0
    set u:processing = true
    set u:infected = true
    loop
        exitwhen (i==bj_MAX_INVENTORY)
        if (UnitItemInSlot(u,i)!=null) then
            call B(UnitItemInSlot(u,i))
        endif
        set i=i+1
    endloop
    set u:processing = false
    
endfunction

function B takes item it returns nothing
 local unit u = GetOwningUnit(it)

    set it:infected = true
    if (not u:processed) then
        call A(u)
    endif
endfunction

How that processed and infected stuff works should probably be a good topic for another tutorial, nevertheless it should be clear that A needs to call B while B needs to call A, however this is impossible to do in Jass, and to prevent bad mistakes, vJass forbids it as well, but that doesn't mean something similar is possible, in order to allow functions to call functions that were not declared above, I have added the evaluate method to functions.

evaluate is a method, therefore you call it just like the way used to call methods from structs, this method has exactly the same argument list and return value as the function, its job is merely to allow you to call the function before it has been declared.

Collapse JASS:
function A takes unit u returns nothing
 local integer i=0
    set u:processing = true
    set u:infected = true
    loop
        exitwhen (i==bj_MAX_INVENTORY)
        if (UnitItemInSlot(u,i)!=null) then
            call B.evaluate(UnitItemInSlot(u,i))
        endif
        set i=i+1
    endloop
    set u:processing = false
    
endfunction

function B takes item it returns nothing
 local unit u = GetOwningUnit(it)

    set it:infected = true
    if (not u:processed) then
        call A(u)
    endif
endfunction

This is vJass' solution to this problem, and as you can see, it really isn't a big deal we are merely adding .evaluate after the name of the function instead of calling it directly.

If the function has a return value, it will still work, in a less practical example:

Collapse JASS:
function A takes nothing returns nothing
 local integer i= B.evaluate(1,2)
    call BJDebugMsg(I2S(i))
endfunction

function B takes integer x, integer y returns nothing
    return x+2*y
endfunction

We are again calling function B, but this time it has a return value and two arguments, x and y, the arguments we are giving to it are 1 and 2 therefore the function B will return 5 (which is 1+2*2) , evaluate is returning the same value.

There is still another small issue and it is the concept of Jass' threads. To explain this small problem, I'll have to use an example:

Collapse JASS:
function A takes unit u returns nothing
    call TriggerSleepAction(5.0)
    call KillUnit(u)
endfunction

function B takes nothing returns nothing returns nothing
 local unit u=GetTriggerUnit()
 local unit ua=GetAttacker()

    call A(u)
    call A(ua)

endfunction

What we intended was to kill the Triggering unit and the attacker after 5 seconds, however, what will truly happen is that the triggering unit will die after 5 seconds, and then the attacker will die after another period of 5 seconds.

This is because calling a function will stop the execution of the calling function, because of this waits in A will also make B wait, but that's not usually what is intended.

In order to call a function without having to wait for it to finish executing, we can create another thread, this is often done with ExecuteFunc or TriggerExecute (after creating a trigger), again these are a little overcomplicated, vJass fixed it by adding execute. execute is yet another method for functions, and it works exactly like evaluate, the main difference is that it doesn't support return values, because the function from which you invoke execute will not be blocked by waits inside the other functions

Collapse JASS:
function A takes unit u returns nothing
    call TriggerSleepAction(5.0)
    call KillUnit(u)
endfunction

function B takes nothing returns nothing returns nothing
 local unit u=GetTriggerUnit()
 local unit ua=GetAttacker()

    call A.execute(u)
    call A.execute(ua)

endfunction


Whats a function interface? - We finally get to the point!

Could we please revisit the "ideal" we've been talking about? That silly idea from the level-3 guy...

Collapse JASS:
function randomEvents takes nothing returns nothing
 local real duration
 local integer p
 local ????? array V
 local integer n

    set V[0] = GameEvent0
    set V[1] = GameEvent1
    set V[2] = GameEvent2
    set n=3

    loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          set duration = V[p]()

          call TriggerSleepAction(duration )

    endloop
endfunction

I am the guy who made vJass, therefore I wanted to actually have something that could work like this ideal... Of course, I also did not want to worry making something impossible to compile, in order for you to understand function interfaces, perhaps it would help you to see what I was trying to do when inventing them and why they ended up looking like that.

Basically, what this guy wanted was a way to put a function in a variable and then... call that variable as if it was a function. In the case of the randomEvents application we are trying to implement here, the variables is actually an array, but we could just worry about that later, this is what this guy wants to have:

Collapse JASS:
    function GameEvent0 takes nothing returns real
        call BJDebugMsg("woot we are running event 0")
        
        return 2.0
    endfunction
    
function somethingElse takes nothing returns nothing
 local function F //declare a variable of 'function' type

 set F = GameEvent0 //assign a function to our variable
 local real dur
 
   set dur = F() //Just call the variable...

endfunction

Pay attention to this, the guy wants to have 'function variables' basically a variable in which you can store a function and later call it. That code should do the same as if you just did set dur= GameEvent0(), the point is that the function is in a variable, at the time you do call F(), that line of code does not need to know what function it is calling. So somewhere else you can reassign the value of F to GameEvent1, for example, if we call F() again it will call GameEvent1.

That's actually what we were trying to do with strings/triggers and ExecuteFunc/TriggerEvaluate. However, as you could see, those things complicated everything up.

It would have been very nice to have something like that, I wanted to have something like that. Of course, vJass compiles to normal Jass and therefore there will always be limitations, let's see what I thought when trying to make this possible.

First of all, let's talk about how we are calling the variable:

Collapse JASS:
   set dur = F()

I didn't like this too much, of course, it looks awesome but back then this appeared like it was too hard to implement, there is also the problem that it looks way too much like calling a function, which would be ok, if the operation was as fast as calling a normal, but in Jass, that was not possible. So I just reused that syntax we have just learned: .evaluate() and .execute():

Collapse JASS:
   set dur = F.evaluate()

That's right, evaluate() is what we use to call one of our 'function variables'. Seems ok, at least vJass is consistent, cause this is what we are using to evaluate functions declared bellow.

Another issue is, how exactly will my compiler know the return type of the function? Its argument list? The number of arguments? And how will it be able to call it? Well, the thing is, Jass is not very dynamic in these regards...

The only solution I could think of was to split the functions in different categories of functions based on their argument lists and return values, so only functions of the same type could be assigned to a variable of that type. By the time you call the variable of that type, vJass will now the argument list and return value since we can be sure only function of that time are allowed as values here.

How would we allow the user to specify what type of function do you want in a variable? That's another issue, the initial idea was to do this:

Collapse JASS:
    function GameEvent0 takes nothing returns real
        call BJDebugMsg("woot we are running event 0")
        
        return 2.0
    endfunction
    
function somethingElse takes nothing returns nothing
 local (function takes nothing returns real) F //declare a variable of the wanted function type.
 set F = GameEvent0 //assign a function to our variable
 local real dur
 
   set dur = F.evaluate() //Just call the variable... (use .evaluate() )

endfunction
All right, not a great idea, now the code is terribly awful, and you will have to type that sort of giberish every time you want to use a variable of this function type...

My better idea was to allow people to give these types a name so you can later just use the name, instead of typing the whole function specs everytime you want to use this type of functions. Something like this:

Collapse JASS:
type somename extends (function takes nothing returns real)
// Declare the "function type" name it 'somename'

    function GameEvent0 takes nothing returns real
        call BJDebugMsg("woot we are running event 0")
        
        return 2.0
    endfunction
    
function somethingElse takes nothing returns nothing
 local somename F //declare a variable of the wanted function type, this time "somename"
 set F = GameEvent0 //assign a function to our variable
 local real dur
 
   set dur = F.evaluate() //just call it

endfunction

Anyway... I didn't like that type stuff too well, it actually makes sense, but I thought it wasn't too nice, I wanted a little more abstraction... I am not sure if that was a good idea but I decided to call one of these types a function interface. This way, the declaration would be similar to a function's declaration.

Collapse JASS:
function interface somename   takes nothing returns real

So please notice this, when you see some non-sense such as @function interface@ in some code, it just means the guy is declaring a "function type" so he can have "function variables" a detailed syntax is:

Collapse JASS:
function interface <NAME>   <ARGUMENT_LIST> <RETURN_VALUE>

NAME would be just the name of the function interface, after this statement the NAME just becomes another type that you can use in vJass, just like integer is a type, just like every struct is a type, a function interface is a type, so you can have variables, arrays, arguments and struct members of this new type.

The ARGUMENT_LIST and RETURN_VALUE work exactly the same as the argument list and return value work when you declare a function. Basically, only functions that have similar argument list and return value can be used as values for this type.

What is a similar argument list? Well, basically, the number of arguments must be the same, and the type of each argument must be the same, the names of the arguments don't matter that much.

As a matter of fact, the last code example will compile just fine in a recent jasshelper version, however in the past, there was another issue, how would you assign a function to one of these 'function variables' ? vJass was unable to just use the function's name. I also wanted the compiler to be able to validate whether the function belongs to the type you want to use - else we would be able to have silly bugs in code - For this reason I had a way to create function pointers.

The syntax was like this:

Collapse JASS:
function interface somename   takes nothing returns real
// Declare the "function type" name it 'somename'

    function GameEvent0 takes nothing returns real
        call BJDebugMsg("woot we are running event 0")
        
        return 2.0
    endfunction
    
function somethingElse takes nothing returns nothing
 local somename F //declare a variable of the wanted function type, this time "somename"
 set F = somename.GameEvent0 //assign a function to our variable, notice the odd syntax...
 local real dur
 
   set dur = F.evaluate() //just call it

endfunction

Anyway... Just using the function name works now, so I will not ellaborate on this odd syntax in this tutorial. To recapitulate, vJass allows this:

Collapse JASS:
function interface somename   takes nothing returns real
// Declare the "function type", name it 'somename'
// the somename function interface requires the function to take no arguments and return a real.


    function GameEvent0 takes nothing returns real
    /// This function takes nothing and returns real, therefore it is possible to use it in 'somename' variables.
    //
        call BJDebugMsg("woot we are running event 0")
        
        return 2.0
    endfunction
    
function somethingElse takes nothing returns nothing
 local somename F //declare a variable of the wanted function type, this time "somename"
 set F = GameEvent0 //assign a function to our variable
 local real dur
 
   set dur = F.evaluate() //just call it

endfunction

That's nice isn't it?

Level Eight - Applying what we learned.

Let's go back to Level-7 's code:

Collapse JASS:
library RandomEvents initializer init

    globals
     public  real    Duration = 0.0

     private integer n = 0 //now that n variable becomes a private global so we
                           //can use it in all functions in this system

     private trigger array V  //the same with the v trigger array
    endglobals

    function AddGameEvent takes code cod returns nothing
        set V[n] = CreateTrigger()
        call TriggerAddAction( V[n] , cod)
        set n=n+1
    endfunction

    private function init takes nothing returns nothing
     local real duration

       loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          call TriggerExecute( V[p] )

          set duration = RandomEvents_Duration //see the use of a public global here.

          call TriggerSleepAction(duration )
    endfunction


endlibrary

We want to make this use function interfaces... Why? Because right now we are using a global variable to pass stuff, with function interfaces the code would be closer to what we really want, that is to call a random function and get its real return value...

We'll begin by declaring the function interface, since our game event functions should take nothing and return a real, then the ARGUMENT_LIST and RETURN_VALUE parts are easy, but, what about the name? We could call the function interface "hoochieMama" if we wanted to, but it should be better to use a descriptive name, hoping that it makes the code easier to read... Thinking of a good name is always a hard thing to do... Let's name it RandomEventFunction , nice? Well, not that much, but it works.

Collapse JASS:
library RandomEvents initializer init

function interface RandomEventFunction   takes nothing returns real

// ...

That trigger stuff looks so silly now... Instead of a trigger array we want a RandomEventFunction array (Since it is a type we can use it on arrays and many other things...)

Collapse JASS:
    globals
     private integer               n = 0 //now that n variable becomes a private global so we
                                         //can use it in all functions in this system

     private RandomEventFunction   array V
    endglobals

Something gone from the globals block is that Duration variable thing, we don't need it anymore.

AddGameEvent? It is full of giberish right now, taking a code argument, creating a whole trigger, adding it to the array, what we'd like now is to just add the given function to the array.

Collapse JASS:
    function AddGameEvent takes RandomEventFunction fun returns nothing
        set V[n] = fun
        set n = n + 1
    endfunction

Yes... we can have 'RandomEventFunction' arguments as well, this is getting repetitive...

init? It is doing non-sense right now, calling TriggerExecute on a RandomEventFunction object... using that Duration variable we just removed, this needs some fixing...

Collapse JASS:
    private function init takes nothing returns nothing
     local real duration

       loop
           //keep repeating the loop
          call TriggerSleepAction( GetRandomReal(70, 70000) ) //wait a random amount of seconds

          set p = GetRandomInt(0, n-1 )
          set duration = V[p].evaluate() //Let's evaluate the function we just picked.
          //That's right, we just get the return value! It works!

          call TriggerSleepAction(duration )
    endfunction
endlibrary

So the system is ready, you may be wondering how will people be able to add a game event?

Collapse JASS:
scope GameEvent0 initializer init


    private function doEvent takes nothing returns real
        // Do all the stuff for this event
        
        //...
        
        return 15.0
    endfunction

    private function init takes nothing returns nothing
        call AddGameEvent( doEvent ) //just put the function name.
        
        /// Alternatively, you may also use:
        //
        //// call AddGameEvent( RandomEventFunction.doEvent )
    endfunction

endscope

FAQ - I can see it coming
Q. This is it? Isn't this tutorial too short?
This function interfaces stuff didn't seem to deserve more than that.

Q. I still don't understand...
I dunno what else to do, perhaps you are the kind of guy that learns better by doing than by reading examples, perhaps you need to make someone else write the tutorial.

Q. If Level-8 is that smart, shouldn't he be using timers or something like that?
There are plenty of things that could be improved in Level-8's code. I just wanted to focus on this whole "function variables" thing.

Q. THIS BE SIMPLE WITH VARIABLES WHY USE IT, ITS SLOW PIZDA
Yes, the example is very simple, just a return value, no arguments, etc.

Imagine we also needed arguments in the thing and you wanted to make this system as a general purpose thing and submit it so people use it.

Collapse JASS:

function interface TodoWhenUnitIsFound takes unit finder, unit found  returns nothing

//The user may just do:

function WhenFoundKillEnemy takes unit finder, unit found returns nothing
   if( IsUnitEnemy(GetOwningPlayer(finder), found) ) then
       call KillUnit(found)
   endif
       
endfunction

function init takes nothing returns nothing
    call StartUnitFoundSystem( gg_h000_bloodmage, WhenFoundKillEnemy )
endfunction

The alternative would be to kindly ask the user to use a bunch of variables or call functions just to get the arguments to his function...

And nope, it is not slower.

Q. But most people are used to that!
There was a time in which most people were used to using gamecache to attach stuff.

Q. Level-7 code was fine, and it worked, so why bother using function interfaces?
Level-1's code also worked just fine.

Q. I dunno I am still not convinced why should I use them
Then don't.

Q. Aren't interfaces better than function interfaces?
Yes, actually, in a way they are, that's the reason I'll talk about them in part II of this tutorial...

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Further reading - (skip this if you don't like off-topic)
Most real languages have either gotten very close to the ideal or have actually implemented the ideal, for example, look at what python does:

Code:
Python 2.5.2 (r252:60911, May  7 2008, 15:19:09) 
[GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> def sum(a,b):
...     return a+b
... 
>>> sum(2,3)
5
>>> x = sum
>>> x(4,5)
9

In fact, most languages use this "function variables" stuff to do some of the most basic operations, ever wondered how to do sorting in C++ ? (of course you didn't)

Code:
struct pair
{
     int x;
     int y
};

struct pairCompare
{
     bool operator ()(const pair A, const pair B)
     {
            if ( A.x < B.x )
                   return true;
            if (A.x == B.x)
                   return (A.y > B.y);

            return false;
     } 
};

void do()
{
     pair v[10];
     //fill v
     
     sort(v , v + 10 , pairCompare() )  //look at our "function variable"
}
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Sponsored Links - Login to hide this ad!
Old 11-16-2008, 01:04 AM   #2
Jazradel
Superuser
 
Jazradel's Avatar
 
Join Date: Dec 2005
Posts: 651

Submissions (1)

Jazradel has a spectacular aura about (110)Jazradel has a spectacular aura about (110)Jazradel has a spectacular aura about (110)Jazradel has a spectacular aura about (110)

Send a message via AIM to Jazradel Send a message via MSN to Jazradel
Default

I like it. A whole lot.
I'm keenly looking foward to part II.

I do have two questions though.
What would the code look like when parsed from vJass to Jass?
And have you considered something like this:
Collapse JASS:
function interface TodoWhenUnitIsFound takes unit finder, unit found  returns nothing

TodoWhenUnitIsFound WhenFoundKillEnemy
endfunction

I don't think the section about the level-5 person is needed. You already have the basic idea in level-4, and level-6 is the correct syntax. At most you should put in a footnote stating why using type code won't work.

And a few minor grammatical points, the sentence
"..ever wondered how to do sorting in C++ ? (of course you didn't)"
Would be easier to read like so:
"...have you ever wondered how to do sorting in C++ ? (of course not)"
There were a couple of things like that, but they're not important so I don't remember them.
__________________


Table:
Jump Map
Point System
Warcraft III Model Archive

W3 Model Archive should be available from https://www.mediafire.com/folder/cd5..._III_Resources, PM me if it doesn't work.

Last edited by Jazradel : 11-16-2008 at 01:24 AM.
Jazradel is offline   Reply With Quote
Old 11-16-2008, 01:23 AM   #3
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,898

Submissions (37)

Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)

Hero Contest #3 - 2nd Place

Default

Quote:
What would the code look like when parsed from vJass to Jass?

It would pretty much look like what Level-7 did.

well, at an init function generated by jasshelper, all functions used as function values are assigned a trigger in some array. .evaluate() and execute() get translated into a function call that then sets the arguments to globals , calls TriggerEvaluate/Execute and then returns a variable that was assigned by the code that was evaluated/executed. And the function pointers get translated into the index of these trigger arrays.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 11-16-2008, 03:04 AM   #4
Jazradel
Superuser
 
Jazradel's Avatar
 
Join Date: Dec 2005
Posts: 651

Submissions (1)

Jazradel has a spectacular aura about (110)Jazradel has a spectacular aura about (110)Jazradel has a spectacular aura about (110)Jazradel has a spectacular aura about (110)

Send a message via AIM to Jazradel Send a message via MSN to Jazradel
Default

I see, thanks.
__________________


Table:
Jump Map
Point System
Warcraft III Model Archive

W3 Model Archive should be available from https://www.mediafire.com/folder/cd5..._III_Resources, PM me if it doesn't work.
Jazradel is offline   Reply With Quote
Old 11-16-2008, 05:57 PM   #5
cohadar
master of fugue
 
cohadar's Avatar
 
Join Date: Jun 2007
Posts: 2,453

Submissions (5)

cohadar is just really nice (250)cohadar is just really nice (250)cohadar is just really nice (250)cohadar is just really nice (250)cohadar is just really nice (250)

Default

Quote:
Originally Posted by Vexorian
Towards more malleable functions - (Certain vJass functions basics)
This part is a little blurry, you might want to simplify it a bit.

Anyways level stuff was a good idea, it made me feel like I was reading about myself from the times long forgotten

Hidden information:

There once was a master programmer cohadar who wrote vJass code without function interfaces. A novice level-8 jasser, seeking to imitate him, also began to write code without function interfaces. When the novice asked the master to evaluate his progress, the master bitch-slapped him for writing such code, saying, "What is appropriate for the master is not appropriate for the novice. You must understand the Tao before transcending vJass structure"
__________________
Omg database crash deleted my signature, as a side effect this immensely improved wc3c.

Last edited by cohadar : 11-16-2008 at 05:58 PM.
cohadar is offline   Reply With Quote
Old 11-17-2008, 03:15 AM   #6
grim001
requires vJass
 
grim001's Avatar


Code Moderator
 
Join Date: Nov 2006
Posts: 1,540

Submissions (10)

grim001 is just really nice (277)grim001 is just really nice (277)

Send a message via AIM to grim001
Default

I hope this tutorial is going to eventually cover the concept of people using interfaces to register multiple event listeners. Then I can point them here instead of having to explain it in my documentation.
grim001 is offline   Reply With Quote
Old 11-17-2008, 09:14 AM   #7
cohadar
master of fugue
 
cohadar's Avatar
 
Join Date: Jun 2007
Posts: 2,453

Submissions (5)

cohadar is just really nice (250)cohadar is just really nice (250)cohadar is just really nice (250)cohadar is just really nice (250)cohadar is just really nice (250)

Default

Quote:
Originally Posted by grim001
I hope this tutorial is going to eventually cover the concept of people using interfaces to register multiple event listeners. Then I can point them here instead of having to explain it in my documentation.

Or you can write that yourself and post it here as an addition to this tut.
__________________
Omg database crash deleted my signature, as a side effect this immensely improved wc3c.
cohadar is offline   Reply With Quote
Old 12-05-2008, 03:09 PM   #8
PitzerMike
Alcopops
 
PitzerMike's Avatar


Tools & Tutorials Moderator
 
Join Date: Jan 2003
Posts: 2,794

Submissions (12)

PitzerMike is a splendid one to behold (643)PitzerMike is a splendid one to behold (643)PitzerMike is a splendid one to behold (643)PitzerMike is a splendid one to behold (643)

Approved Map: Pitzer's Minesweeper

Default

Nice anecdote.
__________________
Zoom (requires log in)
PitzerMike is offline   Reply With Quote
Old 12-07-2008, 12:29 AM   #9
ProFeT
User
 
ProFeT's Avatar
 
Join Date: Aug 2003
Posts: 189

Submissions (1)

ProFeT will become famous soon enough (71)ProFeT will become famous soon enough (71)ProFeT will become famous soon enough (71)

Outstanding Tool

Send a message via MSN to ProFeT
Default

What is the "null" function's pointer ? Same as structs, 0 ?
__________________
3dsMAX 5 Scripts:
mdl importer : http://wc3campaigns.net/showthread.php?p=918051 (with animations)
events editor : http://wc3campaigns.net/showthread.php?t=89816
ProFeT is offline   Reply With Quote
Old 12-07-2008, 12:52 AM   #10
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,898

Submissions (37)

Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)

Hero Contest #3 - 2nd Place

Default

0
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 03-22-2009, 01:07 AM   #11
Troll-Brain
User
 
Join Date: Oct 2006
Posts: 1,490

Submissions (1)

Troll-Brain has a spectacular aura about (134)

Default

Nice tutorial.
Do you plan to write the part II ?

Quote:
Q. Aren't interfaces better than function interfaces?
Yes, actually, in a way they are, that's the reason I'll talk about them in part II of this tutorial...
Troll-Brain is offline   Reply With Quote
Old 03-22-2009, 01:22 AM   #12
Vexorian
Free Software Terrorist
 
Vexorian's Avatar


Technical Director
 
Join Date: Apr 2003
Posts: 14,898

Submissions (37)

Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)Vexorian has a reputation beyond repute (1062)

Hero Contest #3 - 2nd Place

Default

yes.
__________________
Zoom (requires log in)Wc3 map optimizer 5.0
Someone should fix .wav sound in this thing.
Zoom (requires log in)JassHelper 0.A.2.A
Turns your simple code into something that is complicated enough to work.
Faster != more useful
Vexorian is offline   Reply With Quote
Old 04-11-2009, 08:55 PM   #13
NordCoder
User
 
NordCoder's Avatar
 
Join Date: Apr 2009
Posts: 1

NordCoder has little to show at this moment (1)

Default

Very interesting tut, Vexorian! I am fairly new to advanced vJASS concepts like these but it was nevertheless a great read. I am looking forward to Part II

I have one question though: Since the .evaluate method enables you to use a function before its declaration...Is that not the same as keyword? As far as I understand, keyword enables you to do the same. So in theory (and practice I guess) you could use this instead?
NordCoder is offline   Reply With Quote
Reply


Thread Tools Search this Thread
Search this Thread:

Advanced Search

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off


All times are GMT. The time now is 01:50 PM.


Affiliates
The Hubb The JASS Vault Clan WEnW Campaign Creations Clan CBS GamesModding Flixreel Videos

Powered by vBulletin (Copyright ©2000 - 2018, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services