[vJass] Key Timers 2
Requirements:
- Jass NewGen
EXAMPLE SPELLS:
These two spells are the ones I use to test new versions of KT2. They demonstrate the full power of the system in curious ways... ^_^
Sandman.
Sharl.
Key Timers 2 is, at it's core, a timer attachment system. However, it does not actually use one timer per instance. It uses NO H2I to attach data, and has certain efficiency implementations to make it run faster than even TU red for periods below 0.3 seconds, although for higher periods it may not be quite as efficient.
This is also, at the time of release, the most efficient timer system in existance for low periods.
Updates:
Key Timers 2:
- Version 1.6.0: Removed the maximum period limit and removed the first procurement inaccuracy issue. Also added warning for invalid periods, although these cannot detect mismatches below 0.001 of a second.
- Version 1.5: Made it stable and reliable, removed the boolexpr memory leaks. Now 22% faster than before. ONE INCOMPATABILITY - if you called KT_GetData more than once, or not at all in your code, it will need to be modified to call KT_GetData exactly once.
- Version 1.4: Switched to putting all attached code on one trigger, using Or boolexprs and modifying GetData. Unofficial release.
- Version 1.3: Optimized and inlined. Is within margin on error in speed to verson 1.1. Estimated 0.2% efficiency gain.
- Version 1.2: Wasn't released, pending optimization. Rewritten to use a different data storage system (linked lists), to remove the limit of 400 instances per period and 20 different periods max. The "Add" function should also be faster (not bench tested).
- Version 1.1: Updated to use globals instead of locals for efficiency. Changed attaching method to KT_GetData for efficiency. The old GetTriggerExecCount(GetTriggeringTrigger()) method must be replaced with KT_GetData() now to work. Various other optimizations such as using globals to store certain array values instead of constantly referencing arrays. Much bench testing went into determining these things.
- Version 1.0: Release. Does the same thing as Key Timers 1 but with a nicer interface.
Key Timers 2
Version 1.6.1
Requirements:
- Jass NewGen
JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ KT ~~ Key Timers 2 ~~ By Jesus4Lyf ~~ Version 1.6.1 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// What is Key Timers?
// - Key Timers attaches structs to timers, or more to the point timed
// effects.
// - You can specify different periods.
// - Key Timers only uses one timer with one trigger per low period
// to keep things efficient, especially within the looping.
// - Key Timers alternatively uses one single timer for all higher periods
// to allow accurate expirations in a stable and reasonably efficient fashion.
//
// =Pros=
// - Easy to use.
// - Fastest attachment loading system (storing in parallel arrays).
// - Fastest execution system for low periods (attaching all functions to one trigger).
// - Allows multiple periods to be used.
//
// =Cons=
// - The code passed into KT2 must call KT_GetData exactly once.
// - Periods must be a multiple of 0.00125 seconds. Not 0.007, for example.
//
// Functions:
// - KT_Add(userFunc, struct, period)
// - KT_GetData returns the struct
//
// - userFunc is to be a user function that takes nothing and returns boolean.
// It will be executed by the system every period until it returns true.
//
// - KT_GetData is to be used inside func, it will return the struct passed to
// to the Add function. It must be called exactly once within the func.
//
// Details:
// - KT2 treats low periods and high periods differently, optimizing each
// with appropriate speed and accuracy, although this effect is invisible
// to you, the mapper.
//
// - While func returns false the timer will continue to call it each period.
// Once func returns true the instance will be detached from system.
//
// Thanks:
// - Daxtreme: For encouraging me to return to Key Timers 2, rather than
// leave it to rot. His interest in the system restored it, and helped
// it to become what it is now. :)
//
// - Captain Griffen: For his work on Rapid Timers, demonstrating that it
// is possible to attach all functions to one trigger, and that it is
// indeed faster.
//
// - Cohadar: Told me to make Key Timers without a textmacro.
// Thanks to him for helping me with the original Key Timers system too.
// Also, I'd like to thank him for his work on Timer Ticker (TT) which
// demonstrated how to use triggers/conditions in this sort of system,
// which has been used in Key Timers 2.
//
// How to import:
// - Create a trigger named KT.
// - Convert it to custom text and replace the whole trigger text with this.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library KT initializer Init
///////////////
// Constants //
////////////////////////////////////////////////////////////////////////////
// That bit that users may play with if they know what they're doing.
// Not touching these at all is recommended.
globals
private constant real PERIODTHRESHOLD=0.3 // MUST be below 10.24 seconds.
private constant real HT_MARGINOFERROR=0.01
endglobals
//////////////////////////
// Previous KT2 Globals //
////////////////////////////////////////////////////////////////////////////
// These needed to be moved here for HT to hook GetData.
globals
private timer array KeyTimer
private trigger array TimerTrigger
private integer array KeyTimerListPointer
private integer array KeyTimerListEndPointer
private triggercondition array TriggerCond
private boolexpr array Boolexpr
private integer array Data
private integer array Next
private integer array Prev
private integer TrigMax=0
private integer array NextMem
private integer NextMemMaxPlusOne=1
private integer array ToAddMem
private triggercondition array ToRemove
private boolexpr array ToDestroy
private boolean array IsAdd
private integer AddRemoveMax=0
// Locals
private integer t_id=-1
private integer t_mem
private integer t_lastmem
private integer a_id
private integer a_mem
// Code Chunks
private conditionfunc RemoveInstanceCond
endglobals
/////////////////////
// Hibernate Timer //
////////////////////////////////////////////////////////////////////////////
// KT2 implementation for higher periods (low frequency).
private keyword HTnode
globals
private timer HT_Moderator=CreateTimer()
private real HT_Time=0.0
private HTnode HT_Top
private constant integer HT_DATAMEM=8190 // Added for KT2 hook. Don't change.
endglobals
globals//locals
private HTnode newHTnode
endglobals
private struct HTnode
HTnode prev // Make sure to link this before use
HTnode next // Make sure to link this before use
real period
real firewhen // = HT_Time at creation
trigger trig
integer data
static method new takes code func, integer data, real period returns HTnode
set newHTnode=HTnode.create()
if newHTnode.trig==null then
set newHTnode.trig=CreateTrigger()
endif
call TriggerAddCondition(newHTnode.trig,Condition(func))
set newHTnode.data=data
set newHTnode.period=period
set newHTnode.firewhen=HT_Time
return newHTnode
endmethod
method link takes HTnode prev, HTnode next returns nothing
set prev.next=this
set next.prev=this
set this.next=next
set this.prev=prev
endmethod
method unlink takes nothing returns nothing
set this.prev.next=this.next
set this.next.prev=this.prev
endmethod
method autolink takes HTnode start returns nothing
loop
if start.next==0 then
call this.link(start,0)
return
endif
if start.next.firewhen > this.firewhen then
call this.link(start,start.next)
return
endif
set start=start.next
endloop
endmethod
endstruct
globals//locals
private HTnode HT_handlenode
private HTnode HT_handlenext
endglobals
private function HT_Handler takes nothing returns nothing
// KT2 Data Hook
set t_mem=HT_DATAMEM
// End KT2 Data Hook
set HT_handlenode=HT_Top
set HT_handlenext=HT_handlenode.next
loop
set HT_handlenode=HT_handlenext
exitwhen HT_handlenode==0 // Would pause timer here under old method.
set HT_handlenext=HT_handlenode.next
if HT_handlenode.firewhen<HT_Time+HT_MARGINOFERROR then
// Load data
set Data[HT_DATAMEM]=HT_handlenode.data
// End load data
if TriggerEvaluate(HT_handlenode.trig) then
call HT_handlenode.unlink()
call TriggerClearConditions(HT_handlenode.trig)
call HT_handlenode.destroy()
else
set HT_handlenode.firewhen=HT_handlenode.firewhen+HT_handlenode.period
call HT_handlenode.unlink()
call HT_handlenode.autolink(HT_handlenode.prev)
if HT_handlenext==0 then
set HT_handlenext=HT_handlenode
endif
endif
else
// More to fire, but not yet.
return
endif
endloop
endfunction
globals//locals
private HTnode HT_addnode
endglobals
private function HTadd takes code func, integer data, real period returns nothing
set HT_addnode=HTnode.new(func, data, period)
set HT_addnode.firewhen=HT_Time+period
call HT_addnode.autolink(HT_Top)
//if HT_Top.next==HT_addnode then
// //Old start timer stuff
//endif
endfunction
private function HT_ModeratorLoop takes nothing returns nothing
set HT_Time=HT_Time+HT_MARGINOFERROR
if HT_Top.next.firewhen<HT_Time then
call HT_Handler()
endif
endfunction
private function HTinit takes nothing returns nothing
call TimerStart(HT_Moderator,HT_MARGINOFERROR,true,function HT_ModeratorLoop)
set HT_Top=HTnode.create()
set HT_Top.prev=0
set HT_Top.next=0
set HT_Top.firewhen=0.0
// Allow for GetData
set Next[HT_DATAMEM]=HT_DATAMEM
set Prev[HT_DATAMEM]=HT_DATAMEM
// End allow for GetData
endfunction
//////////////////
// Previous KT2 //
////////////////////////////////////////////////////////////////////////////
// The rest of the KT2 implementation
// Globals have been moved to top.
private function KeyTimerLoop takes nothing returns nothing
set t_id=R2I(TimerGetTimeout(GetExpiredTimer())*800)
set t_mem=KeyTimerListEndPointer[t_id]
call TriggerEvaluate(TimerTrigger[t_id])
set t_mem=0
loop
exitwhen t_mem==AddRemoveMax
set t_mem=t_mem+1
if IsAdd[t_mem] then
set TriggerCond[ToAddMem[t_mem]]=TriggerAddCondition(TimerTrigger[t_id],Boolexpr[ToAddMem[t_mem]])
else
call TriggerRemoveCondition(TimerTrigger[t_id],ToRemove[t_mem])
call DestroyBoolExpr(ToDestroy[t_mem])
endif
endloop
set AddRemoveMax=0
set t_id=-1
endfunction
private function RemoveInstance takes nothing returns boolean
// Will only fire if code returns true.
set AddRemoveMax=AddRemoveMax+1
set IsAdd[AddRemoveMax]=false
set ToRemove[AddRemoveMax]=TriggerCond[t_lastmem]
set ToDestroy[AddRemoveMax]=Boolexpr[t_lastmem]
if Next[t_lastmem]==0 then
set KeyTimerListEndPointer[t_id]=Prev[t_lastmem]
endif
set Prev[Next[t_lastmem]]=Prev[t_lastmem]
if Prev[t_lastmem]==0 then
set KeyTimerListPointer[t_id]=Next[t_lastmem]
if KeyTimerListPointer[t_id]<1 then
call PauseTimer(KeyTimer[t_id])
endif
else
set Next[Prev[t_lastmem]]=Next[t_lastmem]
endif
set NextMem[NextMemMaxPlusOne]=t_lastmem
set NextMemMaxPlusOne=NextMemMaxPlusOne+1
return false
endfunction
private function KTadd takes code func, integer data, real period returns nothing
set a_id=R2I(period*800)
if KeyTimer[a_id]==null then
set KeyTimer[a_id]=CreateTimer()
set TimerTrigger[a_id]=CreateTrigger()
endif
if NextMemMaxPlusOne==1 then
set TrigMax=TrigMax+1
set a_mem=TrigMax
else
set NextMemMaxPlusOne=NextMemMaxPlusOne-1
set a_mem=NextMem[NextMemMaxPlusOne]
endif
set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
if t_id==a_id then
set AddRemoveMax=AddRemoveMax+1
set IsAdd[AddRemoveMax]=true
set ToAddMem[AddRemoveMax]=a_mem
else
if KeyTimerListPointer[a_id]<1 then
call TimerStart(KeyTimer[a_id],a_id/800.0,true,function KeyTimerLoop)
set KeyTimerListEndPointer[a_id]=a_mem
endif
set TriggerCond[a_mem]=TriggerAddCondition(TimerTrigger[a_id],Boolexpr[a_mem])
endif
set Data[a_mem]=data
set Prev[a_mem]=0
set Next[a_mem]=KeyTimerListPointer[a_id]
set Prev[KeyTimerListPointer[a_id]]=a_mem
set KeyTimerListPointer[a_id]=a_mem
endfunction
public function GetData takes nothing returns integer // Gets hooked by HT.
set t_lastmem=t_mem
set t_mem=Prev[t_mem]
return Data[t_lastmem]
endfunction
private function KTinit takes nothing returns nothing
set RemoveInstanceCond=Condition(function RemoveInstance)
endfunction
///////////////
// Interface //
////////////////////////////////////////////////////////////////////////////
// Stitches it all together neatly.
public function Add takes code func, integer data, real period returns nothing
if period<PERIODTHRESHOLD then
call KTadd(func,data,period)
else
call HTadd(func,data,period)
endif
endfunction
private function Init takes nothing returns nothing
call KTinit()
call HTinit()
endfunction
endlibrary
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// End of Key Timers 2
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EXAMPLE SPELLS:
These two spells are the ones I use to test new versions of KT2. They demonstrate the full power of the system in curious ways... ^_^
Sandman.
Sharl.
Key Timers 2 is, at it's core, a timer attachment system. However, it does not actually use one timer per instance. It uses NO H2I to attach data, and has certain efficiency implementations to make it run faster than even TU red for periods below 0.3 seconds, although for higher periods it may not be quite as efficient.
This is also, at the time of release, the most efficient timer system in existance for low periods.
Updates:
Key Timers 2:
- Version 1.6.0: Removed the maximum period limit and removed the first procurement inaccuracy issue. Also added warning for invalid periods, although these cannot detect mismatches below 0.001 of a second.
- Version 1.5: Made it stable and reliable, removed the boolexpr memory leaks. Now 22% faster than before. ONE INCOMPATABILITY - if you called KT_GetData more than once, or not at all in your code, it will need to be modified to call KT_GetData exactly once.
- Version 1.4: Switched to putting all attached code on one trigger, using Or boolexprs and modifying GetData. Unofficial release.
- Version 1.3: Optimized and inlined. Is within margin on error in speed to verson 1.1. Estimated 0.2% efficiency gain.
- Version 1.2: Wasn't released, pending optimization. Rewritten to use a different data storage system (linked lists), to remove the limit of 400 instances per period and 20 different periods max. The "Add" function should also be faster (not bench tested).
- Version 1.1: Updated to use globals instead of locals for efficiency. Changed attaching method to KT_GetData for efficiency. The old GetTriggerExecCount(GetTriggeringTrigger()) method must be replaced with KT_GetData() now to work. Various other optimizations such as using globals to store certain array values instead of constantly referencing arrays. Much bench testing went into determining these things.
- Version 1.0: Release. Does the same thing as Key Timers 1 but with a nicer interface.
Last edited: