• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!

[System] Timer Tools

Level 7
Joined
Dec 3, 2006
Messages
339
Could you perhaps explain why you would switch between Tt, TimerUtils(the new version by magtherion), and CTL. Like when is it best to use what?
I think CTL is very fast periodic timers; but the differentiation between TimerUtils and Tt is a bit harder for me.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You'd want to use TimerUtils when you have a low chance of merger. If you have lots of 1 shot timers that are all different periods and all occur sporadically, you'd probably want to use TimerUtils rather than Timer Tools. If you have lots of 1 shot timers that only have a few different periods, then you'd probably want to use Timer Tools.


The overhead of creation/destruction is pretty decent, so you want to weigh that overhead with the overhead of individual timers ;P. The better the merge chance, the more you should use Timer Tools over Timer Utils ; ).


Also for CTL, it's whenever you have a period of .03125, repeating or not : p.


Also keep in mind that smaller periods on Timer Tools have a 100% chance to merge.


Finally, if you are worried about super accuracy, you should use TimerUtils. Timer Tools and CTL maximize merging using feasible inaccuracy =P.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Get ready for Timer Tools v2!


Recoded the entire library! Centralized everything like I wanted to : ). Modules are now super, super tiny. You don't even need modules anymore as you can use the functions if you want to : p.

Tested this for leaks, bugs, and etc... I put this thing through the wringer ; P. I need this thing to work for my map.

CTQ and CTM share the same instancing and merging. LCTQ does share the same merging as it does 0 merging since it specializes in 1 shot timers (merging and what not has too much overhead to be worth it for a 1 shot timer).

CTQ's creation and destruction is a little bit slower than CTM, but it has slightly ini for its loops (array read instead of hashtable read).


I have not tested CTQ or CLTQ yet, but I will be using CTQ in AuraStruct in my map tomorrow when I get up, so I'll be seeing if it works or not later. The code in it is extremely simple, so I don't anticipate any problems.*

edit
Ok, this can be improved a little bit more and CTQ will bug as it is now. With next update, CTM and CTQ will be the same speed, so I'll be removing CTQ. There will also be no hashtable read inside of the module! This thing will end up owning the speed of old Timer Tools ^)^.

edi
ALAS, I have found the Timer Tools bug that was plaguing CTQ and is now plaguing CTM with the update that isn't up yet!! Now I must figure out how to fix it...

edit
Still fixing the bug... it runs deep >.<.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Get ready for Timer Tools v2!


Recoded the entire library! Centralized everything like I wanted to : ). Modules are now super, super tiny. You don't even need modules anymore as you can use the functions if you want to : p.

Tested this for leaks, bugs, and etc... I put this thing through the wringer ; P. I need this thing to work for my map.

CTQ and CTM share the same instancing and merging. LCTQ does share the same merging as it does 0 merging since it specializes in 1 shot timers (merging and what not has too much overhead to be worth it for a 1 shot timer).

CTQ's creation and destruction is a little bit slower than CTM, but it has slightly ini for its loops (array read instead of hashtable read).


I have not tested CTQ or CLTQ yet, but I will be using CTQ in AuraStruct in my map tomorrow when I get up, so I'll be seeing if it works or not later. The code in it is extremely simple, so I don't anticipate any problems.*

edit
Ok, this can be improved a little bit more and CTQ will bug as it is now. With next update, CTM and CTQ will be the same speed, so I'll be removing CTQ. There will also be no hashtable read inside of the module! This thing will end up owning the speed of old Timer Tools ^)^.

edi
ALAS, I have found the Timer Tools bug that was plaguing CTQ and is now plaguing CTM with the update that isn't up yet!! Now I must figure out how to fix it...

edit
Still fixing the bug... it runs deep >.<.

edit
FIXED IT, FUAHAHAHAHAHA

to those of you who want to test for bugs, try to break this. It will break if you see an error message and messages stop spewing. This debug version disables the system in the event of an error.

JASS:
library Tt /* v2.0.0.0
*************************************************************************************
*
*    Timer tools timers use a special merging algorithm. When two timers merge, they
*    both expire on the same timer. The first tick accuracy is based on the size of the timer's timeout. 
*    The larger the timeout, the more inaccurate the first tick can be. The tick only becomes inaccurate
*    if it merges with another timer.
*
************************************************************************************
*
*    SETTINGS
*/
globals
    /*************************************************************************************
    *
    *                    RELATIVE_MERGE
    *
    *    Effects the accuracy of first tick. The smaller the merge value, the less chance two
    *    timers have of sharing the same first tick, which leads to worse performance.
    *    However, a larger merge value decreases the accuracy of the first tick.
    *
    *    Formula: Log(RELATIVE_MERGE/timeout)/100*timeout
    *        Log(64000/3600)/100*3600=44.995589035797596625536391805959 seconds max off
    *
    *************************************************************************************/
    private constant real RELATIVE_MERGE=64000

    /*************************************************************************************
    *
    *                    CONSTANT_MERGE
    *
    *    Effects the accuracy of the first tick. This is a constant merge. It just adds
    *    this value to the relative merge.
    *
    *    The max 3600's first tick can be off is 44.995589035797596625536391805959+.03
    *    or 45.295589035797596625536391805959 seconds.
    *
    *************************************************************************************/
    private constant real CONSTANT_MERGE=.03//.3
