library T2 initializer OnInit /* Timer 2, by Bribe
Timer 2
Timer 2 is a pseudo-timer system optimized for timeouts longer than 0.3
seconds.
The most common approach to doing what Timer2 does:
local timer t = GetExpiredTimer()
local integer this = GetTimerData(t)
call this.deallocate()
call ReleaseTimer(t)
set t = null
...
local integer this = thistype.allocate()
local timer t = NewTimer()
call SetTimerData(t, this)
call TimerStart(t, 3.25, false, function Expire)
set t = null
Timer2's API looks like this:
implement T2Expire
call this.deallocate()
implement T2End
...
call this.allocate(3.25)
That's just a quick & dirty show of how to use this resource. However, it
only uses one timer per "struct/endstruct" instead of one timer per instance.
A side effect of this is a huge bonus of reducing RAM. Another side effect
is that the allocate/deallocate are shared for all structs, so you will use
fewer global variables yet achieve the same end.
API must be implemented into a struct which "extends array" and you must have
JassHelper 0.A.2.B.
Module T2
- Declare any local variables here.
- API:
- static method allocate takes real timeout returns thistype
> Runs the code between T2Expire and T2Null after "timeout" seconds.
It repeats this process forever until you call the next method:
- method deallocate takes nothing returns nothing
> Releases the instance when its job is done.
- method operator allocated takes nothing returns boolean
> Was "whichTimer" released?
- method operator timeout takes nothing returns real
> Returns the timeout you specified in "newTimer".
Module T2Expire
- Place system code here using "this" as a pointer.
Module T2Null
- Null any local variables here.
Module T2End
- Implement this to complete the module package process.
*/
globals
private boolean array live //If a Timer is running (Live)
private boolean array merged //Same expire-time as next Timer (Merged)
private integer array next //Next Timer on queue (Ordinance)
private real array tout //Original timeout
private real array ttnx //Time until next Expiration
private timer array queue //1 timer for each Queue
private integer array iter //A mini-list for timer expiration (Iteration)
private boolean array stop //Marks when to exit loops (Stop)
private integer index = 0 //Index count
private integer array recy //Recycle list
endglobals
//Initialize
private function OnInit takes nothing returns nothing
set stop[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 head, integer this, code c returns nothing
local real x = tout[this] //Stored for efficiency
local real y = 0 //The idea is to align "x" and "y"
local integer i = head //Volatile Timer (changes frequently)
loop
if stop[next[i]] then //Insert as last Timer
set ttnx[i] = x - y
exitwhen true
endif
set y = y + ttnx[i]
if x == y then //Same expiration-time as next Timer
set merged[this] = true
exitwhen true
elseif x < y then //Insert "this" between "i" and "next[i]"
set ttnx[this] = y - x
set ttnx[i] = ttnx[i] - ttnx[this]
exitwhen true
endif
loop //Ignore "merged" Timers
set i = next[i]
exitwhen not merged[i]
endloop
endloop
set next[this] = next[i]
set next[i] = this
if i == head then
call TimerStart(queue[head], ttnx[head], false, c)
endif
endfunction
//Allocates a new timer instance for a node.
private function I takes integer head, real timeout, code c returns integer
local integer this = recy[0]
if this == 0 then
set this = index + 1
set index = this
else
set recy[0] = recy[this]
endif
set tout[this] = timeout
set ttnx[head] = TimerGetRemaining(queue[head])
call A(head, this, c)
set live[this] = true
return this
endfunction
//T2 module's onInit behavior
private function Init takes nothing returns integer
set index = index + 1
set next[index] = index
set queue[index] = CreateTimer()
set stop[index] = true
return index
endfunction
//Timer expiration behavior
private function E takes integer head, code c returns integer
local integer i = head
local integer this = 0
loop
set i = next[i]
if live[i] then
set iter[i] = this
set this = i
else
set recy[i] = recy[0]
set recy[0] = i
endif
exitwhen not merged[i]
set merged[i] = false
endloop
if next[i] != head then
set ttnx[head] = ttnx[i]
call TimerStart(queue[head], ttnx[head], false, c)
endif
set next[head] = next[i]
return this
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 head
private static code runCode
//Allocates a new timer instance.
static method allocate takes real timeout returns thistype
return I(.head, timeout, .runCode)
endmethod
//Deallocate the Timer when its job is done (else it repeats forever).
method deallocate takes nothing returns nothing
set live[this] = false
endmethod
//Returns true if the Timer is still scheduled to expire.
method operator allocated takes nothing returns boolean
return live[this]
endmethod
//Returns the timeout you originally passed to the Timer.
method operator timeout takes nothing returns real
return tout[this]
endmethod
private static method expireProxy takes nothing returns nothing
local integer this = E(.head, .runCode)
endmodule
module T2Expire
implement T2 //To make the implementation of T2 optional.
loop
exitwhen stop[this]
endmodule
module T2Null
if live[this] then
call A(.head, this, .runCode)
else
set recy[this] = recy[0]
set recy[0] = this
endif
set this = iter[this]
endloop
endmodule
module T2End
implement T2Null //To make the implementation of T2Null optional.
endmethod
private static method onInit takes nothing returns nothing
set .head = Init()
set .runCode = function thistype.expireProxy
endmethod
endmodule
module Timer2 //Simple version for people who like simple.
implement T2
implement T2Expire
call this.expire()
implement T2Null
implement T2End
endmodule
endlibrary