| 04-06-2009, 01:50 AM | #1 |
So, in regards to the Op limit (which is.. 8191?), is that how many individual actions can be done per thread, or what? If so, what defines the opening of a thread? For example, say (for whatever absurd reason) I had a function that, say, did 4 functions (let's say, SetUnitX/Y for 2 units), and then called a 5th function that had a loop which looped about 8189 times and called one function (like a TSA) every iteration, and then did some more stuff following the conclusion of that function. Would this hit the op limit? Or would the calling of the function (or the opening of the loop) start a new thread and reset the counter for the op limit? Sorry if this is "common knowledge". I know next-to-nothing about the workings of programming anything (such as threads). |
| 04-06-2009, 03:47 AM | #2 |
TSA is actually a common method to AVOID hitting the op-limit, as it puts the thread on sleep, so you reset the op-count upon picking it back up. As to what the actual 'op-limit' is, I'm not sure that that's every actually been tested out or verified. It doesn't really matter because most (<-- this word could actually be replaced with all, unless you're doing something stupid like leaving a loop running without an exitwhen, in which case you will crash no matter what, as opposed to exitwhen i==9999999999, which would likely hit the limit somewhere along the way, though technically it's possible to reach the end) functions will cause noticeable lag on-screen before they hit the op-limit and crash. |
| 04-06-2009, 05:46 AM | #3 |
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; |
| 04-06-2009, 11:06 AM | #4 |
Okay, so a TSA will actually reset the Op limit for a thread and I wouldn't have to worry about that? Neat. Thanks. And thanks PipeDream for the in-depth bytecode explanation that made no sense to me. =p |
| 04-10-2009, 10:06 PM | #5 |
TSA means ? |
| 04-10-2009, 11:26 PM | #6 |
TriggerSleepAction |
| 04-10-2009, 11:41 PM | #7 |
Hi, I'm just wondering: my map at its initialization (starts from 0s elapsed time) often hits the Op Limit, so I use PolledWait2 (Vex's PolledWait) then it is ok. But is TSA better than PolledWait in this case ? ( a native and a function ) (the "wait" is about 0.1) |
| 04-11-2009, 12:07 AM | #8 |
What pipe dream basically said is that one function can crash the op limit if it's obsurd (and will cause an overflow error or crash when compiling) but more to the point; each call to a single operator adds one to the op limit. (The operators as the op-codes he listed - which should be apparant as to what the jass-equivalent operator is) Moreover; you can't reach the thread limit without the use of a loop. It would require a massive chunk of code to reach without one; the only real use of the op-limit it to avoid infinite loops. |
| 04-11-2009, 04:31 AM | #9 |
Yes, TriggerSleepAction(0.0) is better than PolledWait/2 for that application. All other uses of TriggerSleepAction/PolledWait are best off as timers. |
| 04-11-2009, 10:17 AM | #10 |
Agreed here, waits will cause the code to run after map loading, while starting new threads with ExecuteFunc or .execute() gets everything done before the map starts, thus avoiding any map start lag. |
