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

[System] Timer 2

Timer2 operates based on a running philosophy of mine: doing more with less. How does one timer per struct sound, even with different expiration periods? If you want an alternative to TimerUtils (spamming a timer handle per instance) and need something more versatile than TimerQueue (preset, static interval), this could be a nice alternative.

Internally, you can consider it a "priority queue". If you don't know what it means, wikipedia has a nice explaination with a disclaimer: "not too efficient". Even though I've scripted this extremely optimally, I don't expect it to win a furious number of benchmarks, but I'll tell you the areas it excels at in speed:

If you have a spell that hits a group of units at once with some kind of buff that ends after 1 second, you would probably use TimerUtils. However, you know all the timers will expire at the exact same instant, so logically you know it could be done with just one timer. This system knows to "merge" the expirations - calling the expire method for each expired instance instead of having a bunch of timers expiring at once. In such a case, Timer2 will often turn out faster than TimerUtils (depending on how many instances had to expire... 1 or 2 probably won't make a difference but 5-10, or even 10-20, there will be a serious speed boost).

If you think your system will never come across such a scenario (having a group of timers expiring at the same time), I can assure you TimerUtils will be faster if speed is your concern. If you're a speed-freak, you've been warned. Because the "shorten names" part of Vexorian's map optimizer actually crashes the map in many cases, this also suffers from very short variable names. If you're a longVariableNameLover, you've also been warned.

In all seriousness, speed is the least important factor for timers that actually need a system like TimerUtils (in other words, couldn't be done with a struct loop already). Those timers expire so rarely that you'll never feel a difference in framerate. If keeping handle-count low and maintaining the lowest RAM possible are more important areas of focus, then I strongly recommend this library for you.

JASS:
library T2 initializer OnInit // Timer 2, by Bribe
/*
    API
        Module T2
        - static method newTimer takes real timeout returns thistype
          > calls a method from your struct entitled "expire" after "timeout"
            seconds. It repeats this forever until you call "T2_Release" with
            the integer it returned.
        
        function T2_Release takes integer whichTimer returns nothing
        - Releases "whichTimer" when its job is done
        
        function T2_Active takes integer whichTimer returns boolean
        - Was "whichTimer" released?
        
        function T2_Timeout takes integer whichTimer returns real
        - Returns the timeout you specified in "newTimer"
*/

// Timer instance vars
globals
    private boolean array l // If a Timer is running (Live)
    private boolean array m // Same expire-time as next Timer (Merged)
    private integer array o // Next Timer on queue (Ordinance)
    private real array z // Original time (Zeit)
    private real array e // Time until next Expiration
endglobals
    
// Misc vars
globals
    private timer array q // 1 timer for each Queue
    private integer array i // A mini-list for timer expiration (Iteration)
    private boolean array s // Marks when to exit loops (Stop)
endglobals
    
// Initialize
private function OnInit takes nothing returns nothing
    set s[0] = true
endfunction
    
//===========================================================================
// Timer operates as a "priority queue", inserting Timers with lower timeouts
// before Timers with higher timeouts to ensure they expire first. It uses a
// search method to scan through each Timer for the right spot, unfortunately
// turning this into an inefficient process in many cases.
//
private function A takes integer h, integer t, code c returns nothing
    local real x = z[t]         // Stored for efficiency only
    local real y = 0            // The idea is to align "x" and "y"
    local integer v = h         // Volatile Timer (changes frequently)
    loop
        if s[o[v]] then         // Insert as last Timer
            set e[v] = x - y
            exitwhen true
        endif
        set y = y + e[v]
        if x == y then          // Same expiration-time as next Timer
            set m[t] = true
            exitwhen true
        elseif x < y then       // Insert "t" between "v" and "o[v]"
            set e[t] = y - x
            set e[v] = e[v] - e[t]
            exitwhen true
        endif
        loop                    // Ignore "merged" Timers
            set v = o[v]
            exitwhen not m[v]
        endloop
    endloop
    set o[t] = o[v]
    set o[v] = t
    if v == h then
        call TimerStart(q[h], e[h], false, c)
    endif
endfunction
    
globals // Index generator and recycler
    private integer n = 0   // Index count
    private integer array r // Recycle list
    private integer p = 0   // Previously-recycled Timer
endglobals
    
// When you call "newTimer", this is what you're really calling
private function I takes integer h, real y, code c returns integer
    local integer t // This Timer
    if p == 0 then
        set t = n + 1
        set n = t
    else
        set t = p
        set p = r[t]
    endif
    set z[t] = y
    set e[h] = TimerGetRemaining(q[h])
    call A(h, t, c)
    set l[t] = true
    return t
endfunction
    
// Timer module's init behavior
private function Init takes nothing returns integer
    set n = n + 1
    set o[n] = n
    set q[n] = CreateTimer()
    set s[n] = true
    return n
endfunction
    
// Timer expiration behavior
private function E takes integer h, code c returns integer
    local integer v = h // Volatile Timer
    local integer t = 0 // Terminal Timer
    loop
        set v = o[v]
        if l[v] then
            set i[v] = t
            set t = v
        else
            set r[v] = p
            set p = v
        endif
        exitwhen not m[v]
        set m[v] = false
    endloop
    if o[v] != h then   // Restart the Timer
        set e[h] = e[v]
        call TimerStart(q[h], e[h], false, c)
    endif
    set o[h] = o[v]
    return t
endfunction
    
// Returns the timeout you gave the Timer originally
function T2_Timeout takes integer t returns real
    return z[t]
endfunction

// Returns if the Timer has been destroyed or not
function T2_Active takes integer t returns boolean
    return l[t]
endfunction

// Release the Timer when its job is done (else it repeats)
function T2_Release takes integer t returns nothing
    set l[t] = false
endfunction
    
//===========================================================================
// Running a textmacro (module) is what makes Timer work without triggers. It
// also significantly increases the Add method's performance, allocating one
// timer and one queue per struct rather than merging it all for each struct.
// For what it does, this is a very short module and shouldn't be too painful
// to implement.
// 
module T2
    private static integer h
    private static method zeit takes nothing returns nothing
        local integer v = E(h, function thistype.zeit)
        loop
            exitwhen s[v]
            call thistype(v).expire()
            if l[v] then
                call A(h, v, function thistype.zeit)
            else
                set r[v] = p
                set p = v
            endif
            set v = i[v]
        endloop
    endmethod
    static method newTimer takes real timeout returns thistype
        return I(h, timeout, function thistype.zeit)
    endmethod
    private static method onInit takes nothing returns nothing
        set h = Init()
    endmethod
endmodule
endlibrary


JASS:
struct Tester extends array
    
    method expire takes nothing returns nothing
        call BJDebugMsg(I2S(this) + " has expired after " + R2S(T2_Timeout(this)) + " seconds!")
    endmethod
    
    implement T2 // Notice T2 is placed below the "expire" method and above
                 // the "newTimer" call - that's because "newTimer" is found
                 // >in< the module and "expire" is called >from< the module
    
    static method create takes real timeout returns thistype
        return thistype.newTimer(timeout)
    endmethod
    
    method destroy takes nothing returns nothing
        call T2_Release(this)
    endmethod
    
endstruct
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
lol
Unlike TimerQueue, this is not going to win too many speed benchmarks
and that's not the purpose that drove me to create it. Truthfully, it
was my dislike of everything that is TimerUtils which led me to start
looking for another way to do dynamic timed-data attachment.

Not too many, more like 0 ;P. It'll be slower than TimerUtils using a hashtable pretty much 100% of the time =D.

Anyways, I guess it is useful if you just want to minimize the handle count, but the handle count doesn't have too much bearing on the map (unless there are leaks over a long period of time).

Anywho.

The best way to do dynamic timers is to have multiple timer queues running on multiple timers. The start of this was done in the Tqg module. I didn't continue it to being 100% dynamic as I couldn't figure out a way to retrieve the period of the expiring timer, but if I could, then it'd be epic :eek:.
 
Speed is the least important factor when you're dealing with timers with periods above 0.1 (basically any period that couldn't be done already with T32).

This allows for one timer per struct, even with a myriad of different timeouts.

One Timer to rule them all, one timer to find them, one timer to bring them all and in the struct bind them.

Also, with spells that deal timed-effects all in the same instant (the same time for all inflicted units), this can end up with lots of merged timers and could end up being faster than TimerUtils.

In fact, such an event is quite common.
 
Got rid of the struct altogether as it was pretty useless.

The naming conventions have all been changed (Timer changed to T2) and the callback static method is now a regular method entitled "expire" (keeping similarity to TimerQueue).

Lots of things have been optimized in this new version. I will continue to make optimizations but, from this point, no API changes are planned.

I didn't continue it to being 100% dynamic as I couldn't figure out a way to retrieve the period of the expiring timer, but if I could, then it'd be epic :eek:.

HandleId + Hashtable :p
 
I'll be implementing another valuable optimization that will eliminate the entire search method in 99% of practical cases. Here's a good illustration:

Suppose a hero casts a shockwave, and each time that shockwave hits there is a buff on the unit for 1 second. Here's the physical breakdown:

Code:
 Caster

    A
  B
   
    C
   
  D

If the caster casts the shockwave through the middle of the units, unit A will be hit first, then B, C and D. If unit E were added with the current Timer2 system, the loop would have to scan through A, B, C and D until it reaches the end and discovers that "E" also just needs to be added at the end.

Since this is such a common scenario, I will implement a check to see if the "E" node is already high enough to be the last node (in most practical cases it will be), if so, add it there, if not, scan through A, B, C, and D to find its place. I will be implementing this later this evening.
 
Despite your feeble attempts to convince me that would in fact be faster, you have yet to show a practical scenario in which a "binary standard tree" is useful. Logically, sure, but practically, in a WarCraft III scenario, 99% of the timers will just be added to the top of the heap instead of being placed sporadically in the middle zone.

And even less practical, all those random timers would have the same expiration behavior? Extremely unlikely. First checking the top node is the primary realistic scenario.

Here's what I posted on TheHelper.net:

Provide a realistic scenario where every single one of these timers with totally random timeouts will all exist simultaneously and, even more unlikely, all of them would share the exact same "expire" method. I also want to see how timers with random timeouts with all the same expire method would also reach such high numbers for it to matter.

IF, and I mean, IF a hero with a level 1 spell with a level 1 duration casts his spell at the same time as level 3 hero with a level 3 duration, it might have to loop through a handful of existing timers at MOST.

I understand "binary tree" is logically faster, but let's look at the circumstances how unlikely it is that lower timeouts will be inserted simultaneously with higher timeouts:

1. It's warcraft iii, so everything happens on a start-finish timescale (can't go backwards in time)
2. Usually, hero levels go UP, not down, so timers will only get higher and higher, so it adapts for each new hero level.
3. A binary tree would only slow things down in that above illustration
4. If two heroes cast two different-leveled spells of the exact same ability, at the exact same time, that's when it searches.
5. The main idea I've had the whole time with Timer 2 was spells anyway, because those are the most popular things that are created.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,202
The code is really poorly done. Avoid crap like function A, I, B etc as that is poor coding practice as it is hard to read and understand. I have never had shortened variable names crashing maps with vexorians optimizer as well so why did you do this? The main problem though is the lack of comments, as the code lacks a lot of implicitness due to meaningless variable names.

