| 11-14-2006, 12:54 AM | #1 |
T I M E R S
The ins and the outs The tricks and the traps Understanding the Timer JASS:type timer extends handle The timer is the most accurate means in Warcraft III coding to render short intervals of time. Because call TriggerSleepAction() and call PolledWait() are so inaccurate in short intervals and that playing on BNet does nothing but compound this inaccuracy, Jassers needed to find a way to measure short intervals of time for fast acting and reliable spells and code. Timers were the answer to that call. Make note, because a timer variable extends a handle, you will want to nullify any locally declared timers at the end of your code to prevent leaks. For help fixing memory leaks, see PipeDream's tutorial here. How Timers WorkThe below tidbit of code is the native used for starting a timer variable. The following shows the arguments of the TimerStart() native. JASS:native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing
How to Use TimersOkay, so now that you realize what a timer is and how it works, you need to know how to apply them to your code. There are multiple ways with which they can be useful, but for our purposes we're going to make a very simple spell that will fire a missile from the caster to the target after 1/4 second. Pretty simple spell, but as you can see we need to accurately create that missile after 1/4 seconds have elapsed. Since the other methods are miserable, we have thus decided to use timers. For the purposes of this demonstration, I will use global variables to move variables between functions. This first step assumes you understand how to create a trigger, add it to a map, and practically just know the basics of JASS. If you don't, I suggest reading Vex's Introduction to Jass tutorial here. JASS:function Firebolt_Conditions takes nothing returns boolean return GetSpellAbilityId() == 'A000' endfunction function Firebolt_Actions takes nothing returns nothing endfunction function InitTrig_Firebolt takes nothing returns nothing set gg_trg_Firebolt = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(gg_trg_Firebolt, EVENT_PLAYER_UNIT_SPELL_EFFECT) call TriggerAddCondition(gg_trg_Firebolt, Condition(function Firebolt_Conditions)) call TriggerAddAction(gg_trg_Firebolt, function Firebolt_Actions) endfunction JASS:function Firebolt_Actions takes nothing returns nothing local timer t = CreateTimer() call TimerStart(____, ____, ____, ____) set t = null endfunction Now fill in the blanks of the arguments and store the units. JASS:function Firebolt_Actions takes nothing returns nothing local timer t = CreateTimer() //************************************************ //* This keeps the units for use in the callback set udg_FireboltCaster = GetSpellAbilityUnit() set udg_FireboltTarget = GetSpellTargetUnit() //* WARNING: THIS IS NOT MULTI-INSTANCEABLE! //* //*************************************************************************************************** //* The Timer The Interval One-Time Use The function called on expiration call TimerStart( t , 0.25 , false , function Firebolt_Callback ) set t = null endfunction JASS:function Firebolt_Callback takes nothing returns nothing local timer t = GetExpiredTimer() //************************************************************** //* This is where we do all that cool stuff to make a firebolt. local unit u = udg_FireboltCaster local unit d = udg_FireboltTarget local unit s = CreateUnit(GetOwningPlayer(u), 'n000', GetUnitX(d), GetUnitY(d), 0) call UnitAddAbility(s, 'A001') call IssueTargetOrder(s, "firebolt", d) call UnitApplyTimedLife(s, 'BTLF', 1.0) set u = null set d = null set s = null //* Just assume all of that works. //* //* call DestroyTimer(t) set t = null endfunction Periodic/Repeating Timers JASS:call TimerStart(SomeTimer, SomeInterval, true, function SomeFunction) Moving Values to the Callback FunctionGame Cache Applications JASS:function Firebolt_Actions takes nothing returns nothing local timer t = CreateTimer() //************************************************ //* This keeps the units for use in the callback set udg_FireboltCaster = GetSpellAbilityUnit() set udg_FireboltTarget = GetSpellTargetUnit() //* call TimerStart(t, 0.25, false, function Firebolt_Callback) set t = null endfunction JASS:function Firebolt_Actions takes nothing returns nothing local integer i = H2I(CreateTimer()) //************************************************ //* This keeps the units for use in the callback call SetHandleHandle(I2Timer(i), "Firebolt_Caster", GetSpellAbilityUnit()) call SetHandleHandle(I2Timer(i), "Firebolt_Target", GetSpellAbilityUnit()) //* call TimerStart(I2Timer(i), 0.25, false, function Firebolt_Callback) endfunction JASS:function Firebolt_Callback takes nothing returns nothing local integer i = H2I(GetExpiredTimer()) //************************************************************** //* This is where we do all that cool stuff to make a firebolt. local unit u = GetHandleUnit(I2Timer(i), "Firebolt_Caster") local unit d = GetHandleUnit(I2Timer(i), "Firebolt_Target") local unit s = CreateUnit(GetOwningPlayer(u), 'n000', GetUnitX(d), GetUnitY(d), 0) call UnitAddAbility(s, 'A001') call IssueTargetOrder(s, "firebolt", d) call UnitApplyTimedLife(s, 'BTLF', 1.0) set u = null set d = null set s = null //* Just assume all of that works. //* //* call DestroyTimer(I2Timer(i)) endfunction Griffen's TimerAttach Function There is also a trick discovered by one of our resident project development moderators, Captain Griffen. This shows us an interesting way to attach integers to a timer, which can be useful in some cases. JASS:function TimerAttach takes timer t, real time, real value, code func returns nothing call TimerStart(t, value, false, null) call PauseTimer(t) call TimerStart(t, time, false, func) endfunction // ONLY call on an expired timer. function GetTimerInt takes timer t returns integer return R2I(TimerGetRemaining(t) + 0.5) endfunction function SFX_Timed_Loc_Child takes nothing returns nothing call DestroyEffect(I2E(GetTimerInt(GetExpiredTimer()))) call DestroyTimer(GetExpiredTimer()) endfunction function SFX_Timed_Loc takes string Effect, real X,real Y,real Time returns nothing call TimerAttach(CreateTimer(),Time,H2I(AddSpecialEffect(Effect,X,Y)),function SFX_Timed_Loc_Child) endfunction As soon as you try to store something with more digits than that (A unit type for example), the method isn't accurate. It appears as though this is simply a limitation of timers. In this case, cache or vJass applications may be useful. (See above) vJASS Applications vJass, through the Jass NewGen pack linked below, allows for excellent passage of multiple values through functions into other functions. You can store several things into a struct, or a group of variables, and then all you have to move between functions is the struct reference which is an integer. All of the type safety issues of game cache vanish if you're just moving integers, making cache and vJass powerful in unison. There are of course more advanced methods to attaching values to timers, you can find several systems regarding it in WC3C's System resource area. Timer EfficiencyTimer Recycling Many advanced users will use a process termed recycling timers to prevent the need to ever destroy a timer. Effectively, there is an array of timers where whenever you need a timer, you pull it from the stack and whenever you're done with a timer you return it to the stack to be used later. It's very simple in principle and in code, as you'll see below. A bit of code written by our Technical Director, Vexorian, reveals this in greater detail. The original post can be found here. The code shown below requires preprocessor help to fit into WC3, which can be done through the Jass NewGen Pack located here. JASS:library CSSafety //****************************************************************************************** //* //* CSSafety 14.1 //* ¯¯¯¯¯¯¯¯ //* //* Utilities to make things safer. Currently this simply includes a timer recycling //* Stack. Once you replace CreateTimer with NewTimer and DestroyTimer with ReleaseTimer //* you no longer have to care about setting timers to null nor about timer related issues //* with the handle index stack. //* //****************************************************************************************** //========================================================================================== globals private timer array T private integer N = 0 endglobals //========================================================================================== function NewTimer takes nothing returns timer if (N==0) then return CreateTimer() endif set N=N-1 return T[N] endfunction //========================================================================================== function ReleaseTimer takes timer t returns nothing call PauseTimer(t) if (N==8191) then debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!") //stack is full, the map already has much more troubles than the chance of bug call DestroyTimer(t) else set T[N]=t set N=N+1 endif endfunction endlibrary Pitfalls to TimersTimer Nullifying This is perhaps one of the most annoying issues with the timer variable. While the timer is very useful for jassers all over the world, there is a severe pitfall if it is used in conjunction with game cache storage. If you nullify a locally declared timer with set SomeTimer = null and you've set handles onto that timer, there is a chance the handles will return null the next time you attempt to recall them. This bug can be averted nearly entirely with the above timer recycling, since it removes the need to ever nullify timers. (As they are re-used) JASS:function H2I takes handle h returns integer return h return 0 endfunction function I2Timer takes integer i returns timer return i return null endfunction Example: JASS:local integer i = H2I(CreateTimer()) call TimerStart(I2Timer(i), .033, false, function SomeFunc) Destroying An Unfinished Timer This issue is a little more subtle than some of the others. If in some function you call DestroyTimer() on a timer that might not have finished running, it can fail some handle indicies as well as cause bugs in-game. Fortunately, the fix is relatively simple. JASS:call PauseTimer(YourTimer) TimerGetElapsed() If in the off-chance you happen to be working with a periodic timer, you CAN NOT get the total time the timer has been running by calling TimerGetElapsed()! If you want to maintain an index of how long the timer has been running, you'll need to store an integer to the timer that increments once every iteration of the timer callback. ConclusionThe aforementioned techniques and coding maneurisms are all of the basics as well as most of the advanced things that can be done or done to timers. What has been covered in this tutorial is everything your everyday mapper will find a need for. If there is any aspect to timers you feel this tutorial has left out, please let me know either by private message or posting in this thread and I will update it accordingly. Thank you for reading! |
| 11-14-2006, 12:58 AM | #2 |
It IS a good tutorial, but I wouldn't approve it, it is outdated... Timers shouldn't be destroyed but recycled, really. It saves us of all the esoteric things about when to set timers to null. It is safe and doesn't leak, since you need to pause the timer to recycle it you don't worry about the "destroyed timer still expires" bug either. |
| 11-14-2006, 02:01 AM | #3 |
I like it, it's useful and I think it has all that a JASS noob like me needs. Good Job Rising :) |
| 11-14-2006, 11:52 AM | #4 | |
Couple of comments: I suggest you mention this bug found by Griffen. It can be useful at times. Kept for reference: scratch the below, Vex is right. I think you might have used repeating in some place though, so might wanna change that for consistency.
|
| 11-14-2006, 11:55 AM | #5 |
periodic is not a GUI word. native TimerStart takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing |
| 11-14-2006, 04:16 PM | #6 |
Updated to remove the only instance of the word "repeating" in the tutorial. Updated to include the bug Griff discovered. And if it is obsolete... Well I have no way of updating it to include "recycling" timers. I didn't even know that was possible, let alone how to do it such that I could explain it to someone else. *Sigh* |
| 11-14-2006, 04:44 PM | #7 |
Actually, you ought to point out my method actually stores a real, rather than an int. Normally, this isn't significant, and is fine for 8 digit numbers (say, handle indexes). With 10 digits, it will fail (eg: unit types). Reals simply don't have the accuracy. |
| 11-14-2006, 05:36 PM | #8 |
Updated again to include a (hopefully) comprehensive explanation of timer recycling as well as a better explanation of Griff's trick. Hopefully it's more complete as a whole now. |
| 11-15-2006, 11:48 AM | #9 |
Just a thought -- Not to seem picky, but I think maybe this was placed in the wrong section. I hardly think any "beginners" would find it useful how a timer functions in jass. If anything, I think it would fit best in the "JASS" tutorial section. The entire tutorial is virtually made of Jass, afterall. |
| 12-23-2006, 03:33 PM | #10 |
thanks very much for the tutorial. it's very helpful. one question though maybe kind of directed at vexorian because it's about his code... how do i insert that without a preprocessor.. or with one, if i knew where to obtain one of those? |
| 12-27-2006, 05:53 PM | #11 |
The processor is jasshelper and there's a link to it in my signature(bellow): |
| 12-29-2006, 05:11 AM | #12 |
yes i'm very grateful for that. i've also got wehelper as well. and the caster system too. blizz need to include all this in the next patch. |
| 04-18-2007, 04:19 PM | #13 | |
Quote:
WFR Maley |
| 05-10-2007, 12:26 AM | #14 |
This is a greatest tutorial in the world!!! It contains ALL, what can help about timers (with links) - very good. 6/5, 100/10 :) |
| 05-10-2007, 01:53 AM | #15 |
I'm glad it was helpful. :) |
