- Joined
- Sep 26, 2009
- Messages
- 9,534
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.
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: