| 08-25-2007, 04:24 AM | #1 |
USING MACRO INLINING This is an advanced JASS tutorial for people who want to optimize their code. To be able to use the information from this tutorial you need NewGen pack. Ok lets begin. As you probably know function calls are time expensive in JASS, this is the reason people remove BJs after all. Unfortunately this results in a massive code bloating, code becomes bigger, harder to understand and maintain. ----- Let us look at a classic example: JASS:
call SetUnitFlyHeight(blackdragon, RMaxBJ(GetUnitFlyHeight(greendragon), GetUnitFlyHeight(reddragon)), 0.0)
Ok this is simple, we are making flying armada and we want to set the height of our blackdragon to the maximum height of other dragons. This code is all fine and working properly but at some point people see this RMaxBJ and say stuff like "you have BJs in your code", like that is something wrong. In this case particular it would be extremely and obviously pointless to inline that part of code but for the sake of example we will do that. Now after looking in JassShopPro we find out that RMaxBJ is defined like this: JASS:function RMaxBJ takes real a, real b returns real if (a < b) then return b else return a endif endfunction Now we inline our code: JASS:
if (GetUnitFlyHeight(greendragon) < GetUnitFlyHeight(reddragon)) then
call SetUnitFlyHeight(blackdragon, GetUnitFlyHeight(reddragon), 0.0)
else
call SetUnitFlyHeight(blackdragon, GetUnitFlyHeight(greendragon), 0.0)
endif
But wait that looks bad, we need some variables: JASS:
local real greenHeight
local real redHeight
//...
set greenHeight = GetUnitFlyHeight(greendragon)
set redHeight = GetUnitFlyHeight(reddragon)
if (greenHeight < redHeight) then
call SetUnitFlyHeight(blackdragon, redHeight, 0.0)
else
call SetUnitFlyHeight(blackdragon, greenHeight, 0.0)
endif
Cool we did it, our code is fast and inlined :) WRONG! We did nothing, first of all we created 2 new local variables greenHeight and redHeight for the sole purpose of calculating a MAX? second our code just turned from one line into more than 10 lines !!! If we continue inlining like this the code will become so big and ugly that noone will be able to understand it easily, and if we make a bug somewhere in that code we will have a hell of a time finding it. Ok so how to do this properly ? Answer: MACRO INLINING We will replace RmaxBJ with a macro. Ah this is simple you think all we have to do is transform this: JASS:function RMaxBJ takes real a, real b returns real if (a < b) then return b else return a endif endfunction into this: JASS://! textmacro RMAXBJ takes RETURN, A, B if ($A$ < $B$) then set $RETURN$ = $B$ else set $RETURN$ = $A$ endif //! endtextmacro WRONG AGAIN! if you try to use RMAXBJ macro you will find out that you have to do this: JASS:
local real blackHeight
//! runtextmacro RMAXBJ("blackHeight", "GetUnitFlyHeight(reddragon)", "GetUnitFlyHeight(greendragon)")
call SetUnitFlyHeight(blackdragon, blackHeight, 0.0)
Ok that is not so bad you think i declare one local variable instead of two, but if you look how the RMAXBJ macro expands the code you will see this: JASS:
local real blackHeight
if (GetUnitFlyHeight(greendragon) < GetUnitFlyHeight(reddragon)) then
set blackHeight = GetUnitFlyHeight(reddragon)
else
set blackHeight = GetUnitFlyHeight(greendragon)
endif
call SetUnitFlyHeight(blackdragon, blackHeight, 0.0)
Shit, we call functions 2 times, it is even worse than before, and that local variable declaration is just a nuisance. Ok that is it macros are of no help with this. But wait there might be a solution, what if you try macro like this: JASS://! textmacro RMAX takes A, B set ParamA = $A$ set ParamB = $B$ if (ParamA < ParamB) then set RMAX = ParamB else set RMAX = ParamA endif //! endtextmacro globals real ParamA real ParamB real RMAX endglobals Of course, macro parameters should have their own variables. But this is dangerous if we want to put this macro in our library and use it in more triggers because multiple triggers can mess with that global parameters so we will do this: JASS:globals private real ParamA private real ParamB private real RMAX endglobals so we need to put this into every trigger that is going to use RMAX? mmm ok. (read on) So how do we actually use RMAX? simple: JASS:
//! runtextmacro RMAX("GetUnitFlyHeight(reddragon)", "GetUnitFlyHeight(greendragon)")
call SetUnitFlyHeight(blackdragon, RMAX, 0.0)
Hay that is not bad at all, 2 lines of code and it even expands ok: JASS:
set ParamA = GetUnitFlyHeight(reddragon)
set ParamB = GetUnitFlyHeight(greendragon)
if (ParamA < ParamB) then
set RMAX = ParamB
else
set RMAX = ParamA
endif
call SetUnitFlyHeight(blackdragon, RMAX, 0.0)
Cool, this is good. But putting that globals into every trigger when we want to use RMAX is boring it is all copy-paste and stuff but it is still boring. And what if I inline function that has 7 params? wouldn't I be bloating my code with param globals now? Actually no, all you need is this: JASS://! textmacro DeclareRMAX private real ParamA private real ParamB private real RMAX //! endtextmacro // and you call that macro like this: scope SomeSpell globals //! runtextmacro DeclareRMAX() endglobals Let us resume on our work here: For every function you want to inline you need 2 macros. One to define parameters and one to replace function. Having real macro parameters is extremely important because it prevents multiple function call and ensures type safety after all. It is also good practise to name the return parameter the same as macro ALWAYS declare macro parameters as private! JASS://! textmacro DeclareRMAX private real ParamA private real ParamB private real RMAX //! endtextmacro //! textmacro RMAX takes A, B set ParamA = $A$ set ParamB = $B$ if (ParamA < ParamB) then set RMAX = ParamB else set RMAX = ParamA endif //! endtextmacro When you want to use your macro in some trigger you need to declare it: JASS:globals //! runtextmacro DeclareRMAX() endglobals and then simply use it as many times as you like: JASS:
//! runtextmacro RMAX("GetUnitFlyHeight(reddragon)", "GetUnitFlyHeight(greendragon)")
call SetUnitFlyHeight(blackdragon, RMAX, 0.0)
Now this might all seem like a lot of work, but it is not much work when compared to standard inlining, and if you plan to inline same function on lots of places it is definitely worth it. Benefits are cleaner, smaller and faster code. |
| 08-25-2007, 03:03 PM | #2 |
That's rather interesting. I may try some of that the next time I go a'coding. |
| 08-25-2007, 03:35 PM | #3 |
although it works, I am not sure if it is a proper way, in a sense that it is kind of awful, I am seriously thinking of implementing Zoxc's defines in vjass, I know pipedream would kill me, but still |
| 08-25-2007, 04:01 PM | #4 |
Well given the syntax the vjass currently has this is the only way. When you improve macros I will think of another :P Btw macros are a dangerous species, pipedream is probably right. In some programming companies macros are even forbidden to use. Besides I have the feeling that all we ever do is make workarounds around blizzards bugs ![]() |
| 09-12-2007, 05:50 PM | #5 |
Guess what? *approved* |
| 09-13-2007, 03:12 PM | #6 |
Or if your that worried about it you could just copy the bj function and just make it another function named RMax and just use it from now on. You know, so you can fit in with the cool kids who hate BJ's. |
| 09-13-2007, 04:07 PM | #7 |
I am not one of those kids, I absolutely hate unnecessary optimizing. This inlining is meant to be used extremely rarely, preferably only in systems and only in performance bottlenecks. |
| 09-13-2007, 09:22 PM | #8 |
who is textmacro evil... |
| 09-13-2007, 09:35 PM | #9 | |
Quote:
|
| 03-28-2008, 06:21 PM | #10 |
You have a serious error. If you call RMAX() on a function within the same library that also calls RMAX you will change the private global parameters. That means if the second parameter is a function call it may overwrite the first parameter before you compare them. For example JASS:function F2 takes nothing returns real //changes Param A from 3 to 2, losing the 3 //! runtextmacro RMAX(2, 1) return RMAX //== 2 endfunction //should return 3, but returns 2 function F1 takes nothing returns real //sets Param A to 3, then calls F2, which overwrites A to 2 //Then F2 returns and you essentially get max(2, 2) //! runtextmacro RMAX(3, F2()) return RMAX //== 2, but should be 3 endfunction You can't inline a recursive function with arguments without some sort of stack unless the arguments are no longer needed when the call is made. (My example recursed after A was set, but before the comparison, so we need a stack). Note that we could also remove the possibility of recursion by making the arguments local. JASS://! textmacro DeclareRMAX private real array RMAX_ParamA private real array RMAX_ParamB private real RMAX private integer RMAX_count //! endtextmacro //! textmacro RMAX takes A, B set RMAX_count = RMAX_count + 1 set RMAX_ParamA[RMAX_count] = $A$ set RMAX_ParamB[RMAX_count] = $B$ if RMAX_ParamA[RMAX_count] < RMAX_ParamB[RMAX_count] then set RMAX = RMAX_ParamA[RMAX_count] else set RMAX = RMAX_ParamB[RMAX_count] endif set RMAX_count = RMAX_count - 1 //! endtextma Frankly I prefer just using RMaxBJ. Looking at the above code, it does everything a function call does except jump. Some time could be saved by totally removing RMAX_ParamB and just using RMAX: JASS://! textmacro DeclareRMAX private real array RMAX_Param private real RMAX private integer RMAX_count //! endtextmacro //! textmacro RMAX takes A, B set RMAX_count = RMAX_count + 1 set RMAX_Param[RMAX_count] = $A$ set RMAX = $B$ if RMAX_Param[RMAX_count] > RMAX then set RMAX = RMAX_Param[RMAX_count] endif set RMAX_count = RMAX_count - 1 //! endtextma |
| 04-06-2009, 12:56 PM | #11 |
i don't understand why a lot of tutorial users don't make test maps...most tutorials i almost get the point and i would understand completely if they attached a test map to the tutorial... |
| 04-06-2009, 03:27 PM | #12 |
The real question is how on earth would a test map help understanding this tutorial. |