I am also not too sure if this is faster than a timer per struct instance as your merged calling code looks like it may have a lengthly overhead. Did you benchmark it over other approaches?
 
The code is really poorly done. Avoid crap like function A, I, B etc as that is poor coding practice as it is hard to read and understand. I have never had shortened variable names crashing maps with vexorians optimizer as well so why did you do this? The main problem though is the lack of comments, as the code lacks a lot of implicitness due to meaningless variable names.

I blame Nestharus for the super short names stuff :p. Look at his recent resources and see if they make sense.

But the optimizer is horribly broken. Breaks ExecuteFunc, native declarations and TriggerRegisterVariableEvent.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
OFF-TOPIC :

Seriously the only thing you can cry about wc3mapoptimizer is the native declaration.

Tell me how wc3mapoptimizer is supposed to handle such things :

call ExecuteFunc(<nonConstantString>)
call TriggerRegisterVariableEvent(...,<nonConstantString>...)

It doesn't have a crystal ball.

I have never tried the options but i think you can add exceptions, i mean wc3mapoptimizer would ignore such functions, don't try to make them shorter.
Maybe the same for real variables, but i really don't know and don't care personally.

I'm just sicked of these random complains about Vexorian stuff, sure it's not perfect, but at least he has done it.
(I'm for one was complaining about Vexorian stuff but i just realized people is more often complaining about some issues instead of be grateful for all his free work)
 
Glad to see more logical replies from you, Deaod. No reasoning, no comments, just facetious criticism. You failed on every argument against Table JUST because you're obsessed with Vexorian's Table, now if you feel like opening a can of worms, do share why you think the "Event" library does not make sensible use of TriggerRegisterVariableEvent.
 
Level 14
Joined
Nov 18, 2007
Messages
816
Loop over an array of function interface functions, THAT would be a better solution than any Event library. And the best part is that you cant implement a general purpose Event library that uses function interfaces.
This is why J4L's Event library sucks, and its also why Nestharus' Event library sucks. Function Interfaces beats triggers/boolexprs.
While what i suggested is very likely not as fast as using triggers/boolexprs, its still reasonably fast.

Oh, and btw, just because i havent posted more about your Table library doesnt mean that ive conceded that argument to you. I still think your library is superfluous or at the very least has serious API design flaws.
 
Function interfaces generate a lot of wasted code (duplicating the text of functions), and are at slower in proportion the more there are. Easier for users to work with, arguable, but the behind-the-scenes, where it counts, is horrific. Use vJass responsibly - some events are fired quite frequently and can benefit from any efficiency boost possible.

That the new Table is superfluous is arguable. A lot of people might as well comment out most of the savable types as most of them are useless to store in hashtables. I should just leave the least-used ones commented out and the user can un-comment if needed.

Regarding API design flaws, I'd appreciate some specifics. What's wrong with the API?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Ok.. this is major failure >.>

A binary heap of queues will run way better than this, which is what I'm working on for a timer system right now ^)^. It's 1 timer for the entire system : ).

