JASS - Timers and how to pass data to them

Viikuna

No Marlo no game.
Reaction score
265
Timers and how to pass data to them​

This little tutorial aims to teach you how to use timers and make MUI spells and systems with them.

You need to have a nice knowledge about structs and Jass in general. This tutorial contains lots of vJass stuff, so you must have NewGen and newest JassHelper


Basic Timer Knowledge​

Timer is a really usefull handle, because it gives us a control over the time itself. We can delay our actions or call some function in really fast periods, which is needed for stuff like moving units smoothly around.

List of useful timer functions:
JASS:
native CreateTimer takes nothing returns timer

JASS:
native DestroyTimer takes timer whichTimer returns nothing

In these days, we dont destroy timers anymore. They are usually recycled.
More about this later.
JASS:
native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing

This starts our timer. Function handlerFunction is the function which is called when timer expires. Boolean periodic determines whether timer is paused after it expires, or does it keep calling the handlerFunction.
JASS:
native PauseTimer takes timer whichTimer returns nothing

JASS:
native GetExpiredTimer takes nothing returns timer

You can use this in handlerFunction. It gives you the timer, which is calling the handlerFunction, so you can for example pause it using PauseTimer.

There is more timer related functions in Jass, and you can find them using NewGens Function List.

The MUI problem​

The problem with timers is that the actions are called in handlerFunction, which prevents us from using local variables. To make our spells and systems MUI, we need to somehow pass all needed data to the handlerFunction. There is several ways of doing this.

JASS:
globals
   timer time=CreateTimer()
endglobals

function handler takes nothing returns nothing
     
     call KillUnit(u) // error, local unit u doesnt work here.
     // What should we do now?
     
endfunction

function start takes nothing returns nothing
    
    local unit u=GetTriggerUnit()
    call TimerStart(time,5.,false,function handler)

endfunction


There is dozens cool timer and struct attachment -systems around there, which can do this job for you.
The purpose of this tutorial is not to teach you use those systems, but to learn few techniques you can use to do it by yourself.

I mention TimerUtils here as an example of a timer system.

Periodic Timers​

For fast periodic timers you can use one timer with struct arrays. This is pretty easy technique, and Im now going to tell how it works.

The idea is to have all spell instances in a struct array and then loop through that array and call actions for all instances no matter whether there is 1 or 100 of them.

You should already know something about structs and how they work.
JASS:
struct Data

 // stuff you need, like:
 
    unit caster
    real damage
    integer count=0
    
    method action takes nothing returns nothing
        // some stuff
    endmethod

endstruct


For this technique you need a timer, an integer and a Data array.
JASS:
globals
    private integer Total=0
    private Data array D
    private timer T=CreateTimer()
endglobals


Adding Struct to array:

When you have created a new spell instance ( Data ), you need to add it to array D, so timer can start calling its actions.

Integer Total tells us how many active instances there is currently. Our first action is to check if Total is 0. This means that we need to start our timer.
JASS:
        
        if Total==0 then
           call TimerStart(T,0.03,true,function TimerLoop)
        endif


You better not to forget adding your struct to array.
JASS:
set D[Total]=this

After this we incerease Total, because there is now a new instance created.
JASS:
set Total=Total+1


JASS:
private function Start takes nothing returns nothing
        local Data this=Data.create()
        // store all needed data to struct and do actions you need
        if Total==0 then
           call TimerStart(T,0.03,true,function TimerLoop)
        endif
        set D[Total]=this
        set Total=Total+1
endfunction


Periodic Actions:

Our handlerFunction is a function called TimerLoop. We need to loop through the array now.

JASS:
private function TimerLoop takes nothing returns nothing
     local integer i=0 // integer i is our loopindex
     loop
        exitwhen i>=Total // We stop when we have done actions for all instances

            // actions

        set i=i+1
     endloop

endfunction


You also need to do all the actions. If you are making a damage over time spell, you do your damage here. If you are making a knockback, this is where the units are moved.

One option is to have your actions in a method.

JASS:
private function TimerLoop takes nothing returns nothing
     local integer i=0
     loop
        exitwhen i>=Total

            call D<i>.action() 

        set i=i+1
     endloop
endfunction</i>


