JASS - Timers and how to pass data to them

Viikuna

No Marlo no game.
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.
 

Viikuna

No Marlo no game.
I got the same feeling. I think Im gonna write some more examples and instructions.
 

Grymlax

Probably not around
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.
 

Zwiebelchen

You can change this now in User CP.
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.
 

Sooda

Diversity enchants
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?
 

Viikuna

No Marlo no game.
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.
 

Sooda

Diversity enchants
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?
 

Homer

New Member
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.
 

Kenny

Back for now.
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
 

Homer

New Member
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.
 

Viikuna

No Marlo no game.
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.
 

ImmortaL_GoD

New Member
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.
 

_whelp

New Member
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.
  • Ghan Ghan:
    Yeah that's a nice feature. The last one I posted I just typed "More here:" and pasted in the full link so it would do that.
  • tom_mai78101 tom_mai78101:
    Also fixed a bug, where I prevented the CPU fans from spinning wildly if I reverse the order of rendering and updating function calls in Pokemon Walking
  • The Helper The Helper:
    Is that going to be a full game now tom?
  • The Helper The Helper:
    what is this Ghan? https://enterprise.ghannet.com/ you are totally holding out on us my friend
  • Ghan Ghan:
    Not mine, I just ran the server. They let the domain expire but it was a cool project so I kept it alive there.
  • Ghan Ghan:
    Used to be enterpriserpg.com I think.
  • The Helper The Helper:
    If you go to the world editor tutorials site and click the starcraft link it takes you there :)
  • Ghan Ghan:
    lol
  • Ghan Ghan:
    I forgot about that alias name. (The full URL still went to the correct site.) That should be fixed.
  • Ghan Ghan:
    Silkroad Forums site really needs help.
  • Ghan Ghan:
    Its software has been out of support since 2017.
  • Ghan Ghan:
    The woes of using custom styles....
  • The Helper The Helper:
    does anyone still use silk road forums?
  • Ghan Ghan:
    It's about as active as here I'd say. Not much going on.
  • Wizard Wizard:
    I think it doesn't help that the silk road game isn't as popular any more I think. The only MMO I play these days is FFXIV like most other people I know.
  • The Helper The Helper:
    I checked a status page for players and it says the game has 500 daily players.
  • The Helper The Helper:
    I cannot believe that site is still going I wonder if Ryoko is involved with it at all would love to talk to him again and see what he is up to
  • The Helper The Helper:
    my oldest daughter failed her driving test today for her license she is 22 and yes I have been driving her everywhere her whole life
  • The Helper The Helper:
    Now they are blaming me on Facebook for her failure LOL I am accepting it because it is my failure I am her driving teacher even though she had certified driving instruction from the best place in town I for some reason cannot teach her how to drive this is frustrating
  • jonas jonas:
    I didn't get my license until I was 25
    +1
  • The Helper The Helper:
    Back in the old days when I was getting a license people were getting them at 16 yrs old and many were getting hardships so they could get them earlier. Almost nobody did not drive when they were able. Nowadays though....
  • Ghan Ghan:
    Why drive when there's Uber Eats?
    +2
  • The Helper The Helper:
    I hope Varine is OK have not heard from him in a while.
    +1
  • Ghan Ghan:
    Diablo 2 Resurrected launching in 9 days.

    Members online

    No members online now.

    Affiliates

    Hive Workshop NUON Dome
    Top