Also, you did your module stuff poorly.. you want to have a binary heap in the core and evaluate the module triggers, which in turn loop thru and call the functions. This way you minimize the number of expired timers. Rather than 6 timers expiring, it can be 1 expired timer with a trigger evaluation with 6 trigger conditions on it.

Also, if you build the triggers before hand rather than on the timer expiration (each queue of the same remaining time), you can save even more speed. The biggest thing when you get to that level is that TriggerAddCondition call, so by removing that, you end up maxing out the speed to go even beyond standard timers ; ).
 
An improved version of TimerUtils Red would be unbeatable in terms of speed,
the only problem is the handle count. That's why I don't care so much about
speed in this case, because a system that expires with a long timeout would
have to have hundreds of trigger evaluations running to notice a framerate
difference during expiration.

If you're evaluating triggers + the timer's own expiration, obviously you could
use only 1 timer but you and I talked about that when I was first building it
and you said it should use one timer per expire-behavior... I'm surprised you
changed your mind on that.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I changed my mind because 1 timer with 600 trigger evaluations (not 1 with 600 trigger conditions) is faster than 600 expiring timers =).

Anyways what this means is that a good 1 timer system will outdo TimerUtils Red, or even plain timers with no operations in their functions.

I'm getting closer and closer to getting a timer system that's faster than timers >: P.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Proven that a 1 timer system will always be crap at thehelper.net

It's impossible to make a 1 timer system that is better than even natives with data attachment. 1 timer systems end up using more memory + more ram as well.


The performance difference was clearly shown in my binary heap implementation of a 1 timer system, which is certainly better than this resource ;p.
 
Top