endglobals
/*
************************************************************************************
*
*    Functions
*
*       function GetExpired takes nothing returns integer
*           Gets the expiring timer. This is not the expiring timer method instance! This is read
*           inside of GetTimerFirstInstance, which is why timer instances can only be retrieved inside of
*           an expiring timer method.
*
*           Boolean expressions on the same timer will expire for that same timer. Use timer methods instead.
*
*       function TimerMethodAddInstance takes real timeout, integer timerMethod returns integer
*           Adds a new instance to a timer method. These must be looped through inside of the boolean expression method.
*           Returns a timer instance.
*       function TimerMethodRemoveInstance takes integer timerInstance returns nothing
*           Removes an instance from a timer method. Timer method is destroyed after it is done expiring when no instances
*           are left.
*       function CreateTimerMethod takes boolexpr method returns integer
*           Creates a new timer method from a boolean expression. This boolean expression is run whenever the timer
*           expires.
*
*       function TimerGetElapsedEx takes integer timerInstance returns real
*       function TimerGetRemainingEx takes integer timerInstance returns real
*       function TimerGetTimeoutEx takes integer timerInstance returns real
*
*       function GetTimerFirstInstance takes integer timerMethod returns integer
*           Gets first timer instance in a timer method. Can only be called within an expiring timer method.
*       function GetTimerNext takes integer timerInstance returns integer
*           Gets next timer instance given a timer instance.
*           Sentinel is 0.
*
************************************************************************************
*
*   struct TimerList extends array
*
*       method register takes boolexpr b returns integer
*           Registers a boolean expression to timer
*       method unregister takes integer i returns nothing
*           Unregisters boolean expression from timer
*           Timer is automatically destroyed if no boolean expressions are left
*
************************************************************************************
*
*   struct Timer extends array
*       static method operator [] takes real timeout returns thistype
*           Converts a timeout to a timer group
*       method operator list takes nothing returns TimerList
*           Creates a new timer in the timer group if there are no viable currently
*           existing timers. Will return an existing timer if that timer either expires soon enough
*           or has enough time left to be close enough to the timeout.
*
************************************************************************************
*
*   Modules
*
*       module CTM (optional)
*           locals
*       module CTMExpire (not optional)
*           expiration code
*       module CTMNull (optional)
*           null locals
*       module CTMEnd (not optional)
*           static method create takes real timeout returns thistype
*           method destroy takes nothing returns nothing
*           method operator elapsed takes nothing returns real
*           method operator remaining takes nothing returns real
*           method operator timeout takes nothing returns real
*
*       module CLTQ
*           expiration code
*       module CLTQEnd
*           static method create takes nothing returns thistype
*           method destroy takes nothing returns nothing
*           method operator elapsed takes nothing returns real
*           method operator remaining takes nothing returns real
*
*           Interface:
*               private static constant real timeout
*
************************************************************************************/
    globals
        //list
        private integer array lf
        private integer array ln
        private integer array lp
        private integer array lr
        private integer lc = 0
        private timer array tm
        
        //nodes
        private trigger array nt
        private integer array nc
        private integer array nh
        private boolean array aa
        private boolean array nr
        
        //trigger conditions
        private integer ntcc = 0
        private integer array ntcr
        private triggercondition array ntc
        
        //trigger condition add after
        private integer array paf
        private integer array pan
        private integer array pap
        private boolexpr array pab
        
        //trigger condition destroy after
        private integer array ds
        private integer dsc = 0
        
        //expiring timer
        private integer exp = 0
        
        //module
        private integer mtfr = 0
        private boolexpr array mb
        
        private integer mnf = 0
        private integer mnft = 0
        private integer mnp = 0
        
            //list
            private hashtable mlf = InitHashtable()
            private hashtable mlf2 = InitHashtable()
            private hashtable mnt = InitHashtable()
            private hashtable mnc = InitHashtable()
            private integer array mln
            private integer array mlp
            
            //true list
            private integer array mlnd
            private integer array mlpd
            
            //add after
            private integer array mlnp
            private integer array mlpp
            private boolean array mlap
            private integer array mlfp
            
            private integer array mlr
            private integer mlc = 0
            
            //nodes
            private integer array mnh
            private integer array mno
            
            //destroy after
            private integer array mds
            private integer mdsc = 0
            private boolean array mdb
        
        private boolean array al
        private boolean array alm
    endglobals
    
    //hiveworkshop.com/forums/jass-functions-413/snippet-natural-logarithm-108059/
    //credits to BlinkBoy
    private function Ln takes real a returns real
        local real s=0
        loop
        exitwhen a<2.71828
        set a=a/2.71828
        set s=s+1
        endloop
        return s+(a-1)*(1+8/(1+a)+1/a)/6
    endfunction
    
    private function AL takes nothing returns integer
        local integer i = lr[0]
        
        if (0 == i) then
            set lc = lc + 1
            set al[lc] = true
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Allocated Node: "+I2S(lc))
            return lc
        endif
        
        set lr[0] = lr[i]
        
        set al[i] = true
        
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Allocated Node: "+I2S(i))
        
        return i
    endfunction
    
    function GetExpired takes nothing returns integer
        return exp
    endfunction
    
    globals
        private boolean en = true
    endglobals
    private function DIS takes nothing returns nothing
        set en = false
        loop
            exitwhen 0 == lc
            call PauseTimer(tm[lc])
            set lc = lc - 1
        endloop
    endfunction
    private function DELM takes integer i returns nothing
        if (en) then
            if (alm[i]) then
                set mlr[i] = mlr[0]
                set mlr[0] = i
                set alm[i] = false
            else
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DOUBLE FREE: "+I2S(i))
                call DIS()
            endif
        endif
    endfunction
    //! textmacro TIMER_TOOLS_ADD_NODE takes D
        if (en) then
            if (alm[i]) then
                set mln$D$[i] = 0
                if (0 == mlp$D$[t]) then
                    set mlp$D$[i] = t
                    set mln$D$[t] = i
                else
                    set mlp$D$[i] = mlp$D$[t]
                    set mln$D$[mlp$D$[i]] = i
                endif
                set mlp$D$[t] = i
                
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"ADD: "+I2S(mlp$D$[t])+"->"+I2S(t)+"->"+I2S(mln$D$[t])+" on "+I2S(mnh[i]))
                set i = mlp$D$[t]
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"ADD 1: "+I2S(mlp$D$[i])+"->"+I2S(i)+"->"+I2S(mln$D$[i])+" on "+I2S(mnh[i]))
                set i = mln$D$[t]
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"ADD 2: "+I2S(mlp$D$[i])+"->"+I2S(i)+"->"+I2S(mln$D$[i])+" on "+I2S(mnh[i]))
            else
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"ATTEMPTED TO ADD NULL NODE: "+I2S(i)+" on "+I2S(mnh[i]))
                call DIS()
            endif
        endif
    //! endtextmacro
    private function ADDM takes integer i, integer t returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"EXP LIST")
        //! runtextmacro TIMER_TOOLS_ADD_NODE("")
    endfunction
    private function ADDMD takes integer i, integer t returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"TRUE LIST")
        //! runtextmacro TIMER_TOOLS_ADD_NODE("d")
    endfunction
    private function ADDMP  takes integer i, integer t returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"ADD AFTER LIST")
        //! runtextmacro TIMER_TOOLS_ADD_NODE("p")
    endfunction
    //! textmacro TIMER_TOOLS_REMOVE_NODE takes D
        if (en) then
            if (alm[i]) then
                if (0 != f) then
                    if (0 == mln$D$[i]) then
                        set mlp$D$[f] = mlp$D$[i]
                    else
                        set mlp$D$[mln$D$[i]] = mlp$D$[i]
                    endif
                    
                    set mln$D$[mlp$D$[i]] = mln$D$[i]
                    
                    if (f == mln$D$[f]) then
                        set mln$D$[f] = 0
                        set mlp$D$[f] = 0
                    else
                        set mln$D$[mlp$D$[f]] = 0
                    endif
                endif
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"REM: "+I2S(mlp$D$[f])+"->"+I2S(f)+"->"+I2S(mln$D$[f])+" on "+I2S(mnh[i]))
                set i = mlp$D$[f]
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"REM 1: "+I2S(mlp$D$[i])+"->"+I2S(i)+"->"+I2S(mln$D$[i])+" on "+I2S(mnh[i]))
                set i = mln$D$[f]
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"REM 2: "+I2S(mlp$D$[i])+"->"+I2S(i)+"->"+I2S(mln$D$[i])+" on "+I2S(mnh[i]))
            else
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"ATTEMPTED TO REMOVE NULL NODE: "+I2S(i)+" on "+I2S(mnh[i]))
                call DIS()
            endif
        endif
    //! endtextmacro
    private function REMM takes integer i, integer f returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"EXP LIST")
        //! runtextmacro TIMER_TOOLS_REMOVE_NODE("")
    endfunction
    private function REMMD takes integer i, integer f returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"TRUE LIST")
        //! runtextmacro TIMER_TOOLS_REMOVE_NODE("d")
    endfunction
    private function REMMP takes integer i, integer f returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"REMOVE AFTER LIST")
        //! runtextmacro TIMER_TOOLS_REMOVE_NODE("p")
    endfunction
    private function DAF takes integer i returns nothing
        if (en) then
            if (not mdb[i]) then
                set mdb[i] = true
                set mds[mdsc] = i
                set mdsc = mdsc + 1
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Marked to Deallocate After: "+I2S(i))
            else
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DOUBLE FREE ATTEMPT: "+I2S(i))
                call DIS()
            endif
        endif
    endfunction
    
    private function EXP takes nothing returns nothing
        local integer i = R2I(TimerGetTimeout(GetExpiredTimer())*100)
        local integer n = lf[i]
        local integer t
        local integer f
        set lf[i] = ln[n]
        
        if (not al[n]) then
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"BUG NODE: "+I2S(n))
            call DIS()
            return
        endif
        
        set nr[n] = true
        set exp = n
        call TriggerEvaluate(nt[n])
        set exp = 0
        set nr[n] = false
        
        set i = paf[n]
        set paf[n] = 0
        loop
            exitwhen 0 == i
            set ntc[i] = TriggerAddCondition(nt[n], pab[i])
            set pab[i] = null
            set ntcr[i] = ntcr[0]
            set ntcr[0] = i
            set i = pan[i]
        endloop
        
        set i = mlfp[n]
        set mlfp[n] = 0
        loop
            exitwhen 0 == i
            set t = LoadInteger(mlf, mno[i], mnh[i])
            
            call ADDM(i, t)
            
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Added After: "+I2S(i))
            set i = mlnp[i]
        endloop
        
        set i = dsc
        loop
            exitwhen 0 == i
            set i = i - 1
            call TriggerRemoveCondition(nt[n], ntc[ds[i]])
            set ntc[ds[i]] = null
            set nc[n] = nc[n] - 1
        endloop
        set dsc = 0
        
        set t = mdsc
        loop
            exitwhen 0 == t
            set t = t - 1
            set i = mds[t]
            set mdb[i] = false
            set f = LoadInteger(mlf, mno[i], mnh[i])
            
            if (f == i) then
                set f = LoadInteger(mlf2, mno[i], mnh[i])
                if (f == i) then
                    set f = 0
                endif
                call SaveInteger(mlf, mno[i], mnh[i], f)
            endif
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"First: "+I2S(i)+" -> "+I2S(f))
            
            call REMM(i, f)
            call DELM(i)
            
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Deallocated After: "+I2S(i)+" on "+I2S(n))
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Deallocated After Counter: "+I2S(t))
            
            set mlap[i] = false
        endloop
        set mdsc = 0
        
        if (0 == nc[n]) then
            call PauseTimer(tm[n])
            call DestroyTimer(tm[n])
            set tm[n] = null
            
            if (lf[nh[n]] == n) then
                set lf[nh[n]] = ln[n]
            endif
            
            set ln[lp[n]] = ln[n]
            set lp[ln[n]] = lp[n]
            
            if (ln[n] == n) then
                set lf[nh[n]] = 0
            endif
            
            call DestroyTrigger(nt[n])
            set nt[n] = null
            
            set lr[n] = lr[0]
            set lr[0] = n
            set al[n] = false
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Deallocated Node After: "+I2S(n))
        endif
    endfunction
    
    struct TimerList extends array
        method register takes boolexpr b returns integer
            local integer i = ntcr[0]
            if (0 == i) then
                set i = ntcc + 1
                set ntcc = i
            else
                set ntcr[0] = ntcr[i]
            endif
            
            set nc[this] = nc[this] + 1
            
            if (aa[this]) then
                if (0 == paf[this]) then
                    set paf[this] = i
                    set pan[i] = 0
                    set pap[i] = i
                else
                    set pan[i] = 0
                    set pap[i] = pap[paf[this]]
                    set pan[pap[i]] = i
                    set pap[paf[this]] = i
                endif
                set pab[i] = b
            else
                set ntc[i] = TriggerAddCondition(nt[this], b)
            endif
            
            return i
        endmethod
        method unregister takes integer i returns nothing
            if (not nr[this]) then
                set nc[this] = nc[this] - 1
            
                if (0 == nc[this]) then
                    call PauseTimer(tm[this])
                    call DestroyTimer(tm[this])
                    set tm[this] = null
                    
                    if (lf[nh[this]] == this) then
                        set lf[nh[this]] = ln[this]
                    endif
                    
                    set ln[lp[this]] = ln[this]
                    set lp[ln[this]] = lp[this]
                    
                    if (ln[this] == this) then
                        set lf[nh[this]] = 0
                    endif
                    
                    call DestroyTrigger(nt[this])
                    set nt[this] = null
                    
                    set lr[this] = lr[0]
                    set lr[0] = this
                    set al[this] = false
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Deallocated Node: "+I2S(this))
                endif
            endif
            
            if (null == pab[i]) then
                if (nr[this]) then
                    set ds[dsc] = i
                    set dsc = dsc + 1
                else
                    call TriggerRemoveCondition(nt[this], ntc[i])
                    set ntc[i] = null
                endif
            else
                if (paf[this] == i) then
                    set paf[this] = pan[i]
                endif
                
                if (0 == pan[i]) then
                    set pap[paf[this]] = pap[i]
                else
                    set pap[pan[i]] = pap[i]
                endif
                
                if (0 == pap[i]) then
                    set pan[paf[this]] = pan[i]
                else
                    set pan[pap[i]] = pan[i]
                endif
                
                set pab[i] = null
                
                set ntcr[i] = ntcr[0]
                set ntcr[0] = i
            endif
        endmethod
    endstruct
    struct Timer extends array
        static method operator [] takes real timeout returns thistype
            local integer i = R2I(timeout*100)
            
            if (0 == i) then
                return 1
            elseif (8191 < i) then
                return 8191
            endif
            
            return i
        endmethod
        
        method operator list takes nothing returns TimerList
            local integer t = lf[this]
            local integer l
            local real tl
            local real a
            local integer n
            local real rem
            if (0 == t) then
                set t = AL()
                set tm[t] = CreateTimer()
                call TimerStart(tm[t], this/100., true, function EXP)
                set lf[this] = t
                set lp[t] = t
                set ln[t] = t
                set nt[t] = CreateTrigger()
                set nh[t] = this
                set aa[t] = false
                return t
            endif
            
            set l = lp[t]
            set a = this/100.
            set tl = Ln(RELATIVE_MERGE/a)/230.258509*a+CONSTANT_MERGE
            set rem = TimerGetRemaining(tm[l])
            if (TimerGetElapsed(tm[t]) < tl) then
                set aa[t] = false
                return t
            elseif (rem < tl) then
                set aa[l] = true
                return l
            elseif (TimerGetTimeout(tm[t]) - rem < tl) then
                set aa[l] = false
                return l
            endif
            
            set n = AL()
            set tm[n] = CreateTimer()
            if (en) then
                call TimerStart(tm[n], a, true, function EXP)
            endif
            set ln[l] = n
            set lp[t] = n
            set ln[n] = t
            set lp[n] = l
            set nt[n] = CreateTrigger()
            set nh[n] = this
            set aa[n] = false
            
            return n
        endmethod
    endstruct
    
    private function ALM takes nothing returns integer
        local integer i = mlr[0]
        
        if (alm[i]) then
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DOUBLE ALLOCATE: "+I2S(i))
            call DIS()
        endif
        
        if (0 == i) then
            set mlc = mlc + 1
            set alm[mlc] = true
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Allocated: "+I2S(mlc))
            return mlc
        endif
        
        set mlr[0] = mlr[i]
        set alm[i] = true
        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Allocated: "+I2S(i))
        
        return i
    endfunction
    
    private function RegisterMethod takes integer this, integer t, integer i, boolean after returns nothing
        call ADDMD(i, t)
        
        if (not after) then
            call ADDM(i, t)
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Adding: "+I2S(i))
        else
            set t = mlfp[this]
            if (0 == t) then
                set mlfp[this] = i
                set mlnp[i] = 0
                set mlpp[i] = 0
            else
                call ADDMP(i, t)
            endif
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Marked To Add After: "+I2S(i))
        endif
    endfunction
    
    function TimerMethodAddInstance takes real a, integer o returns integer
        local Timer i = Timer[a]
        local TimerList this = i.list
        local integer t = LoadInteger(mlf2, o, this)
        local integer n
        
        call SaveInteger(mnc, o, this, LoadInteger(mnc, o, this) + 1)
        
        set mnp = this
        
        if (0 == t) then
            set t = ALM()
            call SaveInteger(mlf, o, this, t)
            call SaveInteger(mlf2, o, this, t)
            call SaveInteger(mnt, o, this, this.register(mb[o]))
            set mlp[t] = 0
            set mln[t] = 0
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,I2S(mlp[t])+"->"+I2S(t)+"->"+I2S(mln[t]))
            
            set mlpd[t] = 0
            set mlnd[t] = 0
            
            set mnh[t] = this
            set mno[t] = o
            
            set mnf = t
            set mnft = t
            
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"First: 0 -> "+I2S(t)+" on "+I2S(this))
            
            return t
        endif
        
        set mnf = t
        set mnft = t
        
        set n = ALM()
        set mnh[n] = this
        set mno[n] = o
        call RegisterMethod(this, t, n, aa[this])
        
        return n
    endfunction
    function TimerMethodRemoveInstance takes integer i returns nothing
        local TimerList this = mnh[i]
        local integer o = mno[i]
        local integer f = LoadInteger(mlf2, o, this)
        local integer c = LoadInteger(mnc, o, this) - 1
        
        if (not alm[i]) then
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Attempted To Deallocate Null: "+I2S(i))
            call DIS()
            return
        endif
        
        call SaveInteger(mnc, o, this, c)
        
        if (0 == c) then
            call this.unregister(LoadInteger(mnt, o, this))
        endif
        
        if (f == i) then
            set f = mlnd[i]
            call SaveInteger(mlf2, o, this, f)
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"First Temp: "+I2S(i)+" -> "+I2S(f)+" on "+I2S(this))
        endif
        set mnft = f
        
        call REMMD(i, f)
        
        if (mlap[i]) then
            set mnf = LoadInteger(mlf, o, this)
            
            set f = mlfp[this]
            
            call REMMP(i, f)
            
            set mlap[i] = false
            
            call DELM(i)
            
            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Deallocated From Marked After: "+I2S(i))
        else
            if (nr[this]) then
                call DAF(i)
                set mnf = LoadInteger(mlf, o, this)
            else
                set f = LoadInteger(mlf, o, this)
                
                if (f == i) then
                    set f = mln[i]
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"First: "+I2S(i)+" -> "+I2S(f)+" on "+I2S(this))
                endif
                
                call REMM(i, f)
                call DELM(i)
                
                call SaveInteger(mlf, o, this, f)
                
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Deallocated: "+I2S(i))
                
                set mnf = f
            endif
        endif
    endfunction
    function CreateTimerMethod takes boolexpr b returns integer
        set mtfr = mtfr + 1
        set mb[mtfr] = b
        return mtfr
    endfunction
    function TimerGetElapsedEx takes integer i returns real
        return TimerGetElapsed(tm[mnh[i]])
    endfunction
    function TimerGetRemainingEx takes integer i returns real
        return TimerGetRemaining(tm[mnh[i]])
    endfunction
    function TimerGetTimeoutEx takes integer i returns real
        return TimerGetTimeout(tm[mnh[i]])
    endfunction
    function GetTimerFirstInstance takes integer timerMethod returns integer
        return LoadInteger(mlf, timerMethod, exp)
    endfunction
    function GetTimerNext takes integer timerInstance returns integer
        return mln[timerInstance]
    endfunction
    
    module CTM
        static integer CTMf
        static integer array CTMq
        static integer array CTMqt
        
        static method create takes real t returns thistype
            local integer i
            local integer n
            
            if (en) then
                set i = TimerMethodAddInstance(t, CTMf)
                set CTMq[mnp] = mnf
                set CTMqt[mnp] = mnft
                
                if (mnf != LoadInteger(mlf, CTMf, mnh[i])) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"CREATE FIRST DESYNC: "+I2S(mnf)+"!="+I2S(LoadInteger(mlf, CTMf, mnh[i])))
                    call DIS()
                endif
                if (mnft != LoadInteger(mlf2, CTMf, mnh[i])) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"CREATE FIRST DESYNC TEMP: "+I2S(mnft)+"!="+I2S(LoadInteger(mlf2, CTMf, mnh[i])))
                    call DIS()
                endif
                
                return i
            endif
            return 0
        endmethod
        method destroy takes nothing returns nothing
            local integer n
            
            if (en) then
                call TimerMethodRemoveInstance(this)
                set CTMq[mnh[this]] = mnf
                set CTMqt[mnh[this]] = mnft
                
                if (mnf != LoadInteger(mlf, CTMf, mnh[this])) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DESTROY FIRST DESYNC: "+I2S(mnf)+"!="+I2S(LoadInteger(mlf, CTMf, mnh[this])))
                    call DIS()
                endif
                if (mnft != LoadInteger(mlf2, CTMf, mnh[this])) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"DESTROY FIRST DESYNC TEMP: "+I2S(mnft)+"!="+I2S(LoadInteger(mlf2, CTMf, mnh[this])))
                    call DIS()
                endif
                
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Destroying: "+I2S(this)+" -> "+I2S(mnf)+","+I2S(mnft))
            endif
        endmethod
        method operator elapsed takes nothing returns real
            return TimerGetElapsedEx(this)
        endmethod
        method operator remaining takes nothing returns real
            return TimerGetRemainingEx(this)
        endmethod
        method operator timeout takes nothing returns real
            return TimerGetTimeoutEx(this)
        endmethod
        static method CTMe takes nothing returns boolean
            local thistype this = CTMq[exp]
            local boolean array hit
    endmodule
    module CTMExpire
            implement CTM
            if (not en) then
                return false
            endif
            if (CTMq[exp] != LoadInteger(mlf, CTMf, exp)) then
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"BUG FIRST DESYNC: "+I2S(CTMq[exp])+"!="+I2S(LoadInteger(mlf, CTMf, exp))+" from "+I2S(exp))
                call DIS()
                return false
            endif
            if (CTMqt[exp] != LoadInteger(mlf2, CTMf, exp)) then
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"BUG FIRST DESYNC TEMP: "+I2S(CTMqt[exp])+"!="+I2S(LoadInteger(mlf2, CTMf, exp))+" from "+I2S(exp))
                call DIS()
                return false
            endif
            if (0 == this) then
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"NULL TIMER EXPIRED ON "+I2S(exp))
                call DIS()
            endif
            loop
                exitwhen 0 == this
                if (hit[this]) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"BUG: HIT "+I2S(this)+" MORE THAN ONCE FROM "+I2S(exp))
                    call DIS()
                    return false
                endif
                set hit[this] = true
    endmodule
    module CTMNull
                if (mnh[this] != exp) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"BUG: "+I2S(this)+" "+I2S(mnh[this])+"!="+I2S(exp)+" from "+I2S(exp))
                    call DIS()
                    return false
                endif
                if (not alm[this]) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"BUG NOT ALLOCATED: "+I2S(this)+" from "+I2S(exp))
                    call DIS()
                    return false
                endif
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,I2S(this)+" expired")
                set this = mln[this]
            endloop
            if (CTMq[exp] != CTMqt[exp]) then
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,10,"Temp First Expiration: "+I2S(CTMq[exp])+" -> "+I2S(CTMqt[exp])+" on "+I2S(exp))
            endif
            set CTMq[exp] = CTMqt[exp]
    endmodule
    module CTMEnd
        implement CTMNull
            return false
        endmethod
        private static method onInit takes nothing returns nothing
            set CTMf = CreateTimerMethod(Condition(function thistype.CTMe))
        endmethod
    endmodule
    
    module CLTQ
        static code CTQc
        private static integer array n
        private static integer array p
        static method create takes nothing returns thistype
            local integer i = AL()
            set tm[i] = CreateTimer()
            call TimerStart(tm[i], TIMEOUT, false, CTQc)
            set n[i] = 0
            set p[i] = p[0]
            set n[p[0]] = i
            set p[0] = i
            return i
        endmethod
        method destroy takes nothing returns nothing
            set n[p[this]] = n[this]
            set p[n[this]] = p[this]
            set lr[this] = lr[0]
            set lr[0] = this
            call PauseTimer(tm[this])
            call DestroyTimer(tm[this])
            set tm[this] = null
        endmethod
        method operator elapsed takes nothing returns real
            return TimerGetElapsed(tm[this])
        endmethod
        method operator remaining takes nothing returns real
            return TimerGetRemaining(tm[this])
        endmethod
        static method CTMe takes nothing returns nothing
            local thistype this = n[0]
            set n[0] = n[this]
    endmodule
    module CLTQEnd
            call destroy()
        endmethod
        private static method onInit takes nothing returns nothing
            set CTQc = function thistype.CTMe
        endmethod
    endmodule
endlibrary

You will be spammed with messages, but being spammed is good ;D. Those messages helped me fix this.

I will be optimizing this after I remove all of the debug crap, but BOO YEA : D.

edit
Oh yes, and you may be wondering how heavy the timer retrieval operation is...

is it local thistype this = LoadInteger(table, GetHandleId(GetExpiredTimer()), 0) ? no
local thistype this = LoadInteger(table, someId, 0) ? no
local thistype this = R2I(TimerGetTimeout(GetExpiredTimer())*100) no!

It's local thistype this = array[integer] !


Oh yes, I just totally pwn. The update was to turn the hashtable read into an array read, and I spent like 9.5 hours working on it ; O.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
4 hashtables really.
Can't you just play with "-" for the hashtable function argument (Save... / Load...) in order to use the same hashtable all the time, or at least less hashtables ?

Now, that wouldn't be a problem if the hashtable limit was higher than 256, sure by itself you're still far to the limit, but i consider as a real bad practice to use X hashtables for the same resource if you can reasonably avoid that.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
You really got what i mean by using "-" ? (check internal UnitLL comments)

Now, maybe it's still not possible, since as usual the code is a nightmare to read, so i don't bother to check it, Nestharus will know if it's possible or not. (or yourself)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
4 hashtables really.
Can't you just play with "-" for the hashtable function argument (Save... / Load...) in order to use the same hashtable all the time, or at least less hashtables ?

