• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

ExecuteSoon - A real-world use for bytecode arrays

Status
Not open for further replies.
Level 9
Joined
Jul 30, 2012
Messages
156
ExecuteSoon is a library that allows you to queue a function to be executed after a 0-second timer. This allows you to run some code after current fired events finish running, which is useful for many cases (like Damage triggers).

The idea of this snippet comes from thehelper days (Snippet - ExecuteSoon). However this is the first implementation that uses JASS Bytecode to call functions dinamically. This gives much more flexibility as you can use functions that take arguments of any type, and they will be called directly by the bytecode, with the data you provided as the argument.

Also, since all functions are called directly within a single VM, it should be faster than other implementations that use traditional methods for code execution.

Example Usage:

JASS:
function RemoveUnitSoonCallback takes unit u returns nothing
    call RemoveUnit(u)
endfunction

//This function removes a unit after a 0-second timer.
//# +nosemanticerror
function RemoveUnitSoon takes unit u returns nothing
    call ExecuteSoon(function RemoveUnitSoonCallback, GetHandleId(u))
endfunction
Notice that trying to use a function that takes parameters as a callback will throw a syntax error from pjass, so you currently need to annotate your function with the //# +nosemanticerror flag (@LeP maybe you could finally remove the cant-use-func-as-code check :p)


JASS:
library ExecuteSoon initializer onInit requires Memory

globals
    private timer t = CreateTimer()
    private integer Count = 2
    private integer Id
    private integer EndId
    private boolean running = false
endglobals

private struct BC extends array
    static constant boolean hasTrigger = true
    implement Bytecode
endstruct

private function ExecuteSoonBegin takes nothing returns nothing
    set running = true
    set BC[Count] = 0x16000000
    set BC[Count+1] = EndId
    set Count = Count+2
endfunction

private function ExecuteSoonEnd takes nothing returns nothing
    set running = false
    set BC[Count] = 0x27000000
    set Count = 2
endfunction

//Runs a function by its id, after a 0-second timer. Useful to use with BJs, as their ids are static.
function ExecuteSoonById takes integer funcid, integer data returns nothing
    set BC[Count] = 0x0C010400
    set BC[Count+1] = data
    if running then
       /*calling ExecuteSoon while it's already running should work as expected. The workaround
         is to make ExecuteSoon queue itself, so it runs again after all callbacks are called.*/
        set BC[Count+2] = 0x0C020400
        set BC[Count+3] = funcid
        set BC[Count+4] = 0x13020000
        set BC[Count+6] = 0x13010000
        set BC[Count+8] = 0x16000000
        set BC[Count+9] = Id
        set BC[Count+10] = 0x0B020000
        set Count = Count+12
    else
        set BC[Count+2] = 0x13010000
        set BC[Count+4] = 0x16000000
        set BC[Count+5] = funcid
        set BC[Count+6] = 0x0B010000
        set Count = Count+8
        call ResumeTimer(t)
    endif
endfunction

//Just an inline-friendly wrapper that gets the function id for you.
function ExecuteSoon takes code func, integer data returns nothing
    call ExecuteSoonById(Memory[C2I(func)/4-1], data)
endfunction

//# +nosemanticerror
private function onInit takes nothing returns nothing
    set BC[0] = 0x16000000
    set BC[1] = Memory[C2I(function ExecuteSoonBegin)/4-1]
    set Id = Memory[C2I(function ExecuteSoonById)/4-1]
    set EndId = Memory[C2I(function ExecuteSoonEnd)/4-1]
    call TimerStart(t, .0, false, null)
    call TriggerRegisterTimerExpireEvent(BC.trigger, t)
endfunction

endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
This is some very interesting snippet... I find it really hard to reason about =)... treating [byte]code as data + recursion is just nuts!

@LeP maybe you could finally remove the cant-use-func-as-code check
Agree, having to disable the '//# +nosemanticerror' annotation then comment out the calls to such functions, then compile to see if there were any mistakes, then reenable the annotation and uncomment the calls is a bit annoying.
 
Status
Not open for further replies.
Top