System Key Timers 2

New Edit: Got it working (haven't updated the test map) and found triple boolean expression takes about twice as long to execute as the current version. So the only changes that are strong are to load/save the normal boolean expressions instead of create/destroy them. Tomorrow I'll submit the version which only has those changes and no [ljass]Or[/ljass] calls that slow down the current submisison.
 

Attachments

  • bench2.w3x
    36.2 KB · Views: 450
This has a constant boolean called NO_HASHTABLE that can be set to 'true' to revert to the current 1.23-compatible version, or it can be left to 'false' to recycle boolean expressions using the hashtable:

JASS:

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ KT ~~ Key Timers 2 ~~ By Jesus4Lyf ~~ Version 1.8.0 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  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 trigger per instance for all higher
//           periods to allow accurate expirations in a stable and efficient fashion.
//
//    =Pros=
//         - Easy to use.
//         - Fastest attachment loading system (storing in parallel arrays).
//         - Fastest execution system for low periods (all functions on one trigger).
//         - Allows multiple periods to be used.
//         - No H2I. Backwards compatability through patch 1.23 and 1.24.
//
//    =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. <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />
//
//         - 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&#039;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
    ///////////////
    // Constants //
    ////////////////////////////////////////////////////////////////////////////
    // That bit that users may play with if they know what they&#039;re doing.
    // Not touching these at all is recommended.
    globals
        // Period Threshold is the point at which Key Timers 2 will switch from
        // using the single timer per period mechanism to using TAZO, which is
        // better for higher periods due to the first tick being accurate.
        private constant real PERIODTHRESHOLD=0.3 // MUST be below 10.24 seconds.
        
        // Tazo&#039;s number of precached instances. You can go over this during
        // your map at runtime, but it will probably do some small background
        // processing. Precaching just speeds things up a bit.
        private constant integer TAZO_PRECACHE=64
        
        // Tazo uses the low period part of Key Timers 2 to construct triggers
        // over time when precached ones run out. Here you can set the period used.
        private constant real TAZO_CONSTRUCT_PERIOD=0.03125
        
        // If you are using a version of WarCraft 3 without hashtables, set &quot;true&quot;.
        // Using a hashtable reduces runtime handle-count potentially by thousands.
        private constant boolean NO_HASHTABLE=false
    endglobals
    
    //////////////////////////
    // Previous KT2 Globals //
    ////////////////////////////////////////////////////////////////////////////
    // These needed to be moved here for TAZO 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
    endglobals
  //
  static if NO_HASHTABLE then
    globals
        private boolexpr array ToDestroy
    endglobals
  else  // More efficient;
    globals
        private hashtable table = InitHashtable()
    globals
  endif
  //
    globals
        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
    
    //////////////////
    // Previous KT2 //
    ////////////////////////////////////////////////////////////////////////////
    // The KT2 implementation
    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])
              //
              static if NO_HASHTABLE then
                call DestroyBoolExpr(ToDestroy[t_mem])
              endif
              //
            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]
      //
      static if NO_HASHTABLE then
        set ToDestroy[AddRemoveMax]=Boolexpr[t_lastmem]
      endif
      //
        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]&lt;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
      //
      static if NO_HASHTABLE then
        set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
      else  // More efficient;
        set Boolexpr[a_mem]=LoadBooleanExprHandle(table,&#039;FAST&#039;,GetHandleId(Condition(func)))
        if (Boolexpr[a_mem]==null) then
            set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
            call SaveBooleanExprHandle(table,&#039;FAST&#039;,GetHandleId(Condition(func)),Boolexpr[a_mem])
        endif
      endif
      //
        if t_id==a_id then
            set AddRemoveMax=AddRemoveMax+1
            set IsAdd[AddRemoveMax]=true
            set ToAddMem[AddRemoveMax]=a_mem
        else
            if KeyTimerListPointer[a_id]&lt;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 TAZO.
        set t_lastmem=t_mem
        set t_mem=Prev[t_mem]
        return Data[t_lastmem]
    endfunction
    
    public function Static takes nothing returns nothing // For timers that never return true
        set t_mem=Prev[t_mem]
    endfunction
    
    private function KTinit takes nothing returns nothing
        set RemoveInstanceCond=Condition(function RemoveInstance)
    endfunction
    
    //////////
    // TAZO //
    ////////////////////////////////////////////////////////////////////////////
    // KT2 implementation for higher periods (low frequency).
    globals
        private constant integer TAZO_DATAMEM=8190 // Added for KT2 hook. Don&#039;t change.
    endglobals
    
    globals
        private conditionfunc TAZO_LoadDataCond
        private conditionfunc TAZO_RemoveInstanceCond
        
        private timer   array TAZO_TrigTimer
        private integer array TAZO_Data
    endglobals
  //
  static if NO_HASHTABLE then
    globals
        private boolexpr array TAZO_Boolexpr
    endglobals
  else  // More efficient;
    globals
        private boolexpr TAZO_AddBoolexpr
    endglobals
  endif
  //
    globals
        private trigger array TAZO_AvailableTrig
        private integer       TAZO_Max=0
        
        private integer       TAZO_ConstructNext=0
        private trigger array TAZO_ConstructTrig
        private integer array TAZO_ConstructCount
    endglobals
    
    globals//locals
        private integer TAZO_ConKey
    endglobals
    private function TAZO_Constructer takes nothing returns boolean
        set TAZO_ConKey=GetData()
        call TriggerExecute(TAZO_ConstructTrig[TAZO_ConKey])
        set TAZO_ConstructCount[TAZO_ConKey]=TAZO_ConstructCount[TAZO_ConKey]-1
        if TAZO_ConstructCount[TAZO_ConKey]==0 then
            set TAZO_Max=TAZO_Max+1
            set TAZO_AvailableTrig[TAZO_Max]=TAZO_ConstructTrig[TAZO_ConKey]
            set TAZO_TrigTimer[TAZO_ConKey]=CreateTimer()
            call TriggerRegisterTimerExpireEvent(TAZO_AvailableTrig[TAZO_Max],TAZO_TrigTimer[TAZO_ConKey])
            return true
        endif
        return false
    endfunction
  //
  static if NO_HASHTABLE then
  
    globals//locals
        private trigger TAZO_DeadTrig
        private integer TAZO_DeadCount
    endglobals
    private function TAZO_Recycle takes nothing returns boolean
        set TAZO_DeadTrig=GetTriggeringTrigger()
        set TAZO_DeadCount=GetTriggerExecCount(TAZO_DeadTrig)
        call TriggerClearConditions(TAZO_DeadTrig)
        call DestroyBoolExpr(TAZO_Boolexpr[TAZO_DeadCount])
        call PauseTimer(TAZO_TrigTimer[TAZO_DeadCount])
        set TAZO_Max=TAZO_Max+1
        set TAZO_AvailableTrig[TAZO_Max]=TAZO_DeadTrig
        return false
    endfunction
    
  else  // More efficient;
  
    globals//locals
        private trigger TAZO_DeadTrig
    endglobals
    private function TAZO_Recycle takes nothing returns boolean
        set TAZO_DeadTrig=GetTriggeringTrigger()
        call TriggerClearConditions(TAZO_DeadTrig)
        call PauseTimer(TAZO_TrigTimer[GetTriggerExecCount(TAZO_DeadTrig)])
        set TAZO_Max=TAZO_Max+1
        set TAZO_AvailableTrig[TAZO_Max]=TAZO_DeadTrig
        return false
    endfunction
    
  endif
  //
    private function TAZO_LoadData takes nothing returns boolean
        // KT2 Data Hook
        set t_mem=TAZO_DATAMEM
        set Data[TAZO_DATAMEM]=TAZO_Data[GetTriggerExecCount(GetTriggeringTrigger())]
        // End KT2 Data Hook
        return false
    endfunction
    
    private function InitTrigExecCount takes trigger t, integer d returns nothing
        if d&gt;128 then
            call InitTrigExecCount.execute(t,d-128)
            set d=128
        endif
        loop
            exitwhen d==0
            set d=d-1
            call TriggerExecute(t)
        endloop
    endfunction
    
    globals//locals
        private integer TAZO_AddKey
        private trigger TAZO_AddTrigger
    endglobals
    
    public function TAZOadd takes code func, integer data, real period returns nothing
        if TAZO_Max==0 then
            // Failsafe.
            set TAZO_ConstructNext=TAZO_ConstructNext+1
            set TAZO_AddTrigger=CreateTrigger()
            set TAZO_AddKey=TAZO_ConstructNext
            call InitTrigExecCount.execute(TAZO_AddTrigger,TAZO_AddKey)
            set TAZO_TrigTimer[TAZO_AddKey]=CreateTimer()
            call TriggerRegisterTimerExpireEvent(TAZO_AddTrigger,TAZO_TrigTimer[TAZO_AddKey])
        else
            set TAZO_AddTrigger=TAZO_AvailableTrig[TAZO_Max]
            set TAZO_AddKey=GetTriggerExecCount(TAZO_AddTrigger)
            set TAZO_Max=TAZO_Max-1
        endif
        set TAZO_Data[TAZO_AddKey]=data
      //
      static if NO_HASHTABLE then
        set TAZO_Boolexpr[TAZO_AddKey]=And(Condition(func),TAZO_RemoveInstanceCond)
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_LoadDataCond)
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_Boolexpr[TAZO_AddKey])
      else  // More efficient;
        set TAZO_AddBoolexpr=LoadBooleanExprHandle(table,&#039;SLOW&#039;,GetHandleId(Condition(func)))
        if TAZO_AddBoolexpr==null then
            set TAZO_AddBoolexpr=And(Condition(func),TAZO_RemoveInstanceCond)
            call SaveBooleanExprHandle(table,&#039;SLOW&#039;,GetHandleId(Condition(func)),TAZO_AddBoolexpr)
        endif
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_LoadDataCond)
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_AddBoolexpr)
      endif
      //
        call TimerStart(TAZO_TrigTimer[TAZO_AddKey],period,true,null)
        if TAZO_Max&lt;10 then
            set TAZO_ConstructNext=TAZO_ConstructNext+1
            set TAZO_ConstructTrig[TAZO_ConstructNext]=CreateTrigger()
            set TAZO_ConstructCount[TAZO_ConstructNext]=TAZO_ConstructNext
            call KTadd(function TAZO_Constructer,TAZO_ConstructNext,TAZO_CONSTRUCT_PERIOD)
        endif
    endfunction
    
    private function TAZOinit takes nothing returns nothing
        set TAZO_LoadDataCond=Condition(function TAZO_LoadData)
        set TAZO_RemoveInstanceCond=Condition(function TAZO_Recycle)
        // Allow for GetData
        set Next[TAZO_DATAMEM]=TAZO_DATAMEM
        set Prev[TAZO_DATAMEM]=TAZO_DATAMEM
        // End allow for GetData
        loop
            exitwhen TAZO_Max==TAZO_PRECACHE
            set TAZO_ConstructNext=TAZO_ConstructNext+1 // The index.
            set TAZO_Max=TAZO_Max+1 // Will be the same in the initialiser as ConstructNext.
            set TAZO_AvailableTrig[TAZO_Max]=CreateTrigger()
            call InitTrigExecCount.execute(TAZO_AvailableTrig[TAZO_Max],TAZO_ConstructNext)
            set TAZO_TrigTimer[TAZO_ConstructNext]=CreateTimer()
            call TriggerRegisterTimerExpireEvent(TAZO_AvailableTrig[TAZO_Max],TAZO_TrigTimer[TAZO_ConstructNext])
        endloop
    endfunction
    
    ///////////////
    // Interface //
    ////////////////////////////////////////////////////////////////////////////
    // Stitches it all together neatly.
    public function Add takes code func, integer data, real period returns nothing
        if period&lt;PERIODTHRESHOLD then
            call KTadd(func,data,period)
        else
            call TAZOadd(func,data,period)
        endif
    endfunction
    
    private module InitModule
        private static method onInit takes nothing returns nothing
            call KTinit()
            call TAZOinit()
        endmethod
    endmodule
    
    private struct InitStruct extends array
        implement InitModule
    endstruct