Now, that wouldn't be a problem if the hashtable limit was higher than 256, sure by itself you're still far to the limit, but i consider as a real bad practice to use X hashtables for the same resource if you can reasonably avoid that.

As I said, I still need to optimize it. For now, I want it to be working.

I'm going to turn it all into 1 hashtable, don't you worry. There are 4 different combinations with +, - I can use, meaning I can turn 4 hashtables into 1.


I'm still solving one issue that I am running into... my auras aren't firing for my immolation in wmw.. I'm not going to release Timer Tools and optimize it until I get it all running for my immolation aura perfect (been using that for testing). I'm not sure if the problem lies in AuraStruct or Timer Tools, but I know the map freezes were from Timer Tools.

The current version for CTM does work perfectly and the API hasn't changed, but it isn't quite as fast as the new CTM ^)^.


I will also be happy to explain the design of Timer Tools... it's a bit wild, heh.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
As I said, I still need to optimize it. For now, I want it to be working.

So use proper names, i think you can optimize a text file with wc3mapoptimizer, then all you will have to do is add "private" where it's needed.
Or you can just do it by yourself manually.

I'm going to turn it all into 1 hashtable, don't you worry. There are 4 different combinations with +, - I can use, meaning I can turn 4 hashtables into 1.

Good to know, but i don't worry at all, since i have no use of it.
I can't think about a concrete case where i would use it against "TimerUtils way" or a short unique periodic timer.
It was just a wtf question when i scrolled quickly your code.


