| 11-15-2008, 01:46 PM | #1 |
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: 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: 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: 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: 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: 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: 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 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: 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. 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: 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 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: 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. 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: 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: 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 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... 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: 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: 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(): 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: 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
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: 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. 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: 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: 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: 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: 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. 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...) 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. 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... 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? 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. 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"
}
|
| 11-16-2008, 12:04 AM | #2 |
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: 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. |
| 11-16-2008, 12:23 AM | #3 | |
Quote:
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. |
| 11-16-2008, 02:04 AM | #4 |
I see, thanks. |
| 11-16-2008, 04:57 PM | #5 | ||
Quote:
Anyways level stuff was a good idea, it made me feel like I was reading about myself from the times long forgotten
|
| 11-17-2008, 02:15 AM | #6 |
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. |
| 11-17-2008, 08:14 AM | #7 | |
Quote:
Or you can write that yourself and post it here as an addition to this tut. |
| 12-05-2008, 02:09 PM | #8 |
Nice anecdote. |
| 12-06-2008, 11:29 PM | #9 |
What is the "null" function's pointer ? Same as structs, 0 ? |
| 12-06-2008, 11:52 PM | #10 |
0 |
| 03-22-2009, 12:07 AM | #11 | |
Nice tutorial. Do you plan to write the part II ? Quote:
|
| 03-22-2009, 12:22 AM | #12 |
yes. |
| 04-11-2009, 07:55 PM | #13 |
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? |
