• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Things to know in jass

Status
Not open for further replies.
Level 17
Joined
Apr 27, 2008
Messages
2,455
All experimented "jassers" know that jass is tricky in many ways, there are things which are not obvious, or even senseless.
This thread is an attempt to list all these special behaviors, this way new users (there are still one from time to time which was in his cave and suddenly go out and try jass for no real reason) won't be trapped.
Note that i personnaly can't test anymore now, ofc there is still the possibility that i reinstall wc3 but that's really unlikely.
So i will say just what i'm 100 % sure.
Ofc feel free to contribute and take any, all, or nothing of my words in this thread to create a more elaborated thread.
If you want contribute, please use numbers like i did, for now the numbers are just to list the things, it's totally random, it isn't sorted depends the matter.

There is also this interesting thread : http://www.hiveworkshop.com/forums/...n-j-natives-do-nothing-useless-broken-199627/

Here we go.

1) boolexprs

Boolexprs created with the functions Condition and Filter are created only if it was not already created, if the boolexpr was already created then it points on this one.
In other words :
Filter(function F1) == Condition(function F1)
Yes, you can use either Filter Or Condition, that doesn't matter, i personnaly prefer Filter, just because it's faster to write and this word is generic.

Which also means that most of times DestroyBoolExpr is useless.

Now, a boolexpr created with the functions And and Or is always a new one.

2) texttags

You just can't create more than 100 texttags simultaneously.
The id of a texttag follows a lame convention, the first texttag will have the id "99", and the last one "0", but obviously a null texttag will also have the id "0".
So unless you handle your own counter you can't use GetHandleId to know if you've reached this limit or not (well in fact you can but it's a bit more complicated).

If you create more than 100 texttags, the one with the id "0" will be erased, let's say the previous one.
But good news, despite many other handles which will cause a desync if you create/destroy them within a local block (GetLocalPlayer), you can safely create a texttag in a local block, this way you will reach later or even never this hardcoded limit.

3) variables

- if you use a not defined variable (without any value), it will crash the thread (like if a return was used).

- arrays' size is dynamic, you don't have to handle that. And unlike not array variables you don't have to give a value before using the variable, it will have the default value of the type, such as "false" for boolean.
The index available starts to 0 and ends to 8191, however if you use the index 8191 of any variable you won't be able to load a saved game (yes it's a bug which was never fixed)

- "null" can be used for every type of variable.
For every variable which is not an handle, it will give the default value of the type (excepted string), boolean -> "false", integer and real -> "0".
And for string, null means null, no string and not "".

4) memory handle id leak


Generally, handles and types which are derived of handles have a reference counter.
There are exceptions though, such as texttags and images.
The handle id is recycled only when there is no more variable which points on it, or "immediatly" (maybe it's still delayed, i have not checked it) after the handle was destroyed if there is no reference counter.
For a local variable we would expect that this reference counter is smart enough to handle them correcty, it isn't.
If you don't null them, the handle id will never be recycled, you have to null the local variable.
So, as you can see, the GUI function PolledWait (as an example) leaks an handle id each time it's used, we could also talk about its inaccuracy because of TriggerSleepAction usage, but that's a whole new story ...

However, function arguments (function Test takes ...) are not concerned by this bug, the reference counter handle them correctly.

5) memory leaks


Jass lacks of destructors, for example there is no way to remove a trigger event, a trackable, ...
So sometimes you just can't avoid a memory leak.

We would also expect that destroy a trigger would also destroy everything of it (events, conditions, actions), but it doesn't, or at least it doesn't remove actions, you have to use TriggerRemoveAction before DestroyTrigger.
TriggerClearActions only "deactivate" actions but doesn't clean up the memory at all.
For some reasons you don't have to worry with trigger conditions, it's one of the reason why some people don't use trigger actions but only trigger conditions.
I don't know about events, some people will say that the memory is cleaned up when the trigger is destroyed but the fact is that i've never seen a proof of it, and have never tested myself, so it could just be bullshit as well.

