System Timer2

Bribe

vJass errors are legion
Reaction score
67
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


Demo:
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
 

Bribe

vJass errors are legion
Reaction score
67
Yes, this is a priority queue based on the expiration timer you give ... 0.5 would be inserted before 1.0, 0.75 would be inserted between the two, etc.
 

xofvc4rqb

New Member
Reaction score
2
Since I have difficulties understanding how to use it, it'd be nice if you could provide an example of usage.
 

Nestharus

o-o
Reaction score
84
JASS:

        - public integer data
          > optional placeholder to store an integer (a struct instance)


Get rid of that.. it's a module, so they can add w/e they like.

Make the callback a non static method so they can just use the timer as this*
 

Bribe

vJass errors are legion
Reaction score
67
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.
 

Nestharus

o-o
Reaction score
84
Your retrieval of timers is bad. This will now not only be slower than regular timers and the slowest possible version on TimerUtils, but it will be drastically slower ;|.


j4l used the ingenious idea of the timer timeouts, which obviously can't be used in a timer queue ;|. I'm not sure how to retrieve the current timer queue for any given expiring timer queue. A hashtable lookup is out of the question.
 

Bribe

vJass errors are legion
Reaction score
67
Your retrieval of timers is bad.

I don't retrieve any timers. The module keeps everything localized so the static "zeit" method already knows what its data is supposed to be. I pass code arguments around to avoid unnecessary trigger evaluations.
 

tooltiperror

Super Moderator
Reaction score
231
I will be adding this to my Timer Tutorial.

Beautiful interface, about the same as T32 I guess but more intuitive.
 

Sim

Forum Administrator
Staff member
Reaction score
534
Shouldn't the thread name be "Timer2"?
 

Nestharus

o-o
Reaction score
84
Better, let me quote your post from the other thread
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.


And my response
Still bad, needs to be a BST

If you had times 1,3,4,5,9,12 and you wanted to add .5, a BST would loop through 5,4,1 rather than 12,9,5,4,3,1. If you just did it from the middle, it'd be 5,4,3,1.

Just implement the BST -.-. It's a very obvious improvement to your thing.

For those of you who don't know what a BST is
binarysearchtree4.jpg



Instead of
1,2,3,4,5,6,7,8,9,10


Looping from 10 and adding to the front, you'd have to loop through every single node (10 iterations). Looping from 5, you'd have to loop through 5 nodes. Looping in a BST, it'd be 4 nodes. The change in iterations between the middle and the BST continues to grow larger.

this is the structure I actually suggest as it doesn't require heights be stored to achieve balancing
fig1.jpg
 

Bribe

vJass errors are legion
Reaction score
67
@Daxtreme

When I first submitted this resource I just called it "Timer", but later updated it to be Timer2.

@Nestharus

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.
 

tooltiperror

Super Moderator
Reaction score
231
This is an excellent resource.

It has a good interface, and has been proven by theory to be better than plain Warcraft III natives and offset.

Approved.
 

Nestharus

o-o
Reaction score
84
from THW

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 ; ).


And your timer 2 thing hit 0 fps with 100 different timeouts whereas I think the one mentioned above pulled off 40 (and that was before the speed-ups I've been doing).
 

Bribe

vJass errors are legion
Reaction score
67
I don't know if I said this already but I'm really glad you're going to be remaking this
timer system because the concepts you've brought in to make this faster are way
beyond my current programming experience.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    So what it really is me trying to implement some kind of better site navigation not change the whole theme of the site
  • Varine Varine:
    How can you tell the difference between real traffic and indexing or AI generation bots?
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air
  • The Helper The Helper:
    New dessert added to recipes Southern Pecan Praline Cake https://www.thehelper.net/threads/recipe-southern-pecan-praline-cake.193555/
  • The Helper The Helper:
    Another bot invasion 493 members online most of them bots that do not show up on stats
  • Varine Varine:
    I'm looking at a solid 378 guests, but 3 members. Of which two are me and VSNES. The third is unlisted, which makes me think its a ghost.
    +1
  • The Helper The Helper:
    Some members choose invisibility mode
    +1
  • The Helper The Helper:
    I bitch about Xenforo sometimes but it really is full featured you just have to really know what you are doing to get the most out of it.
  • The Helper The Helper:
    It is just not easy to fix styles and customize but it definitely can be done
  • The Helper The Helper:
    I do know this - xenforo dropped the ball by not keeping the vbulletin reputation comments as a feature. The loss of the Reputation comments data when we switched to Xenforo really was the death knell for the site when it came to all the users that left. I know I missed it so much and I got way less interested in the site when that feature was gone and I run the site.
  • Blackveiled Blackveiled:
    People love rep, lol
    +1
  • The Helper The Helper:
    The recipe today is Sloppy Joe Casserole - one of my faves LOL https://www.thehelper.net/threads/sloppy-joe-casserole-with-manwich.193585/

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top