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:
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:
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:
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:
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 > 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>=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 > 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.
This function uses wc3´s return bug and allows us to use any handles unique handle index.
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 & 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 > 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("no more timers left")
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("MIN_HANDLE_COUNT: "+I2S(H2I(Timers[0])))
A function like this can be used to get timers index:
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("no more timers left")
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("MIN_HANDLE_COUNT: "+I2S(H2I(Timers[0])))
loop
exitwhen i > 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.