• 🏆 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!

[Trigger] What to replace "Waits" with?

Status
Not open for further replies.
Level 8
Joined
Oct 25, 2018
Messages
68
I use action Wait a looot in my map. It was convenient and worked (so far), but I'm 99 I know they can cause issues if left like that. Triggers I use them in have no MUI application, ie; they only run once.
Still. I wanna get the knowledge to fix up current and apply to next triggers.
What do you replace your Waits with?
 
Level 18
Joined
Oct 17, 2012
Messages
820
@ColourWolfe Are you on Reforged or patch 1.31.1? If yes, you can just stick this handy snippet into your map. Make sure to change the script language for your map to Lua in Map Properties - Options.

For maps in development, copy your triggers (it is easy now for 1.31+. Just place everything under one folder and copy to clipboard) and then change script language. Afterwards, paste back the entire folder.

Then fire away in your comfort zone! You can now continue to use waits.
 
Last edited:

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
Simply don't replace it with anything. There has been this trend going on for a long time "waits are bad use timers", but most of them never tell you why it's bad on what context and what are its good uses.
Waits in a spell, or anything with the idea of MUI behind are 99% of the time bad.
Things you have to take into consideration: waits are mostly accurate, the shorter they are, the higher the inaccuracy, especially online it is different and it is somehow calculated with every player's ping to keep it in sync;
shortest possible wait duration is around 0.27s, even if you put one as 0s it will never be that.
In your case, it doesn't seem necessary to replace with timers, unless you want that 100% accuracy, if it is that relevant.
 
Waits have plenty of issues. They send packets over the network, they are innacurate due to latency, they run even when the game is paused (use PolledWait to avoid this), and can cause weird bugs like handle stack corruption. Its up to you whether or not youre willing to deal with these issues, but if you can not use them I would do so.

In Lua its trivial to avoid them, however in JASS your only real option is timers. If you are using GUI in JASS scripting mode then timers might be a pain to implement.
 
Level 9
Joined
Mar 26, 2017
Messages
376
In Lua its trivial to avoid them, however in JASS your only real option is timers. If you are using GUI in JASS scripting mode then timers might be a pain to implement.

Might I ask what you mean by 'In Lua it is trivial'? For the reasons you mentioned I use timers exclusively in my map. But is there a more efficient/useful implementation that mapmakers use for Lua?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
Might I ask what you mean by 'In Lua it is trivial'? For the reasons you mentioned I use timers exclusively in my map. But is there a more efficient/useful implementation that mapmakers use for Lua?
Lua allows a user implementation of Wait using coroutines, times and lambas (as TriggerHappy has mentioned). This has the huge advantage that your program flow can remain exactly the same just with your TriggerSleepAction waits replaced with the coroutine based waits. This avoids all the complexities of tracking state that JASS has when using timers. Lua also does this quite efficiently, possibly more so than JASS due to the exclusive use of built-in language features to manage the state rather than hashtables, indexers or other such systems.

With Lua you can use the StarCraft II style programming approach for waits where an equivalent action does work correctly as you expect it would without any of the negative issues TriggerSleepAction has.
 
Level 9
Joined
Mar 26, 2017
Messages
376
Thanks for the further explanation. I'm trying to wrap my head around it, because I still do not really see how this code would function without timers.

The way I put it right now is something like this:

Lua:
function xxx()
    <part 1>
    local t = CreateTimer()
    TimerStart(t, 10, false, function()
        <part 2>
    TimerStart(t, 10, false, function()
        <part 3>
    end)end)
end

Now instead of starting a timer, you could pause the function right in the middle with the coroutine application. But how can you tell the system to resume after x seconds have passed. Wouldn't you need to start a WC3 Timer for this after all?

Or did I misunderstand this at all, and is the method of coroutines just a way to insert a WC3 timer in the midst of a function, without having to put multiple function() end)'s around the entire thing?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
Now instead of starting a timer, you could pause the function right in the middle with the coroutine application. But how can you tell the system to resume after x seconds have passed. Wouldn't you need to start a WC3 Timer for this after all?
Yes you start a timer and in its callback function you get the coroutine to resume. The callback function is a lambda which retains a copy of some of the local state allowing it to know which coroutine to resume. The creation of the timer, callback, starting and resuming could all be turned into a single function call usable inside any coroutine which fulfils the style of traditional TriggerSleepAction wait.
 
Here is an example of a wait function using coroutines in TypeScript.

JavaScript:
function wait(howMuch: number) {
  const timer = new Timer();
  const co = coroutine.running();
  timer.start(howMuch, false, () => {
    coroutine.resume(co[0]);
  });
  coroutine.yield();
  timer.destroy();
}

const co = coroutine.create(() => {
  print("hello");
  wait(1);
  print("world");
})

coroutine.resume(co);
 
Level 9
Joined
Mar 26, 2017
Messages
376
Ah ok, think I understand it now.
If you need to create a WC3 timer anyway, and need to create a coroutine around each function in which you use waits, then I think I still prefer the method I posted above, but thanks for the explanation. I can see the application though if you have a function with multiple waits (such as a cinematic sequence), it might otherwise get messy.
 
