• 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.

Generic wait in JASS

Status
Not open for further replies.
Level 15
Joined
Aug 7, 2013
Messages
1,338
Hi,

How do I just do a generic wait in a JASS script?

Here's my function. I want it to wait 1.5 seconds before it fires off the rest of the code. I am super confused what's the best way to do this...

JASS:
    method moveToLoc takes location targetLoc returns nothing
        //wait 1.5 seconds
        //call Wait(1.5)
        call SetUnitPositionLoc(pc.u, targetLoc)
        call party.moveGroupToLoc(targetLoc)
        if GetLocalPlayer() == players[pid] then
            call PanCameraToTimed(GetLocationX(targetLoc), GetLocationY(targetLoc), 0)
        endif
    endmethod
 
"Best" way (using TimerUtils, but you can also use a hashtable and just use SaveInteger):
JASS:
    private location tempLoc

    private static method moveToLocDelayed takes nothing returns nothing 
        local thistype this = GetTimerData(GetExpiredTimer())

        /* Delayed Actions */
        call SetUnitPositionLoc(pc.u, .tempLoc)
        call party.moveGroupToLoc(.tempLoc)
        if GetLocalPlayer() == players[pid] then
            call PanCameraToTimed(GetLocationX(.tempLoc), GetLocationY(.tempLoc), 0)
        endif
  
        /* Cleanup */
        call ReleaseTimer(GetExpiredTimer())
    endmethod
 
    method moveToLoc takes location targetLoc returns nothing
        set .tempLoc = targetLoc
        call TimerStart(NewTimerEx(this), false, 1.5, function thistype.moveToLocDelayed)
    endmethod

Lazy way:
JASS:
    method moveToLoc takes location targetLoc returns nothing
        call TriggerSleepAction(1.5)
        call SetUnitPositionLoc(pc.u, targetLoc)
        call party.moveGroupToLoc(targetLoc)
        if GetLocalPlayer() == players[pid] then
            call PanCameraToTimed(GetLocationX(targetLoc), GetLocationY(targetLoc), 0)
        endif
    endmethod
 
Last edited:
Level 15
Joined
Aug 7, 2013
Messages
1,338
And why is there so much overhead for the "best way" when I just want to wait 1.5 seconds??

I've heard the mumbo jumbo about the waits being unreliable, but what could go wrong with the lazy wait?
 
And why is there so much overhead for the "best way" when I just want to wait 1.5 seconds??

Because Blizzard.

sethmachine said:
I've heard the mumbo jumbo about the waits being unreliable, but what could go wrong with the lazy wait?

Not much.
(1) It must be called from a trigger action (hence the name, TriggerSleepAction()), otherwise it will halt the thread. e.g. if you run the method from a condition, it will halt.
(2) The very rare but possible handle stack corruption (but it only happens in a similar scenario as the one in that post)
(3) It pauses the thread. This could be an advantage though. In general, it just means that this has a different behavior:
JASS:
call .moveToLoc(loc)
call BJDebugMsg("Hello World")
With timers, it would call .moveToLoc() and then display "Hello World" immediately afterward. With TriggerSleepAction(), it will wait 1.5 seconds before displaying "Hello World".
(4) Inaccuracy, but that is negligible with larger waits (e.g. waits > 1 second). Not sure about multiplayer though, I've heard TriggerSleepAction's behavior changes a bit with multiplayer.

But that is about it. I think the biggest things to worry about when deciding whether to use timers or TSA is problems (1) and (3).
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
I see. Wait does TriggerSleepAction() need to be run locally (in a local player block?)?