Removing instances from array:

When your spell is done, you need to remove that instance from array.
We also need to keep our array whole. When an instance is removed from array it leaves an empty spot. The best way to fix this spot, is to move our last array member to there..

JASS:
set D<i>=D[Total]</i>


We also need to decerease Total, because we are just removing an instance.

JASS:

             call D<i>.destroy() // dont forget to destroy your struct
             set Total=Total-1 // decerease total by one
             // If Total is greater than 0 we need to reorganize our array a bit 
             if Total &gt; 0 then
                 set D<i>=D[Total] // Here we fill that empty spot.
                 set i=i-1 // Loop index must also be decereased
             else // Total is 0 so there is no active instances
                 call PauseTimer(T) // We can pause our timer
             endif
</i></i>


Complete TimerLoop function:
JASS:
private function TimerLoop takes nothing returns nothing
     local integer i=0
     loop
        exitwhen i&gt;=Total
        if D<i>.end then // end is a boolean which tells us when to stop calling actions
             call D<i>.destroy()
             set Total=Total-1
             if Total &gt; 0 then
                 set D<i>=D[Total]
                 set i=i-1
             else
                 call PauseTimer(T)
             endif
        else
            call D<i>.action()
        endif
        set i=i+1
     endloop
endfunction</i></i></i></i>


Using a struct array is a fast and safe method. It is an easy and good solution for periodic timers and you should use it whenever you can.

Attaching Data to Timers

There is still many situations where above mehod is not an option.
You might need a non periodic timer for waiting few seconds, or 0.0 timer for damage blocking.

In these situations, you need to attach your data to timer. There is many ways to do this, yet all of them are somehow based on famous H2I function.

JASS:
function H2I takes handle h returns integer
    return h
    return 0
endfunction

This function uses wc3´s return bug and allows us to use any handles unique handle index.

JASS:

      local location loc=Location(0.,0.)
      local integer handleID=H2I(loc)


The problem of hanlde indexes is that they are too big integers to be used as array indexes. LocArray[ H2I(loc) ] doesnt really work, but there is few solutions for that.
You can use some hashing algorithm or decerease handle id by wc3´s initial handle amount, which is 0x100000. ( Actutally BJ creates some extra handles too, but this should work )

You can search these timer systems from different wc3 forums and check what kind of methods they use for timer attaching.

Timer System​

This Timer System does pretty much the same thing TimerUtils (Red) does. This part aims to explain how a system like it works.

Some Basic stuff:
JASS:
library TimerSystem
globals
//========================================================================
    // wc3´s initial handle count. If your map has lots of preplaced stuff,   
   //  which incereases handle count, you should incerease this.
    private constant integer MIN_HANDLE_COUNT = 0x100000
    // How many timers you are gonna need?
   // You should use one timer &amp; struct array in most of cases, so you 
   // should not need too many timers
    private constant integer TIMER_COUNT = 100
    
//========================================================================   
   // These are needed for timer recycling.
    private timer array Timers
    private integer Index
endglobals

// H2I is one of the coolest functions ever.
private function H2I takes handle h returns integer
    return h
    return 0
endfunction

endlibrary


Recycling Timers:

In our initializer function, we fill our array with timers. Note that the amount of timers this system can support is limited. You need to know how many timers your need for your map.

JASS:
private function InitTrig takes nothing returns nothing
    local integer i=1
    loop
        exitwhen i &gt; TIMER_COUNT // we stop when we have all the 100 timers have been created
        set Stack<i>=CreateTimer()
        set i = i + 1
    endloop
    set Index=TIMER_COUNT+1
endfunction</i>


This system requires you to use NewTimer and ReleaseTimer functions.
These functions handle the recycling.

JASS:
function NewTimer takes nothing returns timer
    if Index==0 then  // if index is 0 , all our 100 timers are in use.
        call BJDebugMsg(&quot;no more timers left&quot;)
        return null
    endif
    set Index=Index-1 // We use a same kind of array handling here than we
    return Timers[Index]  // used with struct arrays
endfunction

function ReleaseTimer takes timer t returns nothing
    if t != null then
        call PauseTimer(t) // Pausing the timer is a good idea.
        set Timers[Index]=t // We return our timer back to array
        set Index=Index+1 // and incerease Index
    endif
