• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Custom wait

Status
Not open for further replies.
Level 17
Joined
Apr 27, 2008
Messages
2,455
WARNING : THIS IS NOT
FOR SPEEDFREAKS


In jass, the only way to have an accurate and not "random" wait is to play with timers, but then it splits your code and it's quite boring.
Nowadays, we have tools which can play nicely with the JNGP including pre and post script processing.
I come with this idea, create a tool that will convert your waits in timers calls for you.

It will convert local variables in struct/table.
Because a script example is better than words, let's write one :

JASS:
library Test requires Wait

    private function Test takes nothing returns nothing
        local integer i = 9
        local string array s
        local boolean array b
    
        set s[0]="10"
        set s[1]="20"
        set i = 1
        set b[0] = false
        return
        call BJDebugMsg("wait1")
        call Wait(1)
        call BJDebugMsg("end of wait1")
        set i = 0
        set s[0]="100"
        set s[1]="200"
        loop
            call BJDebugMsg("wait2")
            call Wait(2)
            call BJDebugMsg("end of wait2")
            set s[1]="42"
        exitwhen i == 10
        set i = i+1
            call BJDebugMsg("wait3")
            call Wait(3)
            call BJDebugMsg("end of wait3")
            set s[0]="1337"
        exitwhen b[0]
        call BJDebugMsg("end of loop")
        endloop
        call BJDebugMsg("end of function")
    endfunction

endlibrary
should be converted in :

JASS:
library Test requires Wait

    private struct s_wait
        static thistype last
        timer tim
        integer i
        Table s
        Table b
        
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
            set this.tim = NewTimerEx(this)
            set this.s = Table.create()
            set this.b = Table.create()
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            call ReleaseTimer(this.tim)
            call this.s.destroy()
            call this.b.destroy()
            call this.deallocate()
        endmethod
    endstruct
    
    private function Test__loop1_end takes nothing returns nothing
       call BJDebugMsg("end of function")
       call s_wait.last.destroy()
    endfunction
    
    private function Test__loop1_2 takes nothing returns nothing
       set s_wait.last = s_wait(GetTimerData(GetExpiredTimer()))
       call BJDebugMsg("end of wait3")
       set s_wait.last.tab.s.string[0]="1337"
       if s_wait.last.b.boolean[0] then
          call Test__loop1_end()
          return
       endif
       call BJDebugMsg("end of loop")
       call Test__loop1_end()
    endfunction
    
    private function Test__loop1_1 takes nothing returns nothing
        set s_wait.last = s_wait(GetTimerData(GetExpiredTimer()))
        call BJDebugMsg("end of wait2")
        set s_wait.last.s.string[1]="42"
        if s_wait.last.i == 10 then
            call Test__loop1_end()
            return
        endif
        set s_wait.last.i = s_wait.last.i+1
        call BJDebugMsg("wait3")
        call TimerStart(s_wait.last.tim,3,false,function Test__loop1_2)
    endfunction

    private function Test__loop1_begin takes nothing returns nothing
        call BJDebugMsg("wait2")
        call TimerStart(s_wait.last.tim,2,false,function Test__loop1_1)
    endfunction
    
    private function Test__1 takes nothing returns nothing
        set s_wait.last = s_wait(GetTimerData(GetExpiredTimer()))
        
        call BJDebugMsg("end of wait1")
        set s_wait.last.i = 0
        set s_wait.last.s.string[0]="100"
        set s_wait.last.s.string[1]="200"
        // split the loop in several functions, begin, end and each time there is a wait
        call Test__loop1_begin()
    endfunction
    
    private function Test takes nothing returns nothing
        call s_wait.create() //creation of the struct, the timer, the table
        
        set s_wait.last.s.string[0]="10"
        set s_wait.last.s.string[1]="20"
        set s_wait.last.i = 1
        set s_wait.last.b.boolean[0]=false
        // return so destroy
        call s_wait.last.destroy()
        return
        call BJDebugMsg("wait1")
        // first wait
        call TimerStart(s_wait.last.tim,1,false,function Test__1)
    endfunction

endlibrary
In the first step, you should import and requires this library when you want to use a custom wait :

JASS:
library Wait requires Table,TimerUtils
    
    function Wait takes real whichTime returns nothing
    endfunction
    
endlibrary
This way, we let pjass check if the code is valid and then we do the script post-processing with some tool.

Limitations and cons :

- a thread crash will cause leaks, because struct instances, Table and timer won't be released.
However we still can do some safety check to release lost struct instances and its Table and timer.
Because there is a timer linked to a struct instance and we could check if it's in use or not.
But i don't think it does worth it, because you already have a problem if there is a thread crash in your code.

- you can't use local array variable types that can't be store inside an hashtable, because of Table usage.
Like code array, oh wait, well ... xD

- you can't use it inside a function that returns something, for an obvious reason.
But maybe i suppose we could make an exception for a function that returns a boolean since trigger conditions are often used as a trigger action.

So, what do you think about that ?
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
That is so beautiful and ugly at the same time that I can't even contain myself.

Haha, it's because vJass is in use there :grin:

I like the idea, and it could be really nice for things like cinematics (where you'll need specific timing very often).
Yep, or just when efficiency is not critical.
And well, even if it is, that's just some bunch of extra functions calls after all.

I really want this :3

But i suppose we should dream some time for a new patch coming with a such native function maybe ?

And well anyway for doing that i have to learn a "real" language before, like python.
So if someone want to do it, just say it, i have no problem with that.
But please just say it here, i don't want to begin it for nothing.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
I don't think it would be too usefull but it would be very very cool.

I also already had a post typed out about haskell, monads, closures and all that good stuff but it doesn't matter too much if you only want waits...
But if you ever compile real closures though come back and we will do waits the right way :alol:
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
I don't think it would be too usefull but it would be very very cool.

Still more usefull than some resources here :xxd:
Seriously, that is one of the thing that always really pissed me off in jass.
This and groups (no straight forward method to loop through it if you need to keep units during time without splitting the code with ForGroup)
RIP UnitLL by the way :p

I also already had a post typed out about haskell, monads, closures and all that good stuff but it doesn't matter too much if you only want waits...
But if you ever compile real closures though come back and we will do waits the right way :alol:
The right way ? You mean natively in (v)Jass, directly on the vJass preprocessor ?
Sure, it would be better, but harder.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,188
Use StarCraft II where waits work properly?

Waits in conditions should throw a compile time error as they will just never work. People need to stop abusing conditions unless there is an actual performance problem. Conditions are not actions, they are meant to return if the actions should be run or not.

The use of string hash on variable names could cause bugs due to collisions. Specifically "S" and "s" and all such case changes will collide as string hash is case insensitive. Since this is compile time you can compute a unique constant for each variable which will not collide (vJASS even has a keyword for such constants).

Local array support would likely need array size declarations to work. Some arrays the size could be inferred from how they are used as long as the indices used are static.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Use StarCraft II where waits work properly?

Really, stop this, i don't like SC2 modding, that's all, even if i know it's much more powerfull.

Waits in conditions should throw a compile time error as they will just never work. People need to stop abusing conditions unless there is an actual performance problem. Conditions are not actions, they are meant to return if the actions should be run or not.
I agree, but conditions are usefull when you need dynamic triggers.
I'm quite sure you know why.

The use of string hash on variable names could cause bugs due to collisions. Specifically "S" and "s" and all such case changes will collide as string hash is case insensitive. Since this is compile time you can compute a unique constant for each variable which will not collide (vJASS even has a keyword for such constants).
I highly doubt you will ever have a collision.
But it's still more safe and just better anyway, so ok for keys.
EDIT : Nevermind, one Table for each array variable and that's enough.

Local array support would likely need array size declarations to work. Some arrays the size could be inferred from how they are used as long as the indices used are static.
That will make it less friendly user. I'm not sure it does worth it.

EDIT : One Table for each array variable and i don't need to care about the size.
 
Last edited:
Still more usefull than some resources here :xxd:
Seriously, that is one of the thing that always really pissed me off in jass.
This and groups (no straight forward method to loop through it if you need to keep units during time without splitting the code with ForGroup)
RIP UnitLL by the way :p
Yup,thanks for this.
Sharing is caring. ;D
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
When Troll-Brain ascends to J4L h4xx but instead breaks JNGP

I love you

I also love myself.

This probably wins the price for the most disfigured abomination of a library we ever had on Hive. :D

Oh you still have a doubt, because you don't have see the result with nested loops and if/elseif/else/then inside them.
I probably have to do some .evaluate(), or ExecuteFunc, or ForForce, or whatever else shitty stuff. :thumbs_up:

Yup,thanks for this.
Sharing is caring. ;D

It is fun at least.
 
Last edited:
Level 23
Joined
Apr 16, 2012
Messages
4,041
- you can't use it inside a function that returns something, for an obvious reason.
But maybe i suppose we could make an exception for a function that returns a boolean since trigger conditions are often used as a trigger action.

Not obvious at all to me, why is this?

This is valid Jass(maybe not vJass with pJass 1.0k or lower, but on the newest one this is fixed afaik, since I pushed for this in the pJass thread):

JASS:
function f takes nothing returns unit
    call BJDebugMsg("Hey")
    return CreateUnit(...)
endfunction

function w takes nothing returns nothing
    call TimerStart(CreateTimer(), 0., false, function f)
endfunction

This happily prints Hey and creates the unit(presumably, I tried with null and it worked no problem)

Edit: Response to your latest post, the wait seems interesting, but what are the requirements on locals preservations?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Your example is silly, since you don't use the return.
You can use a TriggerSleepAction inside a function that returns something because the thread is halt, but that won't be the case with the timer.

Latest post, you mean the if/then/elseif structure ?
That will the same stuff for locals, it's just a different approach, use cases instead of functions.
That will be less efficient but easier to make and less bloat code.
 
Level 13
Joined
Nov 7, 2014
Messages
571
No Table dependency please!
Would you hand code a spell with a table or with struct members, for example?
The point of all this should be to get a readability improvement over timers (which require splitting of the code),
but still be as fast as hand coded "struct spells".

However there is no need of Timer(0)

I guess so...
JASS:
library Test requires Wait

function Test takes integer j returns nothing
    local integer i = 8 + j
    local string array s/*[2]*/ // local array variables could be forbidden or would require a constant size
    local boolean array b/*[2]*/

    set s[0]="10"
    set s[1]="20"
    set i = 1
    set b[0] = false

    call BJDebugMsg("wait1")

    call Wait(1)

    call BJDebugMsg("end of wait1")

    set i = 0
    set s[0]="100"
    set s[1]="200"
    loop
        call BJDebugMsg("wait2")

        call Wait(2)

        call BJDebugMsg("end of wait2")
        set s[1]="42"
        exitwhen i == 10
        set i = i+1
        call BJDebugMsg("wait3")

        call Wait(3)

        call BJDebugMsg("end of wait3")
        set s[0]="1337"
        exitwhen b[0]
        call BJDebugMsg("end of loop")
    endloop

    call BJDebugMsg("end of function")
endfunction

endlibrary


very dreadfully hand converted to:
JASS:
library Wait requires TimerUtils
endlibrary

library Test requires Wait

struct Test_Locals
    // all locals from function Test become struct members
    integer i
    string array s[2]
    boolean array b[2]

    // keeping track of how far we've gone into the function
    integer state
endstruct

function Test_loop takes nothing returns nothing
    local timer t = GetExpiredTimer() // recycled, nulling is pointless
    local Test_Locals c = GetTimerData(t)

loop // gets rid of TimerStart(t, 0, false, Test_Loop) calls
loop // by breaking/"exitwhen true" from the nested loop and jumping to the "next" state


if c.state == 1 then

    call BJDebugMsg("end of wait1" + " (" + I2S(c) + ")")

    set c.i = 0
    set c.s[0] = "100"
    set c.s[1] = "200"

    set c.state = 2
    call TimerStart(t, 1, false, function Test_loop)
    return

elseif c.state == 2 then // loop begins at state == 2

    call BJDebugMsg("wait2" + " (" + I2S(c) + ")")

    set c.state = 3
    call TimerStart(t, 2, false, function Test_loop)
    return

elseif c.state == 3 then

    call BJDebugMsg("end of wait2" + " (" + I2S(c) + ")")
    set c.s[1] = "42"

    if c.i == 10 then
        set c.state = 5
        exitwhen true
    endif

    set c.i = c.i + 1
    call BJDebugMsg("wait3" + " (" + I2S(c) + ")")

    set c.state = 4
    call TimerStart(t, 3, false, function Test_loop)
    return

elseif c.state == 4 then

    call BJDebugMsg("end of wait3" + " (" + I2S(c) + ")")
    set c.s[0] = "1337"

    if c.b[0] then
        set c.state = 5
        exitwhen true
    endif

    call BJDebugMsg("end of loop" + " (" + I2S(c) + ")")

    set c.state = 2
    exitwhen true

elseif c.state == 5 then // loop ends at state == 5

    call BJDebugMsg("end of function" + " (" + I2S(c) + ")")

    call ReleaseTimer(t) // cleanup
    call c.destroy()
    return

endif


endloop
endloop

endfunction

function Test takes integer j returns nothing
    // everything before the first wait goes here, aka "setup"
    local Test_Locals c = Test_Locals.create()
    set c.i = 8 + j

    set c.s[0] = "10"
    set c.s[1] = "20"
    set c.i = 1
    set c.b[0] = false

    call BJDebugMsg("wait1" + " (" + I2S(c) + ")")

    set c.state = 1
    call TimerStart(NewTimerEx(c), 1, false, function Test_loop)
endfunction

endlibrary

library Foo initializer init requires Test

function call_Test takes nothing returns nothing
    call Test(/*j: */ 1) // doesn't matter what j is because of "silly" example =)
    call Test(1)
endfunction

function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterPlayerEventEndCinematic(t, Player(0))
    call TriggerAddAction(t, function call_Test)
    // call TimerStart(CreateTimer(), 0, false, function call_Test)
endfunction

endlibrary
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
what if I use variable indicies(something like input from user), or something like 0, 1, 547, 3314, 7888, then you gonna do string array[7889]? or string array[5] and then mapping function? Thats still slower than Table
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Aniki said:
No Table dependency please!
And why not ? It's a pretty standard library.

Would you hand code a spell with a table or with struct members, for example?
Yes, i would.

The point of all this should be to get a readability improvement over timers (which require splitting of the code),
Exactly.

but still be as fast as hand coded "struct spells".
Not necessary, optimizations could come later but really that doesn't matter that much.
And if optimizations are coming together with limitations (as in your example of sized array, then it's a big NO)

Btw do you realize that the case approach is less efficient than my function approach ?
(Until i will need some shitty stuff like .evaluate(), ExecuteFunc, ForForce or whatever else)
I mean i don't care that much about efficiency here.
Hell, read the very first line of this thread.

I will use the case approach just because it's easier to handle and generate less bloat code.
Nice trick the loop btw, instead of calling again the function.
 
Status
Not open for further replies.
Top