Last edited:
Level 24
Joined
Jun 26, 2020
Messages
1,850
Ok, I see this thread, and I looked use wait function is bad unless you use lua (I'm using older versions), but I can use waits instead, but what happened if I wanna pass data, you may say I can use TimerUtils, but for that I have to create a lot of variables to do that or do I have another option?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
but what happened if I wanna pass data, you may say I can use TimerUtils, but for that I have to create a lot of variables to do that or do I have another option?
For timer based systems the recommended approach is to use a mapping data structure such as hashtable. You can then map data to the handle id of the timer so that you can retrieve that data again when the timer expires by getting the expired timer's handle ID. This principle was used all the way back with the handlevars system, although hashtables are now much more efficient at it.
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
For timer based systems the recommended approach is to use a mapping data structure such as hashtable. You can then map data to the handle id of the timer so that you can retrieve that data again when the timer expires by getting the expired timer's handle ID. This principle was used all the way back with the handlevars system, although hashtables are now much more efficient at it.
Ok, all of not create a lot of variables.
 
Level 24
Joined
Jun 26, 2020
Messages
1,850
But what can I do in case I use the trick of remove a effect after a time because for that you must do
  • Special Effect - Create a special effect
  • Set Effect = (Last created special effect)
  • Trigger - Run Remove Effect <gen> (Ignoring conditions)
  • Remove Effect
    • Events
    • Conditions
    • Actions
      • Custom script: local effect tempEffect
      • Custom script: set tempEffect = udg_Effect
      • Wait 2.00 seconds
      • Custom script: set udg_Effect = tempEffect
      • Special Effect - Destroy Effect
Because I can't use local timers in this case.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
But what can I do in case I use the trick of remove a effect after a time because for that you must do
  • Special Effect - Create a special effect
  • Set Effect = (Last created special effect)
  • Trigger - Run Remove Effect <gen> (Ignoring conditions)
  • Remove Effect
    • Events
    • Conditions
    • Actions
      • Custom script: local effect tempEffect
      • Custom script: set tempEffect = udg_Effect
      • Wait 2.00 seconds
      • Custom script: set udg_Effect = tempEffect
      • Special Effect - Destroy Effect
Because I can't use local timers in this case.
I've never once experienced an issue with using something like that. Sure, it's not as precise as using timers, but it's hardly noticeable.

Also, you can optimize it and add more control:
  • Actions
    • Special Effect - Create a special effect attached to the chest of (Triggering unit) using Abilities\Spells\Human\DivineShield\DivineShieldTarget.mdl
    • Set Variable SFX_Duration = 1.00
    • Trigger - Run SFX Destroy <gen> (ignoring conditions)
  • Actions
    • Custom script: local effect sfx = GetLastCreatedEffectBJ()
    • Wait SFX_Duration seconds
    • Custom script: call DestroyEffect(sfx)
    • Custom script: set sfx = null
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,870
There's no reason here to replace the wait with a timer unless you want it to be fully accurate. Handle stacking corruption is almost meaningless as it happens 1 in 1000 maps. Waits not pausing when game is paused is indeed one of most annoying aspects of it, but then again, you should know what you're dealing it and it matters in your context or not. Going full timer-freak isn't recommended, specially for GUI.
I've never once experienced an issue with using something like that. Sure, it's not as precise as using timers, but it's hardly noticeable.

Also, you can optimize it and add more control:
  • Actions
    • Special Effect - Create a special effect attached to the chest of (Triggering unit) using Abilities\Spells\Human\DivineShield\DivineShieldTarget.mdl
    • Set Variable SFX_Duration = 1.00
    • Trigger - Run SFX Destroy <gen> (ignoring conditions)
  • Actions
    • Custom script: local effect sfx = GetLastCreatedEffectBJ()
    • Wait SFX_Duration seconds
    • Custom script: call DestroyEffect(sfx)
    • Custom script: set sfx = null
In the first trigger, if there's nothing after creating the effect what is the point of creation another one to destroy the effect? Can do it all in one trigger for fully optimization. The duration variable seems questionable. Also can use the shadowing global variable trick.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,516
There's no reason here to replace the wait with a timer unless you want it to be fully accurate. Handle stacking corruption is almost meaningless as it happens 1 in 1000 maps. Waits not pausing when game is paused is indeed one of most annoying aspects of it, but then again, you should know what you're dealing it and it matters in your context or not. Going full timer-freak isn't recommended, specially for GUI.

In the first trigger, if there's nothing after creating the effect what is the point of creation another one to destroy the effect? Can do it all in one trigger for fully optimization. The duration variable seems questionable. Also can use the shadowing global variable trick.
That's just an example on how to use it.

It's a system for GUI users, whenever they want to destroy an effect after a delay they can set SFX_Duration and then run SFX Destroy, this will destroy the effect after SFX_Duration seconds. It's MUI and the Wait from SFX Destroy is not going to interfere with their original trigger.

Example of proper use:
  • Actions
    • Set Variable SFX_Duration = 2.00
    • Unit Group - Pick every unit in (Units in (Playable map area)) and do (Actions)
      • Loop - Actions
        • Special Effect - Create a special effect attached to the overhead of (Triggering unit) using Abilities\Spells\Undead\AbsorbMana\AbsorbManaBirthMissile.mdl
        • Trigger - Run SFX Destroy <gen> (ignoring conditions)
Obviously if we put the Wait 2.00 seconds in this Loop everything would break, but using the "system" (if you can really call it that :p) works perfectly.
 
Status
Not open for further replies.
Top