endfunction


Attaching Data

Because all timers are created in map init, their handle id´s should be low enough to be converted to suitable array index´s. However, if you are creating a lot of other handles in map init, you might need a bigger number for MIN_HANDLE_COUNT. Suitable MIN_HANDLE_COUNT can be easily found by adding a simple debug msg to init function:
JASS:
    
    set Timers[0]=CreateTimer() // System doesnt use Timers[0], so we can use it to see how
    //  many handles are created before our Timers.
    debug call BJDebugMsg(&quot;MIN_HANDLE_COUNT: &quot;+I2S(H2I(Timers[0])))



A function like this can be used to get timers index:
JASS:
function GetTimerId takes timer t returns integer
    return H2I(t)-MIN_HANDLE_COUNT
endfunction


You can use this index as an array index. An easy way to make spells MUI.
JASS:
globals
    private Data array D
endglobals

function Expire takes nothing returns nothing
    local timer t=GetExpiredTimer()
    local Data d = D[  GetTimerId(t)  ]
    
    //....
    call ReleaseTimer(t)
endfunction

function Start takes nothing returns nothing
    local Data d=Data.create()
    local timer t=NewTimer()
    //....
    set D[ GetTimerId(t) ] = d
    
endfunction


You can also have a one integer array for storing all struct types, and some nice GetTimerData and SetTimerData -functions.
Vexorian´s TimerUtils ( Red flavor ) uses this method. Studying that system is a good idea.

This kind of timer attaching system is really fast, because it only takes an array lookup, substraction and H2I call, but it is not very practical because of limited number of timers.
And still, it is really usefull when you cant use struct arrays for some reason.

JASS:
library TimerSystem initializer Init
globals
//========================================================================
    
    private constant integer MIN_HANDLE_COUNT = 0x100000
    private constant integer TIMER_COUNT = 100
    
//========================================================================   
    private timer array Timers
    private integer Index
endglobals

private function H2I takes handle h returns integer
    return h
    return 0
endfunction

function GetTimerId takes timer t returns integer
    return H2I(t)-MIN_HANDLE_COUNT
endfunction

function NewTimer takes nothing returns timer
    if Index==0 then
        call BJDebugMsg(&quot;no more timers left&quot;)
        return null
    endif
    set Index=Index-1
    return Timers[Index]
endfunction

function ReleaseTimer takes timer t returns nothing
    if t != null then
        call PauseTimer(t)
        set Timers[Index]=t
        set Index=Index+1
    endif
endfunction

private function Init takes nothing returns nothing
    local integer i=1
    set Timers[0]=CreateTimer()
    debug call BJDebugMsg(&quot;MIN_HANDLE_COUNT: &quot;+I2S(H2I(Timers[0])))
    loop
        exitwhen i &gt; TIMER_COUNT
        set Timers<i>=CreateTimer()
        set i = i + 1
    endloop
    set Index=TIMER_COUNT+1
endfunction

endlibrary</i>


This is my first tutorial, and I know that it will need a lot of editing before its complete. Comments and criticism are welcome.

I hope this helps those guys who want to learn about the use of timers.
 
I don't know why, it's just a feeling, but I think this tutorial lacks more explanation. You could extend some parts of it since it might be quite confusing for people that have just understood the basic uses of timers and structs. Good job anyway, +rep.
 
I got the same feeling. I think Im gonna write some more examples and instructions.
 
My "jass education" took a long brake since i couldn't wrap my head around timers (I couldn't find a tutorial and i was to lazy to ask someone) hopefully this will force me to get back to my "studies".

Unfortunately I wont have time to go through this tutorial anytime soon (because of my real education), but i will eventually, Thanks.
 
You should talk more about the pros and cons.

Attaching Structs to Timers possibly is the easiest of use way to do it, whereas your struct array solution has the advantage of not using a lot of extra functions to work, etc.

Also, you should talk more about expected memory usage, runtime, etc. ...
For example, If i really need to make a timer trigger that is run very often, which way is the best to do it?


Also, you should point out probabilities of leaking and how to avoid them.


I also suggest giving an example on your struct array solution.


