HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

How to properly inline stuff - advanced JASS

08-25-2007, 04:24 AM#1
cohadar
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:
Collapse 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:

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

Collapse 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:
Collapse 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:
Collapse JASS:
function RMaxBJ takes real a, real b returns real
    if (a < b) then
        return b
    else
        return a
    endif
endfunction

into this:
Collapse 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:
Collapse 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:
Collapse 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:
Collapse 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:

Collapse 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:
Collapse 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:
Collapse 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:
Collapse 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!

Collapse 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:
Collapse JASS:
globals
    //! runtextmacro DeclareRMAX()
endglobals

and then simply use it as many times as you like:
Collapse 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
Here-b-Trollz
That's rather interesting. I may try some of that the next time I go a'coding.
08-25-2007, 03:35 PM#3
Vexorian
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
cohadar
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
PitzerMike
Guess what?
*approved*
09-13-2007, 03:12 PM#6
zergleb
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
cohadar
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
MaD[Lion]
who is textmacro evil...
09-13-2007, 09:35 PM#9
Vexorian
Quote:
Originally Posted by zergleb
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.
Using a function call for such a thing like Max is overkill.
03-28-2008, 06:21 PM#10
Strilanc
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
Collapse 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.

Collapse 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:
Collapse 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
SanKakU
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
Vexorian
The real question is how on earth would a test map help understanding this tutorial.