endlibrary

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//    End of Key Timers 2
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
Sounds good. Your benchmarks sound about correct, based on what I recall from a year ago (KT2 is very sharply optimised (but for efficiency I'd still recommend T32/TU)).

Now, that implementation is nice if we don't want to touch the interface. Do we know what the actual speed difference is, between the reconstruction of the [LJASS]boolexpr[/LJASS] and the caching of it? The benchmark template you've used before is probably appropriate here... (ie. the one for the [Benchmark] threads in JASS Help.) :)
 
Even if the "Add" function is slower, the "remove" actions will be faster because they omit the "DestroyBoolExpr" and stuff. Hashtables, usually ranking 2-4 times slower than arrays, should pretty much match or beat the speed of an [ljass]And[/ljass] call. Benchmarks coming up, just for the cherry on top, I guess.
 
OK, the results are in. I made a couple of syntactical errors in the version I submitted to you, so here it is revised:

JASS:

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ KT ~~ Key Timers 2 ~~ By Jesus4Lyf ~~ Version 1.8.0 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  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 trigger per instance for all higher
//           periods to allow accurate expirations in a stable and efficient fashion.
//
//    =Pros=
//         - Easy to use.
//         - Fastest attachment loading system (storing in parallel arrays).
//         - Fastest execution system for low periods (all functions on one trigger).
//         - Allows multiple periods to be used.
//         - No H2I. Backwards compatability through patch 1.23 and 1.24.
//
//    =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. <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite1" alt=":)" title="Smile    :)" loading="lazy" data-shortname=":)" />
//
//         - 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&#039;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
    ///////////////
    // Constants //
    ////////////////////////////////////////////////////////////////////////////
    // That bit that users may play with if they know what they&#039;re doing.
    // Not touching these at all is recommended.
    globals
        // Period Threshold is the point at which Key Timers 2 will switch from
        // using the single timer per period mechanism to using TAZO, which is
        // better for higher periods due to the first tick being accurate.
        private constant real PERIODTHRESHOLD=0.3 // MUST be below 10.24 seconds.
        
        // Tazo&#039;s number of precached instances. You can go over this during
        // your map at runtime, but it will probably do some small background
        // processing. Precaching just speeds things up a bit.
        private constant integer TAZO_PRECACHE=64
        
        // Tazo uses the low period part of Key Timers 2 to construct triggers
        // over time when precached ones run out. Here you can set the period used.
        private constant real TAZO_CONSTRUCT_PERIOD=0.03125
        
        // If you are using a version of WarCraft 3 without hashtables, set &quot;true&quot;.
        // Using a hashtable reduces runtime handle-count potentially by thousands.
        private constant boolean NO_HASHTABLE=false
    endglobals
    
    //////////////////////////
    // Previous KT2 Globals //
    ////////////////////////////////////////////////////////////////////////////
    // These needed to be moved here for TAZO 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
    endglobals
  //
  static if NO_HASHTABLE then
    globals
        private boolexpr array ToDestroy
    endglobals
  else  // More efficient;
    globals
        private hashtable table=InitHashtable()
        private key norm_Period
        private key TAZO_Period
    endglobals
  endif
  //
    globals
        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
    
    //////////////////
    // Previous KT2 //
    ////////////////////////////////////////////////////////////////////////////
    // The KT2 implementation
    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])
              //
              static if NO_HASHTABLE then
                call DestroyBoolExpr(ToDestroy[t_mem])
              endif
              //
            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]
      //
      static if NO_HASHTABLE then
        set ToDestroy[AddRemoveMax]=Boolexpr[t_lastmem]
      endif
      //
        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]&lt;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
      //
      static if NO_HASHTABLE then
        set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
      else  // More efficient;
        set Boolexpr[a_mem]=LoadBooleanExprHandle(table,norm_Period,GetHandleId(Condition(func)))
        if (Boolexpr[a_mem]==null) then
            set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
            call SaveBooleanExprHandle(table,norm_Period,GetHandleId(Condition(func)),Boolexpr[a_mem])
        endif
      endif
      //
        if t_id==a_id then
            set AddRemoveMax=AddRemoveMax+1
            set IsAdd[AddRemoveMax]=true
            set ToAddMem[AddRemoveMax]=a_mem
        else
            if KeyTimerListPointer[a_id]&lt;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 TAZO.
        set t_lastmem=t_mem
        set t_mem=Prev[t_mem]
        return Data[t_lastmem]
    endfunction
    
    public function Static takes nothing returns nothing // For timers that never return true
        set t_mem=Prev[t_mem]
    endfunction
    
    private function KTinit takes nothing returns nothing
        set RemoveInstanceCond=Condition(function RemoveInstance)
    endfunction
    
    //////////
    // TAZO //
    ////////////////////////////////////////////////////////////////////////////
    // KT2 implementation for higher periods (low frequency).
    globals
        private constant integer TAZO_DATAMEM=8190 // Added for KT2 hook. Don&#039;t change.
    endglobals
    
    globals
        private conditionfunc TAZO_LoadDataCond
        private conditionfunc TAZO_RemoveInstanceCond
        
        private timer   array TAZO_TrigTimer
        private integer array TAZO_Data
    endglobals
  //
  static if NO_HASHTABLE then
    globals
        private boolexpr array TAZO_Boolexpr
    endglobals
  else  // More efficient;
    globals
        private boolexpr TAZO_AddBoolexpr
    endglobals
  endif
  //
    globals
        private trigger array TAZO_AvailableTrig
        private integer       TAZO_Max=0
        
        private integer       TAZO_ConstructNext=0
        private trigger array TAZO_ConstructTrig
        private integer array TAZO_ConstructCount
    endglobals
    
    globals//locals
        private integer TAZO_ConKey
    endglobals
    private function TAZO_Constructer takes nothing returns boolean
        set TAZO_ConKey=GetData()
        call TriggerExecute(TAZO_ConstructTrig[TAZO_ConKey])
        set TAZO_ConstructCount[TAZO_ConKey]=TAZO_ConstructCount[TAZO_ConKey]-1
        if TAZO_ConstructCount[TAZO_ConKey]==0 then
            set TAZO_Max=TAZO_Max+1
            set TAZO_AvailableTrig[TAZO_Max]=TAZO_ConstructTrig[TAZO_ConKey]
            set TAZO_TrigTimer[TAZO_ConKey]=CreateTimer()
            call TriggerRegisterTimerExpireEvent(TAZO_AvailableTrig[TAZO_Max],TAZO_TrigTimer[TAZO_ConKey])
            return true
        endif
        return false
    endfunction
    
    globals//locals
        private trigger TAZO_DeadTrig
    endglobals
  //
  static if NO_HASHTABLE then
    globals//locals
        private integer TAZO_DeadCount
    endglobals
  endif
  //
    private function TAZO_Recycle takes nothing returns boolean
        set TAZO_DeadTrig=GetTriggeringTrigger()
      //
      static if NO_HASHTABLE then
        set TAZO_DeadCount=GetTriggerExecCount(TAZO_DeadTrig)
        call TriggerClearConditions(TAZO_DeadTrig)
        call DestroyBoolExpr(TAZO_Boolexpr[TAZO_DeadCount])
        call PauseTimer(TAZO_TrigTimer[TAZO_DeadCount])
      else  // More efficient;
        call TriggerClearConditions(TAZO_DeadTrig)
        call PauseTimer(TAZO_TrigTimer[GetTriggerExecCount(TAZO_DeadTrig)])
      endif
      //
        set TAZO_Max=TAZO_Max+1
        set TAZO_AvailableTrig[TAZO_Max]=TAZO_DeadTrig
        return false
    endfunction
    
    private function TAZO_LoadData takes nothing returns boolean
        // KT2 Data Hook
        set t_mem=TAZO_DATAMEM
        set Data[TAZO_DATAMEM]=TAZO_Data[GetTriggerExecCount(GetTriggeringTrigger())]
        // End KT2 Data Hook
        return false
    endfunction
    
    private function InitTrigExecCount takes trigger t, integer d returns nothing
        if d&gt;128 then
            call InitTrigExecCount.execute(t,d-128)
            set d=128
        endif
        loop
            exitwhen d==0
            set d=d-1
            call TriggerExecute(t)
        endloop
    endfunction
    
    globals//locals
        private integer TAZO_AddKey
        private trigger TAZO_AddTrigger
    endglobals
    
    public function TAZOadd takes code func, integer data, real period returns nothing
        if TAZO_Max==0 then
            // Failsafe.
            set TAZO_ConstructNext=TAZO_ConstructNext+1
            set TAZO_AddTrigger=CreateTrigger()
            set TAZO_AddKey=TAZO_ConstructNext
            call InitTrigExecCount.execute(TAZO_AddTrigger,TAZO_AddKey)
            set TAZO_TrigTimer[TAZO_AddKey]=CreateTimer()
            call TriggerRegisterTimerExpireEvent(TAZO_AddTrigger,TAZO_TrigTimer[TAZO_AddKey])
        else
            set TAZO_AddTrigger=TAZO_AvailableTrig[TAZO_Max]
            set TAZO_AddKey=GetTriggerExecCount(TAZO_AddTrigger)
            set TAZO_Max=TAZO_Max-1
        endif
        set TAZO_Data[TAZO_AddKey]=data
      //
      static if NO_HASHTABLE then
        set TAZO_Boolexpr[TAZO_AddKey]=And(Condition(func),TAZO_RemoveInstanceCond)
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_LoadDataCond)
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_Boolexpr[TAZO_AddKey])
      else  // More efficient;
        set TAZO_AddBoolexpr=LoadBooleanExprHandle(table,TAZO_Period,GetHandleId(Condition(func)))
        if TAZO_AddBoolexpr==null then
            set TAZO_AddBoolexpr=And(Condition(func),TAZO_RemoveInstanceCond)
            call SaveBooleanExprHandle(table,TAZO_Period,GetHandleId(Condition(func)),TAZO_AddBoolexpr)
        endif
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_LoadDataCond)
        call TriggerAddCondition(TAZO_AddTrigger,TAZO_AddBoolexpr)
      endif
      //
        call TimerStart(TAZO_TrigTimer[TAZO_AddKey],period,true,null)
        if TAZO_Max&lt;10 then
            set TAZO_ConstructNext=TAZO_ConstructNext+1
            set TAZO_ConstructTrig[TAZO_ConstructNext]=CreateTrigger()
            set TAZO_ConstructCount[TAZO_ConstructNext]=TAZO_ConstructNext
            call KTadd(function TAZO_Constructer,TAZO_ConstructNext,TAZO_CONSTRUCT_PERIOD)
        endif
    endfunction
    
    private function TAZOinit takes nothing returns nothing
        set TAZO_LoadDataCond=Condition(function TAZO_LoadData)
        set TAZO_RemoveInstanceCond=Condition(function TAZO_Recycle)
        // Allow for GetData
        set Next[TAZO_DATAMEM]=TAZO_DATAMEM
        set Prev[TAZO_DATAMEM]=TAZO_DATAMEM
        // End allow for GetData
        loop
            exitwhen TAZO_Max==TAZO_PRECACHE
            set TAZO_ConstructNext=TAZO_ConstructNext+1 // The index.
            set TAZO_Max=TAZO_Max+1 // Will be the same in the initialiser as ConstructNext.
            set TAZO_AvailableTrig[TAZO_Max]=CreateTrigger()
            call InitTrigExecCount.execute(TAZO_AvailableTrig[TAZO_Max],TAZO_ConstructNext)
            set TAZO_TrigTimer[TAZO_ConstructNext]=CreateTimer()
            call TriggerRegisterTimerExpireEvent(TAZO_AvailableTrig[TAZO_Max],TAZO_TrigTimer[TAZO_ConstructNext])
        endloop
    endfunction
    
    ///////////////
    // Interface //
    ////////////////////////////////////////////////////////////////////////////
    // Stitches it all together neatly.
    public function Add takes code func, integer data, real period returns nothing
        if period&lt;PERIODTHRESHOLD then
            call KTadd(func,data,period)
        else
            call TAZOadd(func,data,period)
        endif
    endfunction
    
    private module InitModule
        private static method onInit takes nothing returns nothing
            call KTinit()
            call TAZOinit()
        endmethod
    endmodule
    
    private struct InitStruct extends array
        implement InitModule
    endstruct