Anyways, nice tutorial.
 
Tutorial teaches best way to create MUI triggers. I don't know much about H2I safety. Is there no way to mess up handle count?

> You might need a non periodic timer for waiting few seconds, or 0.0 timer for damage blocking.

It is done by adding as first thing item ability which gives huge amount of bonus health. Then storing that health value to variable and using 'If/ Then/ Else' action to check anything (for example unit current health) and then getting again unit new health value. Unit gets damaged when condition is checked so timer can be skipped.

> // wc3´s initial handle count. If your map has lots of preplaced stuff,
// which incereases handle count, you should incerease this.

How do I calculate new handle count?
 
Is there no way to mess up handle count?

Yes, there is. H2I alone is safe, but if you somehow manage to mess handle count ( Using I2H to dead handle, or destroying a trigger when it triggers, for example ) it can make H2I fail, and cause some other random bugs too.

It is done by adding as first thing item ability which gives huge amount of bonus health. Then storing that health value to variable and using 'If/ Then/ Else' action to check anything (for example unit current health) and then getting again unit new health value. Unit gets damaged when condition is checked so timer can be skipped.

Nice, I didnt know that. Still, 0.0 timer is useful in some ( rare ) cases.


How do I calculate new handle count?

You can just create some random handle and call BJDebugMsg(I2S(H2I( yourhandle ))) to see its handle index.
 
Was too vague first time, I meant this part:
JASS:
    // wc3´s initial handle count. If your map has lots of preplaced stuff,   
   //  which incereases handle count, you should incerease this.
    private constant integer MIN_HANDLE_COUNT = 0x100000

How do I calculate new value?
 
For the struct method and arrays. How do i go about running the function Start? What i mean is how should i trigger this function efficiently? Thanks.
 
Im pretty sure the Start function you are talking about, is just the same as what is generally named the Actions function. So you would add the function to a trigger on map initialization, just like you would for most spells.

JASS:
function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()

    call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
    call TriggerAddCondition(trig,Condition(function Conditions))
    call TriggerAddAction(trig,function Actions) // &lt;---- That is what calls the &quot;Start&quot; function.
    // call TriggerAddAction(trig,function Start) &lt;---- That is what you should right if you actually named the function &quot;Start&quot;.
endfunction
 
makes sense :) Not sure why i couldnt figure that out >.< probably is due to lack of sleep. thanks, this helped me understand how to use timers in a mui approach. Espec the idea of reusing a timer.
 
I should probably improve this with some proper examples, but Im pretty busy with one spell contest and one Hero contest, so this will have to wait.

Anyways, Im glad if this helped some of you. :)

edit. Start is just some function you use to 'start' your periodic actions. As kenny! said, it is usually your triggeraction too.
 
Well I have a spell like:
Trigger:
  • Then - Actions
    • Set temp_group = (Units in (Playable map area) matching (((Matching unit) is owned by Player 1 (Red)) Equal to (==) True))
    • Unit Group - Pick every unit in temp_group and do (Actions)
      • Loop - Actions
        • Special Effect - Create a special effect attached to the overhead of (Picked unit) using Abilities\Spells\Human\Resurrect\ResurrectCaster.mdl
        • Unit - Add Enchantment (Damage) to (Picked unit)
        • Unit - Add Enchantment (Armor) to (Picked unit)
    • Wait 3.00 seconds
    • Unit Group - Pick every unit in temp_group and do (Actions)
      • Loop - Actions
        • Unit - Remove Enchantment (Damage) from (Picked unit)
        • Unit - Remove Enchantment (Armor) from (Picked unit)
        • Special Effect - Destroy (Last created special effect)
    • Custom script: call DestroyGroup (udg_temp_group)

And I need a JASS timer for it I think to work, since it doesn't remove the abilities. So was wondering how/what would I implement..? My friend helped me with the code in vJASS as well, so I have that in case I can't just throw it in the raw script.
 
whats the point of MIN_HANDLE_COUNT and hexidecimals? never really understand why
 