And these functions are not trigger actions (it's a method call, e.g. myStruct.method(args)), so TriggerSleepAction() wouldn't work then.

What about for recurring events (e.g. random encounters like in Pokemon). Can I get away for using a polled wait without needing the TimerUtils library?

e.g. see this post for the RealWait function: http://www.hiveworkshop.com/forums/world-editor-help-zone-98/does-function-work-waiting-246843/

Also what does that bug do? Does it just get an equality when it shouldn't (I would assume calling CreateGroup() == CreateGroup() would be false, since you create a new instance for each call, but apparently the bug causes it to evaluate to true, what use could this possibly have?).
 
I see. Wait does TriggerSleepAction() need to be run locally (in a local player block?)?

No, it does not need to be. Doing so would likely desync.

sethmachine said:
And these functions are not trigger actions (it's a method call, e.g. myStruct.method(args)), so TriggerSleepAction() wouldn't work then.

Well, it all just depends on where the "virtual thread" began. Perhaps this will clarify:
JASS:
function Subsubaction takes nothing returns nothing 
    call TriggerSleepAction(6)
endfunction

function Subaction takes nothing returns nothing 
    call TriggerSleepAction(5)
    call Subsubaction() // works
endfunction

function Act takes nothing returns nothing 
    call Subaction() // works
    call ForForce(bj_FORCE_PLAYER[0], function Subaction) // won't work
    call TimerStart(CreateTimer(), 0, false, function Subaction) // won't work
    call ForGroup(bj_lastCreatedGroup, function Subaction) // won't work
endfunction

function Init takes nothing returns nothing 
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t, function Act)
endfunction

By "won't work", I mean that it will halt the thread rather than sleeping. As long as the "virtual thread" starts from the trigger action, you'll be fine. Note that ForForce(), TimerStart(), ForGroup(), etc. start separate "threads", so TriggerSleepAction() won't work properly.

sethmachine said:
What about for recurring events (e.g. random encounters like in Pokemon). Can I get away for using a polled wait without needing the TimerUtils library?

That should be fine. Although, timers are perfect for recurring events (unless the wait is supposed to be random. still possible with timers, just less ideal).

sethmachine said:
Also what does that bug do? Does it just get an equality when it shouldn't (I would assume calling CreateGroup() == CreateGroup() would be false, since you create a new instance for each call, but apparently the bug causes it to evaluate to true, what use could this possibly have?).

I don't exactly remember the details. It would need to be tested. I know it'll basically f*ck up your map though. Data attachment that relies on GetHandleId() will be ruined, and your handles won't get proper ID's.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
I suppose you could use that bug as a failsafe to make a map unplayable? It seems pretty obscure and I doubt anyone would know what to look for in the war3map.j, "oh it's this bug." So even trying to remove it wouldn't be easy.

But why are timers not ideal for random waiting times?

I guess I'll stick with this RealWait stuff for now. The only problems I'll experience is if I am waiting under a second, otherwise there's no bad bug or glitch that could possibly happen, right?
 
If your code is structured correctly and accounts for the reality that you have a wait there, there shouldn't really be any problems... though if the system needs to be accurate, please stay away from waits...

as for random, well that means pausing then restarting the timer with another value if you randomize it after everytime it "expires"...
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
I tried using the lazy wait, it didn't do anything, even with a value of 10 seconds! I guess I am not calling it from a trigger?

Also purge I'm not sure I can use your "best way" yet, because the targetLoc is an argument, and I can't really make it a static field member, because it could come from anywhere (and is dynamically generated for each struct instance). And I really want to avoid 'searching' for arguments (a stupid restriction, why did they make it so trigger actions and conditions can't take arguments >_>).

Edit: The realwait doesn't work either--I guess because it's calling TriggerSleepAction.

JASS:
library RealWait initializer init
globals
    timer t_WAIT = CreateTimer()
endglobals

function RealWait takes real duration returns nothing
    local real time = TimerGetRemaining(t_WAIT)
    local real goal = time - duration
    loop
        exitwhen TimerGetRemaining(t_WAIT) < goal
        call TriggerSleepAction(bj_POLLED_WAIT_INTERVAL)
    endloop
endfunction

private function init takes nothing returns nothing
    call TimerStart(t_WAIT, 99999, false, null)
endfunction

endlibrary
 
Last edited:
Also purge I'm not sure I can use your "best way" yet, because the targetLoc is an argument, and I can't really make it a static field member, because it could come from anywhere (and is dynamically generated for each struct instance). And I really want to avoid 'searching' for arguments

oops, I made a mistake in my code. It shouldn't be static. I updated it.

sethmachine said:
(a stupid restriction, why did they make it so trigger actions and conditions can't take arguments >_>).

Because Blizzard.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
For some reason TriggerSleepAction breaks my code--it does pause, but the following actions never execute...

JASS:
        call TriggerSleepAction(1.0)
//these never get called...why does it break?
        call SetUnitPositionLoc(u, targetLoc)
        call party.moveGroupToLoc(targetLoc)
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,258
TriggerSleepAction has an unreliable delay. The actual time waited is highly inaccurate and will vary from session to session, especially multiplayer sessions. It is possible that it generates net traffic so is subject to round trip time (would explain why the same delay is longer in multiplayer than in single player and varies from session to session).

Your only option is to use timers, which themselves have other problems. WC3 is by no means SC2 where called waits actually do wait the specified time (to the precision of the game engine). Welcome to the hackery that is WC3 mapping.
 
For some reason TriggerSleepAction breaks my code--it does pause, but the following actions never execute...

JASS:
        call TriggerSleepAction(1.0)
//these never get called...why does it break?
        call SetUnitPositionLoc(u, targetLoc)
        call party.moveGroupToLoc(targetLoc)

The function you called it from didn't have wait permissions in the thread (actions have wait permissions, conditions do not)

A better reason why not to use waits against timers is because waits can't be used in conjunction with map pausing events like player lag dialogs and PauseGame()

Don't use waits.

A better solution in my opinion over Purge's suggestion would look like this:

JASS:
private struct Move
    real x
    real y
    unit u
    Party p
    integer id
endstruct

globals
    private HandleTable tab // Using Vexorian's Table
endglobals

    private static method after takes nothing returns nothing
        private timer time = GetExpiredTimer()
        private Move mv = tab[time]
        call DestroyTimer(time)
        call SetUnitPosition(mv.u, mv.x, mv.y)
        call p.moveGroupToLoc(targetLoc) // fix this. also don't use locations.
        if GetLocalPlayer() == players[mv.id] then
            call PanCameraToTimed(mv.x, mv.y, 0.)
        endif
        call mv.destroy()
        set time = null
    endmethod

    method moveToLoc takes location targetLoc returns nothing //good god never use locations
        local Move mv = Move.create()
        local timer time = CreateTimer()
        set mv.x=GetLocationX(targetLoc)
        set mv.y=GetLocatioNY(targetLoc)
        set mv.u = pc.u
        set mv.p = party
        set mv.id = pid
        set tab[time] = mv
        call TimerStart(time,1.5,false,function thistype.after)
        set time = null
    endmethod

static method onInit takes nothing returns nothing
    set tab = HandleTable.create()
endmethod
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
Questions that arose from the helpful discussion:

1) What can possibly go wrong with a timer (if it's global and I always want it on?)

2) What is wrong with locations (do they leak even if you call RemoveLocation())?
 
Questions that arose from the helpful discussion:

1) What can possibly go wrong with a timer (if it's global and I always want it on?)

Nothing really. They leak if you don't handle them, but that's the only thing I can think of.


2) What is wrong with locations (do they leak even if you call RemoveLocation())?

Locations just don't give you anything useful. (almost) every native which performs a job for you works with X,Y - the same cannot be said for locations unless you use GetLocationX,Y every single time (the only exception off the top of my head is GetLocationZ) Locations have a computational cost associated with creation, destruction, and nulling.

In other words, not only are they not "worth" it, but even if they were free and couldn't leak, the natives jass gives us just work better with coordinates anyway.
 
Level 15
Joined
Aug 7, 2013
Messages
1,338
In other words, not only are they not "worth" it, but even if they were free and couldn't leak, the natives jass gives us just work better with coordinates anyway.

Ah so locations are just (unnecessarily) computationally expensive compared to the other alternatives. But there aren't any gamebreaking bugs or leaks, e.g. RemoveLocation works correctly?
 
Status
Not open for further replies.
Top