6) limit op

There is a safeguard in jass, after a certain number of operations, the jass code is just ignored, the thread crash.
PipeDream said:
When you load a map, warcraft reads the jass file from disk and compiles it to a bytecode format. Bytecode ops are things like allocate a variable, multiply two registers, or call a native. A typical line of jass is 1-6 ops. Each instance of the virtual machine (new one on event, TriggerExecute, TriggerEvaluate, ExecuteFunc, TriggerSleepAction) has a limit at 300000 ops of continuous execution, at which point the VM returns.

1.21 and earlier versions of war3err can trace bytecode
1.22 and later versions of war3err can dump a function's bytecode with
JASS:
    call Cheat("GetBytecode funcname")
Sample chunk of executing bytecode:
Code:
bench :: 0 4:integer 217 0xe:read 3796:sw
bench :: 0 0 217 0x13:push 0
bench :: 0 0 0 0x15:callnative 2070:StopWatchMark
bench :: 0 0 0 0x11:set 3798:t1
bench :: 0 4:integer 218 0xc:literal 1000
bench :: 0 0 218 0x17:typecast 0
bench :: 0 0 218 0x13:push 0
bench :: 0 5:real 219 0xe:read 3798:t1
bench :: 0 0 219 0x13:push 0
bench :: 0 5:real 220 0xe:read 3797:t0
bench :: 0 0 221 0x14:setreg 0
bench :: 220 221 221 0x21:subtract 0
bench :: 0 0 222 0x14:setreg 0
bench :: 221 222 222 0x22:multiply 0
bench :: 0 0 222 0x13:push 0
bench :: 0 0 0 0x15:callnative 596:R2S
bench :: 0 0 0 0x13:push 0
bench :: 0 0 0 0x16:call 2547:BJDebugMsg
BJDebugMsg :: 0 1 6:string 0x8:pop 2077:msg
BJDebugMsg :: 0 0 4:integer 0x5:local 92:i
BJDebugMsg :: 0 4:integer 186 0xc:literal 0
BJDebugMsg :: 0 0 186 0x11:set 92:i
BJDebugMsg :: 0 0 0 0x28:jump target 1
BJDebugMsg :: 0 4:integer 187 0xe:read 92:i
The (full?) list of ops:
Code:
enum OPCODES { OP_ENDPROGRAM=0x1,
    OP_FUNCTION=0x3, // _ _ rettype funcname
    OP_ENDFUNCTION=0x4,
    OP_LOCAL=0x5, // _ _ type name
    OP_GLOBAL=0x6,OP_CONSTANT=0x7,
    OP_POPFUNCARG=0x8, // _ srcargi type destvar
    OP_CLEANSTACK=0xB, // _ _ nargs _
    OP_LITERAL=0xC, // _ type destreg srcvalue
    OP_SETRET=0xD, // _ srcreg _ _
    OP_GETVAR=0xE, // _ type destreg srcvar
    OP_CODE=0xF,
    OP_GETARRAY=0x10,
    OP_SETVAR=0x11,    // _ _ srcreg destvar
    OP_SETARRAY=0x12,
    OP_PUSH=0x13, // _ _ srcreg _
    OP_SETRIGHT=0x14,
    OP_NATIVE=0x15, // _ _ _ fn
    OP_JASSCALL=0x16, // _ _ _ fn
    OP_I2R=0x17,
    OP_AND = 0x18,
    OP_OR = 0x19,
    OP_EQUAL=0x1A,
    OP_NOTEQUAL=0x1B,        // check
    OP_LESSEREQUAL=0x1C,OP_GREATEREQUAL=0x1D,
    OP_LESSER=0x1E,OP_GREATER=0x1F,
    OP_ADD=0x20,OP_SUB,OP_MUL,OP_DIV,
    OP_MODULO = 0x24,              // unused
    OP_NEGATE=0x25,
    OP_NOT = 0x26,
    OP_RETURN=0x27,    // _ _ _ _
    OP_JUMPTARGET=0x28,
    OP_JUMPIFTRUE=0x29,OP_JUMPIFFALSE=0x2A,
    OP_JUMP=0x2B
}
The binary bytecode format:
Code:
typedef struct opcode {
    unsigned char r1,r2,r3;  // register arguments and types
    unsigned char optype;  // one of OPCODES
    int arg;  // values, targets, names
} opcode;