endlibrary

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//    End of Key Timers 2
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Due to the massive lag caused by the generation of boolean expressions, I had to force-quit and then re-open the application to perform each new test. I did this 15-20 times, getting the same results back each time ~ the version I submitted has a 5% faster add period than the current implentation of KeyTimers2.

I swapped the order they were tested in so that half of the tests had the current KT library first while the other half had it second (to see if one of the calls slowed the other one down). My version is faster, either way it's sliced.

Attached is the benchmark map I used.
 

Attachments

  • bench2.w3x
    36.1 KB · Views: 423
Another issue is the TriggerAddCondition and TriggerRemoveCondition, which would cause this to actually be slower than timers and slower than timers with a hashtable read and get handle id call.

100 trigger add conditions with a trigger clear conditions and 1 trigger evaluation is much slower than 100 expiring timers : P.

The best way to handle this is multiple trigger evaluations. For each timeout, there is a segment queue of triggers. What this means is that you loop thru all expiring queues and evaluate the triggers on the first nodes. Then push the first node back etc. In your case, since you already really have these expiring queues, all you'd want to do is move the first node to the back of the queue and evaluate it. From here, all you'd need to do is remove trigger conditions of destroyed timers. When a new timer is created, you adds its code the trigger of the back of the queue. If it doesn't fit into that trigger (dif remaining time), create a new trigger.