H2I(someTimer) is way too high too fit 8192, so taking away 0x100000 or MIN_HANDLE_COUNT will make it fit.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Varine Varine:
    And since almost all of my programming experience is with defunct shit now, I figure my best place is helping preserve legacy stuff. Which I don't know how to do necessarily, but I need some kind of a hobby and figuring out how older things worked is the only shit that really interests me. Well soldering and restoration is fun too, but no one is bringing me new stuff to fix and restore, so it's mostly old shit, and I LOVE OG Xbox so much. I want to make sure it can function as long as possible, until someone can effectively emulate it at least. I have like 15 I was going to fix over the winter and didn't get to.
    +1
  • Varine Varine:
    I also have a couple OG gameboys, but idk if I can do that without like, manufacturing new parts that no one makes anymore and I can't do that right now
  • tom_mai78101 tom_mai78101:
    Currently in the middle of getting the probate process going. We're doing the informal probate process.
    +3
  • Varine Varine:
    A probate is usually done with a will, yes? If so I am sorry for your loss
    +1
  • The Helper The Helper:
    Yeah Tom, me too sorry for your loss buddy my mom told me she finds out her olds friend died from Google searching them. She had not talked to one of her old friends in a year and found out she died from Google. Also another one in the same session. RIP all of them my sincere condolences Tom
    +1
  • Varine Varine:
    We have some elderly guests that regularly come hang out at the bar at the end of the night, and every once in a while we don't see someone for a few weeks and then someone shows up with their obituary.
  • Varine Varine:
    We usually let them do their memorials there in the morning if they want to and I'll make them some snacks and drinks. There was one guy named Tom that came in like every night and would sit by himself and get a bunch of soup and a glass of wine. idk why but he LOVED our fucking soup, like he would order a fucking quart of it at a time and would always get so sad when we stop doing it for the summer.
    +1
  • Varine Varine:
    But he also loved our calamari, which is another thing I hate but it sells super well so I can't change it. There was one day he came in and was asking me how to make it, because he tried to at home once in the off season when we stop running it and he really wanted it lol
  • Varine Varine:
    I think he's one of the only people I've made recipes for for free because he really wanted a broccoli cheddar, and it was like dude I don't have a recipe, it's just whatever I have, but here, this is how you do it
  • Varine Varine:
    I don't think he ever figured out how to do the calamari in a pan though, like idk how to do that either. He was afraid of the at home deep fryers though and it's like yeah, that's fair, I am too
  • Varine Varine:
    He was just such a sweet old man, we had two servers pregnant and they held a baby shower together, he was soooooo fucking excited to get to see a baby. Unfortunately he died a month or so before they were born
  • The Helper The Helper:
    So I decided to Google some people that I had not seen or heard from in a while and sure enough one of my old best friends, we had a falling out years ago but whatever, find out he died of Pancreatic Cancer in January. I have also lost a few of my closer acquaintances from growing up the last year. Getting old - people die - I kinda thought it was going to be this way a few years ago....
    +2
  • The Helper The Helper:
    Forum running super slow again
  • Ghan Ghan:
    Not really clear from the stats as to what is causing the slowness.
  • Ghan Ghan:
    We get a lot of guest traffic so it may just be the load is getting too high and not from any particular source.
  • Ghan Ghan:
    Looks like the server is maxed out on CPU.
  • Ghan Ghan:
    Oh it looks like a lot of the traffic is Silkroad Forums. That domain isn't protected by Cloudflare.
  • Ghan Ghan:
    But the old Silkroad site is still on its own server. I just had a test site set up on this server for it.
  • Ghan Ghan:
    I just disabled that test site. Let's see if that helps the load.
  • Ghan Ghan:
    Looks much better already.
  • The Helper The Helper:
    I had actually forgot about the Silkroad site. I had asked
  • The Helper The Helper:
    SD Ryoko about it and he said the couple of people left on there really like it, that was a few years ago, maybe I should check back
  • jonas jonas:
    I guess when you're getting old, and the last day of soup season draws near, you start wondering
  • jonas jonas:
    will I make it to the start of the next season? or was this the last time I'll ever have my favorite dish?
  • The Helper The Helper:
    I am doing my first Vibe Coding project. In installed the environment and tools according to instructions but it is all chat doing this for me at my direction. It is fun really and holy shit I might finish in 2 hours what it would have taken a day to in my Access and this would be an electron app complete new

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials
      Top