In addition of what PipeDream said every native function which takes a code or boolexpr, such as ForForce, or ForceEnumPlayers reset the limit op, same for trigger conditions and so one.

7) Strings

Check there : http://www.hiveworkshop.com/forums/lab-715/documentation-string-type-240473/

8) StringHash

This is not case sensitive, StringHash("aBcD") == StringHash("abcd")
And it doesn't make a difference between "/" and "\\", StringHash("a/b") == StringHash(a\\b).

9) Gamecache and hashtable

- There is an hardcoded limit of 256, if you try to have more simultaneously, the init function will return null (InitHashtable, InitGameCache).
- The keys of a gamecache are not case sensitive.
- You can't store "null" inside them (well remember about what null means for boolean, integer and real, for these ones you can).
- Same for "ghost" handles, (the handle is destroyed but the variable is not nulled).
And then you can use the boolean of the native Save hashtable functions to know if an handle is valid (not null, neither "ghost").

.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
2) texttags

You just can't display (and probably even create : a test is needed i don't remember) more than 100 texttags simultaneously.
If you display (create ?) more, a (random ? : a test is needed) one will be erased.
You can safely create a texttag in a local block (GetLocalPlayer), this way you will reach later or even never this hardcoded limit.

Ive tested it right now, if I created 100 texttags, the newest had handle id of 0, and if I created 101st it had 0 handle id as well, I didnt show them just create them but it shows if you create more then 100 texttags the newest one(with handle id of 0) will get recycled
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
yes, I just checked again because I didnt test for that, and I ran

JASS:
library lib initializer i

    private function aaa takes nothing returns nothing
        local texttag t = CreateTextTag()
    endfunction

    private function f takes nothing returns nothing
        call DestroyTimer(GetExpiredTimer())
        call TimerStart(CreateTimer(), 0.00, true, function aaa)
    endfunction
    
    private function i takes nothing returns nothing
        call TimerStart(CreateTimer(), 1.00, false, function f)
    endfunction
    
endlibrary

on empty map and the result was increasment of memory from 98,8MB to around 101MB and it stayed there so it seems they are recycling each other in memory as well
(the first 1 sec timer is just so that it wouldnt lagg when the map finishes loading, but it seems its not that heavy call and I still kept around 15-20 fps
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Right now i'm just writing it like that (it's the lab after all), but plz people stop say that trigger conditions don't reset the limit op, they do !
So no they are not faster than trigger actions because they don't open a new thread, they do !
(Well, at this last i could be wrong, but my point is that trigger conditions actually reset the limit op. And jass doesn't act like it's "multi threaded" anyway, one "thread" at a time.
I've already said it in fact in the first post, but since i've found recently the opposite, let's repeat)

And yes you can't use some functions within them, such as TriggerSleepAction, for ... an obvious reason, right ?
But probably some other ones too, like PauseGame, last time i tried.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Heh that's a quite good name imho, now we just need to hijack every brain of jass makers.

thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream
thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream
thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream
thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream
thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream
thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream, thread = stream

That should work better with some silly music on the background.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
How about adding the interrupt-like behavior of "threads" in jass, I think that's worth to know. Threads or Stream, I'll call them interrupts.
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
Thanks man! Though I think it's too late, but since people are still modding warcraft, I guess it should still be relevant.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
And i suppose the memory available (ram) was enough ? Yes i know that when wc3 runs out of memory, it typically crashes with a message error, but i had to ask it.

And what about the number of jass code lines ? Is there any limit ?
Remember that pjass has its own limit due to his implementation.
 
Status
Not open for further replies.
Top