I'm actually trying to solve the problem of removing trigger conditions for expired timers that were previous paused (different remaining time than timeout) or are destroyed w/o a loop.
 
It was proven here that this is 100% useless :\

http://www.thehelper.net/forums/showthread.php/166468-Timer-System-Design?p=1365848#post1365848

See how with 5 timeouts between .00125 and 0.00625, Key Timers 2 barely performs better than native data attachment. When it gets to 50 timeouts, KT2 crashes, and those 50 timeouts are below the .3 threshold for KT2, meaning that it didn't use TAZO.



There is absolutely no case where you'd ever want to use KT2. Given that there are 0 cases for ever using this, I think this merits another look for its viability ;p.
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • The Helper The Helper:
    alternatively if you not making at least 23 an hour you could work in an Aldi warehouse
  • Varine Varine:
    Yeah I've been thinking about using AI for shit. I'm on vacation next week so I'm going to spend some time reorganizing everything and getting all my shit back in order
  • Varine Varine:
    lol I technically make less than 23 right now because I'm on salary and am there all the time, but it's a lot more than a normal wage still. I also have a meeting soon to renegotiate my pay because I want about a 25% increase to account for what I'm actually doing or a re-evaluation of our duties so that that my responsibilities are more in line with my pay. Depending on how that goes I'm prepared to give notice and move on, I don't mind taking less money so I'd have time for the rest of my life, but I'd rather they just fucking pay me enough to justify the commitment on my end. Plus right now I hold pretty much all the cards since I'm the only one actually qualified for my position.
    +1
  • Varine Varine:
    The other chef was there before me and got his position by virtue of being the only adult when the old general manager got married and didn't want to deal with the kitchen all the time, and happened to be in the position when the GM quit. New GM is fine with front of house but doesn't know enough about the kitchen side to really do anything or notice that I'm the one primarily maintaining it. Last time I left they hired like 3 people to replace me and there was still a noticeable drop in quality, so I got offered like 6 dollars an hour more and a pretty significant summer bonus to come back
  • Varine Varine:
    So honestly even if I leave I think it would last a couple of months until it's obvious that I am not exactly replaceable and then I would be in an even better position.
  • Varine Varine:
    But as of right now I have two other job offers that are reasonably close to what my hourly equivalency would be, and I would probably have more time for my other projects. The gap would be pretty easy to fill up if I had time to do my side jobs. I use to do some catering and private events, personal lessons, consultations, ect, and I charge like 120 an hour for those. But they aren't consistent enough for a full time job, too small of a town. And I'm not allowed to move for another year until my probation is over
  • Varine Varine:
    I guess I could get it transferred, but that seems like a hassle.
  • Varine Varine:
    Plus I have a storage room full of broken consoles I need to fix. I need to build a little reflow oven so I can manufacture some mod chips still, but it'll get there.
    +1
  • Varine Varine:
    I would like to get out of cooking in general at some point in the next ten years, but for the time being I can make decent money and pump that into savings. I've been taking some engineering classes online, but those aren't exactly career oriented at the moment, but I think getting into electronic or computer engineering of some sort would be ideal. I'm just going to keep taking some classes here and there until there's one that I am really into.
    +2
  • The Helper The Helper:
    There is money in fixing and reselling consoles. Problem is people know that so they are doing it
  • The Helper The Helper:
    If you can find a source of broken consoles though you can make money fixing them - sometimes some big money
  • Varine Varine:
    I was buying them on Ebay, but it's pretty competitive, so more recently I've just been telling the thrift stores to call me and I will come take all their electronics they can't sell. I've volunteered there before and people use them like a dump sometimes, and so they just have a massive amount of broken shit they throw away
  • Varine Varine:
    The local GoodWill was pretty into it, surprisingly the animal shelter store was not. The lady I talked to there seemed to think I was trying to steal stuff or something, she wasn't very nice about it. Like I'm just trying to stop you having to throw a bunch of electronics away, if you can sell it great. I'd probably pay you for the broken shit too if you wanted
  • Varine Varine:
    Then I make posts on Facebook yard sale pages sometimes saying I want your old electronics, but Facebook being Facebook people on there are also wary about why I want it, then want a bunch of money like it's going to be very worth it
  • Varine Varine:
    Sooner than later I'm going to get my archives business going a little more. I need some office space that is kind of hard to get at the moment, but without it, I have to be like "Yeah so go ahead and just leave your family heirlooms and hundred year old photographs here at my shitty apartment and give me a thousand dollars, and next month I'll give you a thumb drive. I promise I'll take care of them!"
    +1
  • Varine Varine:
    I can do some things with them at their home, but when people have thousands of slides and very delicate newspaper clippings and things, not really practical. I
  • Varine Varine:
    I would be there for days, even with my camera set up slides can take a long time, and if they want perfect captures I really need to use my scanners that are professionally made for that. My camera rig works well for what it is, but for enlargements and things it's not as good.
  • Varine Varine:
    I've only had a couple clients with that so far, though. I don't have a website or anything yet though.
  • Varine Varine:
    Console repair can be worthwhile, but it's also not a thing I can do at scale in my house. I just don't have room for the equipment. I need an office that I can segregate out for archival and then electronic restoration.
  • Varine Varine:
    But in order for that to be real, I need more time, and for more time I need to work less, and to work less I need a different job, and for a different job I need more money to fall back on so that I can make enough to just pay like, rent and utilities and use my savings to find these projects
    +1
  • Varine Varine:
    Another couple years. I just need to take it slow and it'll get there.
  • jonas jonas:
    any chance to get that stolen money back?
  • jonas jonas:
    Maybe you can do console repair just as a side thing, especially if there's so much competition business might be slow. Or do you need a lot of special equipment for that?
  • jonas jonas:
    I recently bought a used sauna and the preowner told me some component is broken, I took a look and it was just a burnt fuse, really cheap to fix. I was real proud of my self since I usually have two left hands for this kinda stuff :p
  • tom_mai78101 tom_mai78101:
    I am still playing Shapez 2. What an awful thing to happen, Varine, and hopefully everything has been sorted out soon. Always use multi-factor authentication whenever you have the opportunity to do so.

      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