I will also be happy to explain the design of Timer Tools... it's a bit wild, heh.

I think it's a must.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
For any shorter repeating timeouts like things for auras or dot effects, this lib beats TimerUtils in a very major way.

Performance wise, there is no comparison. It will destroy even timers w/ no data attachment whatsoever.

Each timer instance is retrieved only using the timeout (no hashtable or anything). From there, 1 trigger is evaluated. In each trigger condition is a loop with 1 array read to get the first node. At the end (after the trigger eval), there are 4 usually empty loops (very light with typically instant exits). This thing is just about as fast as CT32 in speed for expiring timers. It loses in speed from 4 end loops and 1 array set. It's faster than T32 and T32x, and it is of course faster than standard timers.


I'll talk about the actual design after I get some sleep.


The weaknesses of Timer Tools are creation and destruction. CLTQ has no weaknesses and will always beat anything for 1 shot timers with data attachment (anything).
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
myself said:
"TimerUtils way" or a short unique periodic timer.
And by unique i mean for "low" periods (under 0.1 s or so), and with as many timers as needed periods (example : 0.1 s , 1/25 s, ...)

And btw for auras, i would use a single 0.1 timer or something like that.
It's not like it's hard to use our own stack.

I still don't have a concrete case where i would use that.

"CLTQ" and other names are really terrible.

Now, it has been approved, and i don't feel the need to talk more about this, you have your toy, i don't like/have no use of it, simple as that.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Ok, I will now disclose how this operates.


The first instance is the main timer group. It represents all timers that have the same timeout. This is retrieved by just multiplying the timeout by 100 and converting it to an integer. This means that it also has a max timeout of 8.19. Larger timeouts are rather pointless on this as their chance for merging goes way down.

The next thing it does is it lets you retrieve a timer list using that timeout. It looks at the first and last nodes of the list and checks how much time they have remaining. If the first one started a short while ago or the last one is about to expire, it returns one of those. If not, then it returns a new timer. From there, you can then register a boolean expression on that timer. Registering the boolean expression adds a new trigger condition to the trigger that is evaluated on the timer.

When the timer expires, because it solely uses the timer's timeout and a linked list, it can just read from an array using the timer's timeout. The first node on that linked list is always the current expiring timer. When it expires, it does first = first.next. It then evaluates the one trigger. From there, it loops through all timers that are to be destroyed. If a timer is destroyed while it is running, it is pushed on to a destruction stack that is looped through after the trigger evaluation. This is also true for instanced methods that are about to expire. Furthermore, timers that are added to a timer that is about to expire are added on a to be added list. This to be added list is iterated over after the trigger evaluation. These features do decrease the accuracy of the first tick, but they ensure maximal timer mergers, which means more performance.

Each method instance (instance of a boolean expression) is a part of a list. When a new method instance is created, it is added to that list. When a trigger condition is fired, the first of that last can be retrieved by retrieving the current expiring timer (integer(timeout*100)) and the current boolean expression (the instance that the boolean expression was assigned). Because this is 2D, it requires a hashtable. The modules can get away without a hashtable because they store the firsts of each list into an array so that they can just read an array using the expiring timer.

In order to keep the first instance updated on the arrays, two lists instead of one are required. If you recall, timers that are destroyed while the timer is expiring are pushed on to a destruction stack to be destroyed later. This means that if one were to destroy the first instance of the list while the timer was expiring, they could easily do first = first.next. However, that operation is not done until the destruction stack is iterated over, meaning that the firsts can get screwed up. This means that there needs to be a true list that is updated instantly when the destruction occurs (to get the true first) and the actual list that is iterated over when the timer expires. The true list allows one to retrieve the actual new first. This new first is stored into a future first array. The actual first in the module is set to the future first array after the loop that iterates over all instances of the module.


That is how this operates.


The total overhead of expiring timers is for the core
R2I(TimerGetTimeout(GetExpiringTimer())*100)
node = first
first = first.next

trigger evaluate(node)

loop through add stack nodes
loop through add stack timers
loop through destruction stack nodes
loop through destruction stack modules


And for each module
this = first
loop through each instance
first = future first


I benchmarked this = first/first = future first to a hashtable read with 2 array reads and the hashtable lost, meaning that this is faster than using a hashtable. This would of course be much faster than a hashtable with a GetHandleId call.


Core nodes are destroyed when the counts on them reach 0. As things are added to them, the counts go up. As things are removed, the counts go down. This is also true for module instances.


With this light overhead, it of course beats timers with data attachment. Because a trigger evaluation is faster than many expiring timers, it also beats standard timers. Due to the overhead on creation and destruction, this isn't recommended for 1 shot timers.


CLTQ is essentially standard timers with an array. It requires a constant timeout. It takes advantage of the fact that because each timer has the same timeout, new timers will always go to the end. Because of this, creation and destruction is only marginally slower than standard timers (adding to a linked list and removing from a linked list). It also means that it has no chance for merges, so it operates just like timers. However, retrieving the instance is a simple array read local thistype this = n[0], meaning that it of course beats TimerUtils for 1 shot timers. The code for CLTQ is extremely short as well

JASS:
        static code expiringMethod
        private static integer array next
        private static integer array prev
        static method create takes nothing returns thistype
            //simple standard allocation
            local integer this = Allocate()

            //create timer
            set timer[this] = CreateTimer()
            call TimerStart(timer[this], TIMEOUT, false, expiringMethod)

            //add to end of list
            set next[this] = 0
            set prev[this] = prev[0]
            set next[prev[0]] = this
            set prev[0] = this

            return this
        endmethod
        method destroy takes nothing returns nothing
            //remove from list
            set next[prev[this]] = next[this]
            set prev[next[this]] = prev[this]

            //simple standard deallocation
            call Deallocate(this)

            //destroy timer
            call PauseTimer(timer[this])
            call DestroyTimer(timer[this])
            set timer[this] = null
        endmethod
        method operator elapsed takes nothing returns real
            return TimerGetElapsed(timer[this])
        endmethod
        method operator remaining takes nothing returns real
            return TimerGetRemaining(timer[this])
        endmethod
        static method expire takes nothing returns nothing
            //retrieve this
            local thistype this = next[0]

            //update first
            set next[0] = next[this]

            //code here

            call destroy()
        endmethod
        private static method onInit takes nothing returns nothing
            set expiringMethod = function thistype.expire
        endmethod


The reason for the short names is because Timer Systems require short variable names in order to operate quickly. Long variable names will considerably lower the speed of a timer system.


edit
2.1.0.0 released!

bench results
I couldn't get this thing under 64 fps.. I used 8190 timers across like 150 different structs and 99 different timeouts... I even set RELATIVE_MERGE to 1 and CONSTANT_MERGE to 0... I'm not even going to bother comparing this to other resources because I already know the results... and this is again CTM....

the speed astonished even me.. if anyone has a slow computer, can you please attempt to benchmark this... the lowest I got it to was like 63.5 fps (flipping between 63 and 64).
 
Last edited:

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
You should recycle the timers that are used in the CTLQ (or even throughout the whole system) module. Also, if I were you, I'd rewrite this thing so that it wouldn't look like an abomination. Because it currently does.

Also, what kind of a name is Tt?
 
Also, what kind of a name is Tt?

I guess it's for shorter Variable names :p

Nes, I have an idea:

JASS:
library Tt requires ....

    // code

endlibrary

// This is what users will require:
library TimerTools requires Tt
endlibrary

It's a win-win.
You'll maintain speed and users will get more readability :3

Also, if I were you, I'd rewrite this thing so that it wouldn't look like an abomination

Impossibru! :3
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
The map I'm working on uses GetUnitCost

JASS:
library GetUnitCost /* v1.0.1.0
*************************************************************************************
*
*   */uses/*
*       */ UnitIndexer /*               hiveworkshop.com/forums/jass-functions-413/system-unit-indexer-172090/
*       */ RegisterPlayerUnitEvent /*   hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
************************************************************************************
*
*    Functions
*
*       function GetUnitTypeIdGoldCost takes integer unitTypeId returns integer
*       function GetUnitTypeIdWoodCost takes integer unitTypeId returns integer
*
*                   NATIVES WORK WITH NON-HERO UNITS ONLY
*/
        native GetUnitGoldCost takes integer unitid returns integer
        native GetUnitWoodCost takes integer unitid returns integer
/*
*       function GetUnitTotalGoldCost takes unit whichUnit returns integer
*           -   Gets total unit gold cost including prior upgrades
*       function GetUnitTotalWoodCost takes unit whichUnit returns integer
*           -   Gets total unit wood cost including prior upgrades
*
************************************************************************************/
//! textmacro UNIT_COST
    globals
        private Table g = 0                 //unit type id gold cost table
        private Table l = 0                 //unit type id lumber cost table
        private integer array gu            //unit gold cost array (indexed)
        private integer array lu            //unit lumber cost array (indexed)
        private integer array ug            //upgrade gold
        private integer array ul            //upgrade lumber
    endglobals
    //unit cost (sell)
    private function O takes nothing returns boolean
        call RemoveUnit(GetSoldUnit())
        return false
    endfunction
    private function LogUnit takes integer id returns nothing
        //store previous gold, lumber, and food
        local integer k=GetPlayerState(p,PLAYER_STATE_RESOURCE_GOLD)
        local integer w=GetPlayerState(p,PLAYER_STATE_RESOURCE_LUMBER)
        local integer h=GetPlayerState(p,PLAYER_STATE_RESOURCE_FOOD_USED)
        local integer m=GetPlayerState(p,PLAYER_STATE_RESOURCE_FOOD_CAP)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_GOLD,1000000)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_LUMBER,1000000)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_FOOD_USED,0)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_FOOD_CAP,100000)
        //build unit
        set UnitIndexer.enabled=false
        call AddUnitToStock(u,id,1,1)
        call IssueNeutralImmediateOrderById(p,u,id)
        call RemoveUnitFromStock(u,id)
        set UnitIndexer.enabled = true
        //retrieve gold and lumber cost
        set g[id]=1000000-GetPlayerState(p,PLAYER_STATE_RESOURCE_GOLD)
        set l[id]=1000000-GetPlayerState(p,PLAYER_STATE_RESOURCE_LUMBER)
        //set player gold back to what it was
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_GOLD,k)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_LUMBER,w)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_FOOD_USED,h)
        call SetPlayerState(p,PLAYER_STATE_RESOURCE_FOOD_CAP,m)
    endfunction
    function GetUnitTypeIdGoldCost takes integer id returns integer
        debug if (null!=UnitId2String(id)) then
            if (not g.has(id)) then
                if (IsHeroUnitId(id)) then
                    call LogUnit(id)
                else
                    set g[id]=GetUnitGoldCost(id)
                    set l[id]=GetUnitWoodCost(id)
                endif
            endif
            return g[id]
        debug else
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"COSTS ERROR: INVALID UNIT TYPE ID")
        debug endif
        debug return 0
    endfunction
    function GetUnitTypeIdWoodCost takes integer id returns integer
        debug if (null!=UnitId2String(id)) then
            if (not g.has(id)) then
                if (IsHeroUnitId(id)) then
                    call LogUnit(id)
                else
                    set g[id]=GetUnitGoldCost(id)
                    set l[id]=GetUnitWoodCost(id)
                endif
            endif
            return l[id]
        debug else
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"COSTS ERROR: INVALID UNIT TYPE ID")
        debug endif
        debug return 0
    endfunction
    function GetUnitTotalGoldCost takes unit t returns integer
        debug if (null!=t) then
            if (0==GetUnitUserData(t)) then
                return GetUnitTypeIdGoldCost(GetUnitTypeId(t))
            endif
            return gu[GetUnitUserData(t)]
        debug else
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"COSTS ERROR: INVALID UNIT")
        debug endif
        debug return 0
    endfunction
    function GetUnitTotalWoodCost takes unit t returns integer
        debug if (null!=t) then
            if (0==GetUnitUserData(t)) then
                return GetUnitTypeIdWoodCost(GetUnitTypeId(t))
            endif
            return lu[GetUnitUserData(t)]
        debug else
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"COSTS ERROR: INVALID UNIT")
        debug endif
        debug return 0
    endfunction
    //on unit index
    private function I takes nothing returns boolean
        local integer i=GetUnitTypeId(GetIndexedUnit())
        local integer k=GetIndexedUnitId()
        //store gold/lumber cost for unit
        set gu[k]=GetUnitTypeIdGoldCost(i)
        set lu[k]=l[i]
        return false
    endfunction
    //on unit deindex
    private function D takes nothing returns boolean
        local integer k=GetIndexedUnitId()
        //reset gold/lumber cost for unit
        set gu[k]=0
        set lu[k]=0
        return false
    endfunction
    //on unit upgrade start
    private function US takes nothing returns boolean
        local integer k=GetUnitUserData(GetTriggerUnit())
        local integer i=GetUnitTypeId(GetTriggerUnit())
        //if the unit is indexed, store upgrade gold/lumber cost
        if (0!=k) then
            set ug[k]=GetUnitTypeIdGoldCost(i)
            set ul[k]=l[i]
            set gu[k]=ug[k]+gu[k]
            set lu[k]=ul[k]+lu[k]
        endif
        return false
    endfunction
    //on unit upgrade cancel
    private function UC takes nothing returns boolean
        local integer k=GetUnitUserData(GetTriggerUnit())
        //if unit is indexed, add upgrade cost to cost
        if (0!=k) then
            set gu[k]=gu[k]-ug[k]
            set lu[k]=lu[k]-ul[k]
        endif
        return false
    endfunction
//! endtextmacro
//! textmacro UNIT_COST_2
    local trigger t=CreateTrigger()           //sell unit
//! endtextmacro
//! textmacro UNIT_COST_3
    set g=Table.create()                  //gold table
    set l=Table.create()                  //lumber table
    //register unit indexing for setting/resetting gold/lumber costs for units
    call RegisterUnitIndexEvent(Condition(function I),UnitIndexer.INDEX)
    call RegisterUnitIndexEvent(Condition(function D),UnitIndexer.DEINDEX)
    //this is done for removing units that are sold for getting their costs
    call TriggerRegisterUnitEvent(t,u,EVENT_UNIT_SELL)
    call TriggerAddCondition(t,Condition(function O))
    
    //this is done for updating unit gold/lumber costs on upgrade
    call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_START, Condition(function US))
    call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_UPGRADE_CANCEL, Condition(function UC))
//! endtextmacro
endlibrary

>.>
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,454
You could easily do a workaround for those 2 by just giving them the same treatment as you did for ones that don't count with the natives. The speed loss of not using those natives is going to be heavily eclipsed by the speed gain and map file size compression gained by the optimizer.

You can easily fix those natives by manually editing the war3map.j file, which would take less than a few minutes.

What program do you use with this?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You could easily do a workaround for those 2 by just giving them the same treatment as you did for ones that don't count with the natives. The speed loss of not using those natives is going to be heavily eclipsed by the speed gain and map file size compression gained by the optimizer.

Or I could say that vexorian's optimizer being broken is not my problem.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,454
Or I could say that vexorian's optimizer being broken is not my problem.

It is your problem when you have no idea what you wrote in your script because you used senseless variable names and it makes it super difficult to make further edits later on.

It is your problem when you have to work harder to come up with nonsense short variable names rather than names that make sense.

It is your problem (or maybe it isn't) that people who use the optimizer won't use your stuff because it doesn't work with the rest of the project.

It is your problem that you keep passing the responsibility on Vexorian who doesn't even exist in the wc3 community any more instead of doing something about it yourself.

And before we start with that same discussion which we had last week, no I am not going to make it a "requirement" that all scripts must work with the optimizer, nor would I make it a requirement to add a disclaimer, I am simply saying that the optimizer is the absolute best method over any alternative.
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
537
Challenge Accepted.
I was planning on writing one in the past, but I was too lazy.
I guess now would be a good time to get started on one :p

And with optimizer you mean a script that shortens all the names?
e: I mean, the rest is handled by vex' optimizer, no?

And btw. you don't need a optimizer for vJass. Jass is sufficient. And more reasonable.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,454
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
I think what Bribe is trying to point out is that you can convert the passed code to boolexpr in the create method, just like you'd pass them: Filter(myCode)

I know what he's trying to point out, and I'm saying that that's stupid. It just limits the flexibility of the code so that you can no longer do boolexpr arrays and pass in a value in that array (you can't do a code array) and it slows down creation in the module (the module stores a boolexpr and passes that in, meaning that the function doesn't have to convert it). Furthermore, he's probably never going to use that function anyways, so it's a mute point...


Now, what I am planning to do is GetNewFirst and GetFutureFirst so that you can do the same thing as the module does ; ).
 
Top