• 🏆 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!

Key Timers 2

Level 3
Joined
Apr 13, 2004
Messages
28
[vJass] Key Timers 2

Key Timers 2
Version 1.6.1

Requirements:
- Jass NewGen

JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ KT ~~ Key Timers 2 ~~ By Jesus4Lyf ~~ Version 1.6.1 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is Key Timers?
//         - Key Timers attaches structs to timers, or more to the point timed
//           effects.
//         - You can specify different periods.
//         - Key Timers only uses one timer with one trigger per low period
//           to keep things efficient, especially within the looping.
//         - Key Timers alternatively uses one single timer for all higher periods
//           to allow accurate expirations in a stable and reasonably efficient fashion.
//
//    =Pros=
//         - Easy to use.
//         - Fastest attachment loading system (storing in parallel arrays).
//         - Fastest execution system for low periods (attaching all functions to one trigger).
//         - Allows multiple periods to be used.
//
//    =Cons=
//         - The code passed into KT2 must call KT_GetData exactly once.
//         - Periods must be a multiple of 0.00125 seconds. Not 0.007, for example.
//
//    Functions:
//         - KT_Add(userFunc, struct, period)
//         - KT_GetData returns the struct
//
//         - userFunc is to be a user function that takes nothing and returns boolean.
//           It will be executed by the system every period until it returns true.
//
//         - KT_GetData is to be used inside func, it will return the struct passed to
//           to the Add function. It must be called exactly once within the func.
//
//  Details:
//         - KT2 treats low periods and high periods differently, optimizing each
//           with appropriate speed and accuracy, although this effect is invisible
//           to you, the mapper.
//
//         - While func returns false the timer will continue to call it each period.
//           Once func returns true the instance will be detached from system.
//
//  Thanks:
//         - Daxtreme: For encouraging me to return to Key Timers 2, rather than
//           leave it to rot. His interest in the system restored it, and helped
//           it to become what it is now. :)
//
//         - Captain Griffen: For his work on Rapid Timers, demonstrating that it
//           is possible to attach all functions to one trigger, and that it is
//           indeed faster.
//
//         - Cohadar: Told me to make Key Timers without a textmacro.
//           Thanks to him for helping me with the original Key Timers system too.
//           Also, I'd like to thank him for his work on Timer Ticker (TT) which
//           demonstrated how to use triggers/conditions in this sort of system,
//           which has been used in Key Timers 2.
//
//  How to import:
//         - Create a trigger named KT.
//         - Convert it to custom text and replace the whole trigger text with this.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library KT initializer Init
    ///////////////
    // Constants //
    ////////////////////////////////////////////////////////////////////////////
    // That bit that users may play with if they know what they're doing.
    // Not touching these at all is recommended.
    globals
        private constant real PERIODTHRESHOLD=0.3 // MUST be below 10.24 seconds.
        private constant real HT_MARGINOFERROR=0.01
    endglobals
    
    //////////////////////////
    // Previous KT2 Globals //
    ////////////////////////////////////////////////////////////////////////////
    // These needed to be moved here for HT to hook GetData.
    globals
        private timer array KeyTimer
        private trigger array TimerTrigger
        private integer array KeyTimerListPointer
        private integer array KeyTimerListEndPointer
        private triggercondition array TriggerCond
        private boolexpr array Boolexpr
        private integer array Data
        private integer array Next
        private integer array Prev
        private integer TrigMax=0
        private integer array NextMem
        private integer NextMemMaxPlusOne=1
        private integer array ToAddMem
        private triggercondition array ToRemove
        private boolexpr array ToDestroy
        private boolean array IsAdd
        private integer AddRemoveMax=0
        
        // Locals
        private integer t_id=-1
        private integer t_mem
        private integer t_lastmem
        private integer a_id
        private integer a_mem
        
        // Code Chunks
        private conditionfunc RemoveInstanceCond
    endglobals
    
    /////////////////////
    // Hibernate Timer //
    ////////////////////////////////////////////////////////////////////////////
    // KT2 implementation for higher periods (low frequency).
    private keyword HTnode
    globals
        private timer HT_Moderator=CreateTimer()
        private real HT_Time=0.0
        private HTnode HT_Top
        private constant integer HT_DATAMEM=8190 // Added for KT2 hook. Don't change.
    endglobals
    
    globals//locals
        private HTnode newHTnode
    endglobals
    private struct HTnode
        HTnode prev // Make sure to link this before use
        HTnode next // Make sure to link this before use
        real period
        real firewhen // = HT_Time at creation
        trigger trig
        integer data
        static method new takes code func, integer data, real period returns HTnode
            set newHTnode=HTnode.create()
            if newHTnode.trig==null then
                set newHTnode.trig=CreateTrigger()
            endif
            call TriggerAddCondition(newHTnode.trig,Condition(func))
            set newHTnode.data=data
            set newHTnode.period=period
            set newHTnode.firewhen=HT_Time
            return newHTnode
        endmethod
        method link takes HTnode prev, HTnode next returns nothing
            set prev.next=this
            set next.prev=this
            set this.next=next
            set this.prev=prev
        endmethod
        method unlink takes nothing returns nothing
            set this.prev.next=this.next
            set this.next.prev=this.prev
        endmethod
        method autolink takes HTnode start returns nothing
            loop
                if start.next==0 then
                    call this.link(start,0)
                    return
                endif
                if start.next.firewhen > this.firewhen then
                    call this.link(start,start.next)
                    return
                endif
                set start=start.next
            endloop
        endmethod
    endstruct
    
    globals//locals
        private HTnode HT_handlenode
        private HTnode HT_handlenext
    endglobals
    private function HT_Handler takes nothing returns nothing
        // KT2 Data Hook
        set t_mem=HT_DATAMEM
        // End KT2 Data Hook
        set HT_handlenode=HT_Top
        set HT_handlenext=HT_handlenode.next
        loop
            set HT_handlenode=HT_handlenext
            exitwhen HT_handlenode==0 // Would pause timer here under old method.
            set HT_handlenext=HT_handlenode.next
            if HT_handlenode.firewhen<HT_Time+HT_MARGINOFERROR then
                // Load data
                set Data[HT_DATAMEM]=HT_handlenode.data
                // End load data
                if TriggerEvaluate(HT_handlenode.trig) then
                    call HT_handlenode.unlink()
                    call TriggerClearConditions(HT_handlenode.trig)
                    call HT_handlenode.destroy()
                else
                    set HT_handlenode.firewhen=HT_handlenode.firewhen+HT_handlenode.period
                    call HT_handlenode.unlink()
                    call HT_handlenode.autolink(HT_handlenode.prev)
                    if HT_handlenext==0 then
                        set HT_handlenext=HT_handlenode
                    endif
                endif
            else
                // More to fire, but not yet.
                return
            endif
        endloop
    endfunction
    
    globals//locals
        private HTnode HT_addnode
    endglobals
    private function HTadd takes code func, integer data, real period returns nothing
        set HT_addnode=HTnode.new(func, data, period)
        set HT_addnode.firewhen=HT_Time+period
        call HT_addnode.autolink(HT_Top)
        //if HT_Top.next==HT_addnode then
        //  //Old start timer stuff
        //endif
    endfunction
    private function HT_ModeratorLoop takes nothing returns nothing
        set HT_Time=HT_Time+HT_MARGINOFERROR
        if HT_Top.next.firewhen<HT_Time then
            call HT_Handler()
        endif
    endfunction
    private function HTinit takes nothing returns nothing
        call TimerStart(HT_Moderator,HT_MARGINOFERROR,true,function HT_ModeratorLoop)
        set HT_Top=HTnode.create()
        set HT_Top.prev=0
        set HT_Top.next=0
        set HT_Top.firewhen=0.0
        // Allow for GetData
        set Next[HT_DATAMEM]=HT_DATAMEM
        set Prev[HT_DATAMEM]=HT_DATAMEM
        // End allow for GetData
    endfunction
    
    //////////////////
    // Previous KT2 //
    ////////////////////////////////////////////////////////////////////////////
    // The rest of the KT2 implementation
    // Globals have been moved to top.
    private function KeyTimerLoop takes nothing returns nothing
        set t_id=R2I(TimerGetTimeout(GetExpiredTimer())*800)
        set t_mem=KeyTimerListEndPointer[t_id]
        call TriggerEvaluate(TimerTrigger[t_id])
        set t_mem=0
        loop
            exitwhen t_mem==AddRemoveMax
            set t_mem=t_mem+1
            if IsAdd[t_mem] then
                set TriggerCond[ToAddMem[t_mem]]=TriggerAddCondition(TimerTrigger[t_id],Boolexpr[ToAddMem[t_mem]])
            else
                call TriggerRemoveCondition(TimerTrigger[t_id],ToRemove[t_mem])
                call DestroyBoolExpr(ToDestroy[t_mem])
            endif
        endloop
        set AddRemoveMax=0
        set t_id=-1
    endfunction
    
    private function RemoveInstance takes nothing returns boolean
        // Will only fire if code returns true.
        set AddRemoveMax=AddRemoveMax+1
        set IsAdd[AddRemoveMax]=false
        set ToRemove[AddRemoveMax]=TriggerCond[t_lastmem]
        set ToDestroy[AddRemoveMax]=Boolexpr[t_lastmem]
        if Next[t_lastmem]==0 then
            set KeyTimerListEndPointer[t_id]=Prev[t_lastmem]
        endif
        set Prev[Next[t_lastmem]]=Prev[t_lastmem]
        if Prev[t_lastmem]==0 then
            set KeyTimerListPointer[t_id]=Next[t_lastmem]
            if KeyTimerListPointer[t_id]<1 then
                call PauseTimer(KeyTimer[t_id])
            endif
        else
            set Next[Prev[t_lastmem]]=Next[t_lastmem]
        endif
        set NextMem[NextMemMaxPlusOne]=t_lastmem
        set NextMemMaxPlusOne=NextMemMaxPlusOne+1
        return false
    endfunction
    
    private function KTadd takes code func, integer data, real period returns nothing
        set a_id=R2I(period*800)
        
        if KeyTimer[a_id]==null then
            set KeyTimer[a_id]=CreateTimer()
            set TimerTrigger[a_id]=CreateTrigger()
        endif
        
        if NextMemMaxPlusOne==1 then
            set TrigMax=TrigMax+1
            set a_mem=TrigMax
        else
            set NextMemMaxPlusOne=NextMemMaxPlusOne-1
            set a_mem=NextMem[NextMemMaxPlusOne]
        endif
        
        set Boolexpr[a_mem]=And(Condition(func),RemoveInstanceCond)
        if t_id==a_id then
            set AddRemoveMax=AddRemoveMax+1
            set IsAdd[AddRemoveMax]=true
            set ToAddMem[AddRemoveMax]=a_mem
        else
            if KeyTimerListPointer[a_id]<1 then
                call TimerStart(KeyTimer[a_id],a_id/800.0,true,function KeyTimerLoop)
                set KeyTimerListEndPointer[a_id]=a_mem
            endif
            
            set TriggerCond[a_mem]=TriggerAddCondition(TimerTrigger[a_id],Boolexpr[a_mem])
        endif
        set Data[a_mem]=data
        
        set Prev[a_mem]=0
        set Next[a_mem]=KeyTimerListPointer[a_id]
        set Prev[KeyTimerListPointer[a_id]]=a_mem
        set KeyTimerListPointer[a_id]=a_mem
    endfunction
    
    public function GetData takes nothing returns integer // Gets hooked by HT.
        set t_lastmem=t_mem
        set t_mem=Prev[t_mem]
        return Data[t_lastmem]
    endfunction
    
    private function KTinit takes nothing returns nothing
        set RemoveInstanceCond=Condition(function RemoveInstance)
    endfunction
    
    ///////////////
    // Interface //
    ////////////////////////////////////////////////////////////////////////////
    // Stitches it all together neatly.
    public function Add takes code func, integer data, real period returns nothing
        if period<PERIODTHRESHOLD then
            call KTadd(func,data,period)
        else
            call HTadd(func,data,period)
        endif
    endfunction
    
    private function Init takes nothing returns nothing
        call KTinit()
        call HTinit()
    endfunction
endlibrary

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

EXAMPLE SPELLS:
These two spells are the ones I use to test new versions of KT2. They demonstrate the full power of the system in curious ways... ^_^
Sandman.
Sharl.

Key Timers 2 is, at it's core, a timer attachment system. However, it does not actually use one timer per instance. It uses NO H2I to attach data, and has certain efficiency implementations to make it run faster than even TU red for periods below 0.3 seconds, although for higher periods it may not be quite as efficient.

This is also, at the time of release, the most efficient timer system in existance for low periods.

Updates:
Key Timers 2:
- Version 1.6.0: Removed the maximum period limit and removed the first procurement inaccuracy issue. Also added warning for invalid periods, although these cannot detect mismatches below 0.001 of a second.
- Version 1.5: Made it stable and reliable, removed the boolexpr memory leaks. Now 22% faster than before. ONE INCOMPATABILITY - if you called KT_GetData more than once, or not at all in your code, it will need to be modified to call KT_GetData exactly once.
- Version 1.4: Switched to putting all attached code on one trigger, using Or boolexprs and modifying GetData. Unofficial release.
- Version 1.3: Optimized and inlined. Is within margin on error in speed to verson 1.1. Estimated 0.2% efficiency gain. :)
- Version 1.2: Wasn't released, pending optimization. Rewritten to use a different data storage system (linked lists), to remove the limit of 400 instances per period and 20 different periods max. The "Add" function should also be faster (not bench tested).
- Version 1.1: Updated to use globals instead of locals for efficiency. Changed attaching method to KT_GetData for efficiency. The old GetTriggerExecCount(GetTriggeringTrigger()) method must be replaced with KT_GetData() now to work. Various other optimizations such as using globals to store certain array values instead of constantly referencing arrays. Much bench testing went into determining these things.
- Version 1.0: Release. Does the same thing as Key Timers 1 but with a nicer interface.
 
Last edited:
Level 11
Joined
Nov 4, 2007
Messages
337
- The code passed into KT2 must call KT_GetData exactly once.

Thats a big flaw.
You could look into my HandleIndexing thread.. I had the same problem, but I made a solution in the Steel Style. Maybe you can use it, but maybe your's works completely different.. I didn't look clearly at your code.
But the rest of the system seems promising.
 
Level 3
Joined
Apr 13, 2004
Messages
28
It's quite necessary as far as I can see. =/

If you read through the rest and understand it and have an alternative to suggest without any efficiency loss, I'm listening.
 
Level 3
Joined
Apr 13, 2004
Messages
28
Actually. I'd like to point something out about calling KT_GetData once. I could split it into KT_Tick and KT_GetData, but then users would have to remember to call KT_Tick, and it wouldn't be backwards compatible. Besides, why make users call KT_Tick exactly once and then call KT_GetData, when users by default are going to call KT_GetData exactly once anyway? So you'd merge them into one so users can't forget to tick. And if they need to tick and not get the data, call KT_GetData without storing the result. Duh. =]

Hence, the current result, which I don't consider to have any design flaw. It's perfectly logical, imo.
 
Level 11
Joined
Nov 4, 2007
Messages
337
Wouldnt this be easier to use/understand?
Even faster?
JASS:
library Past initializer Init // Parallel array stored timers
    // functions called with Past may not contain waits.
    // Intervals used for timers must be a multiple of MIN_INTERVAL
    function interface PastCode takes integer Dat returns nothing
    
    globals
        private constant real MIN_INTERVAL = 0.05 // Timer Intervals must be a multiple of this value.
        private timer Evaluator = CreateTimer()
        private integer This = 0
        private integer CodeCount = 0
        private PastCode array Codes
        private real array Interval
        private real array IntervalCount
        private integer array Values
    endglobals
    
struct Past
    static method Start takes real interval, PastCode what, integer value returns nothing
        local real mod = ModuloReal(interval,MIN_INTERVAL)
        if  mod != 0. then
            call BJDebugMsg("Past: Wrong interval '"+R2S(interval)+"'.")
            set interval = interval*MIN_INTERVAL
            call BJDebugMsg("Past: New interval: "+R2S(interval)+".")
        endif
        
        set CodeCount = CodeCount + 1
        set Codes[CodeCount] = what
        set Interval[CodeCount] = interval
        set IntervalCount[CodeCount] = 0.00
        set Values[CodeCount] = value
    endmethod
    
    static method Destroy takes nothing returns nothing
        set Codes[This] = Codes[CodeCount]
        set Interval[This] = Interval[CodeCount]
        set IntervalCount[This] = IntervalCount[CodeCount]
        set Values[This] = Values[CodeCount]
        set CodeCount = CodeCount - 1
    endmethod
    
    static method ChangeValue takes integer NewValue returns nothing
        set Values[This] = NewValue
    endmethod
endstruct

private function EvaluateCodes takes nothing returns nothing
    local integer i = 0
    loop
        set i = i + 1
        exitwhen i > CodeCount
        set IntervalCount[i] = IntervalCount[i] + MIN_INTERVAL
        if IntervalCount[i] >= Interval[i] then
            set IntervalCount[i] = 0.00
            set This = i
            call Codes[i].evaluate(Values[i])
        endif
    endloop
endfunction

private function Init takes nothing returns nothing
    call TimerStart(Evaluator,MIN_INTERVAL,true,function EvaluateCodes)
endfunction

endlibrary
 
Last edited:
Level 3
Joined
Apr 13, 2004
Messages
28
Took a moment to understand what you're doing. You're talking about the slower part of the system, "Hibernate Timer" (HT), which isn't really that relevant. You're suggesting I remake the user interface of KT2, making everyone's work so far redundant after a year of this system being released. =/

Additionally, using that interface would not allow the fast part of the system to work at all. If you replaced the fast part of this system with your suggestion, it would be somewhere in the vacinity of 50% as fast (and slower with more instances, as opposed to faster like it is with its current design).

I know some mappers really like vJass interfaces. But the reality is there is no way to use them in KT2 without a large speed hit, regardless. And it would break everyone's work up until now.

Also, note that my minimum interval is 0.00125. I have decided that KT2 MUST support a period of 0.03125 seconds. Running 100 instances, subtracting in the way you do would be quite nasty. I recommend you analyse the code in KT2 more carefully - not the HT part, but the stuff related to KTadd. Feel free to ask questions.

Nonetheless, thanks for your interest in the system so far. I will carefully evaluate all suggestions. :)
 
Level 3
Joined
Apr 13, 2004
Messages
28
>Who needs timer Attaching with low periods?
>You can loop through structs periodically. That should even be faster.

I'm not sure you appreciate how efficient this system is. The maximum speed gained with a struct/stack loop per instance (which I clocked for 100 instances on one struct/stack loop, vs 100 instances on KT2) is about 8 nanoseconds, off memory, on my computer (12 nanosec vs 4 nanosec). This is the smallest difference ever constructed with a timer system. It actually makes struct/stack loops practically redundant, considering to even gain that fractional speed advantages you need 100 instances of one piece of code. If you're writing spells which usually only have 1-2 instances going, max, then KT2 is actually faster than a struct/stack loop.

But when the margins are that small anyway, what's more important is the time it takes to implement the loop VS the time it takes to use KT2. KT2 is a time and thought saver, with a brilliantly straight forward interface.

The only time I would ever use a struct/stack loop is when I know a piece of code is likely to be executing with 50 instances at once.

See the attachment of post #161 here for an interesting graph. KT2 is fast.
 
Level 14
Joined
Nov 18, 2007
Messages
816
you know what? I dont give a shit anymore about this kind of systems. They are slower for all possible applications where speed matters and you have a high frequency. Yes. Direct timer usage outperforms these systems. Even for a thousand timers. For lower frequency, TU is fine enough

JASS:
library YourLibrary // maybe you need an initializer and requirements; add them here

    globals
        private constant    real                    TICK                    = 1./32
    endglobals
    
    private struct Data
        // your struct members go here
        
        private integer i
        
        private static Data array Structs
        private static timer T=CreateTimer()
        private static integer Count=0
        
        method onDestroy takes nothing returns nothing
            // clean your struct here
            set Data.Count=Data.Count-1
            set Data.Structs[.i]=Data.Structs[Data.Count]
            set Data.Structs[.i].i=.i
            if Data.Count==0 then
                call PauseTimer(Data.T)
            endif
        endmethod
        
        private static method Callback takes nothing returns nothing
        local integer i=0
        local Data s
            loop
                exitwhen i>=Data.Count
                set s=Data.Structs[i]
                // do your things here, dont forget to call s.destroy() somewhen
                // set i=i-1 // uncomment where you call s.destroy()
                set i=i+1
            endloop
        endmethod
        
        static method create takes nothing returns Data
        local Data s=Data.allocate()
            // initialize the struct here
            set Data.Structs[Data.Count]=s
            set s.i=Data.Count
            if Data.Count==0 then
                call TimerStart(Data.T, TICK, true, function Data.Callback)
            endif
            set Data.Count=Data.Count+1
            return s
        endmethod
    endstruct
    
endlibrary


library YourSecondLibrary // maybe you need an initializer and requirements; add them here

    globals
        private constant    real                    TICK                    = 1./32
    endglobals
    
    private struct Data
        // your struct members go here
        
        method onDestroy takes nothing returns nothing
            // clean your struct here
        endmethod
        
        private static method Callback takes nothing returns boolean
        local Data s = KT_GetData()
            return false // remember to return true after calling s.destroy()
        endmethod
        
        static method create takes nothing returns Data
        local Data s=Data.allocate()
            // initialize the struct here
            call KT_Add(function Data.Callback, s, TICK)
            return s
        endmethod
    endstruct
    
endlibrary
Compare these two libraries. They do exactly the same, yet the first one is faster.

Oh and if you care, i attached a map where i tested KT against TT and direct timer usage. You need jAPI (that means you need a 1.21b version of WC3) to run the map. Bash me all the way you want to, but ill just keep ignoring KT and TT.
 

Attachments

  • KeyTimersTest.w3m
    44.8 KB · Views: 67
Level 3
Joined
Apr 13, 2004
Messages
28
Please read what I say.

>Please Benchmark this against
>TimerUtils, HandleIndexing, ABC, TT, Past
See the attachment of post #161 here for ... graph.
I won't answer your request because I've already done my benchmarking. And against most of those, no less! This is faster than raw H2I-0x100000. Substancially.

>Yes. Direct timer usage outperforms these systems.

If you mean TU red stuff, you're dead wrong. If you mean struct/stack loops like in your post, then only when you have more than 2 instances max. And I've already benchmarked what you suggested. It's KT1, basically.
JASS:
//! textmacro KeyTimer takes SUBJECT, PERIOD
globals
	private timer $SUBJECT$Timer=CreateTimer()
	private integer $SUBJECT$TimerMax=0
	private integer array $SUBJECT$TimerKey
	private constant real $SUBJECT$Period=$PERIOD$
endglobals
private function $SUBJECT$TimerLoop takes nothing returns nothing
	local integer i=$SUBJECT$TimerMax
	loop
		exitwhen i==0
		if Do$SUBJECT$($SUBJECT$TimerKey[i]) then
			set $SUBJECT$TimerKey[i]=$SUBJECT$TimerKey[$SUBJECT$TimerMax]
			set $SUBJECT$TimerKey[$SUBJECT$TimerMax]=0
			set $SUBJECT$TimerMax=$SUBJECT$TimerMax-1
		endif
		set i=i-1
	endloop
	if $SUBJECT$TimerMax==0 then
		call PauseTimer($SUBJECT$Timer)
	endif
endfunction
function Add$SUBJECT$ takes integer x returns nothing
	set $SUBJECT$TimerMax=$SUBJECT$TimerMax+1
	set $SUBJECT$TimerKey[$SUBJECT$TimerMax]=x
	if $SUBJECT$TimerMax==1 then
		call TimerStart($SUBJECT$Timer,$PERIOD$,true,function $SUBJECT$TimerLoop)
	endif
endfunction
//! endtextmacro
But that's not even the point. You clearly didn't read a thing.
>Who needs timer Attaching with low periods?
>You can loop through structs periodically. That should even be faster.

I'm not sure you appreciate how efficient this system is. The maximum speed gained with a struct/stack loop per instance (which I clocked for 100 instances on one struct/stack loop, vs 100 instances on KT2) is about 8 nanoseconds, off memory, on my computer (12 nanosec vs 4 nanosec). This is the smallest difference ever constructed with a timer system. It actually makes struct/stack loops practically redundant, considering to even gain that fractional speed advantages you need 100 instances of one piece of code. If you're writing spells which usually only have 1-2 instances going, max, then KT2 is actually faster than a struct/stack loop.

But when the margins are that small anyway, what's more important is the time it takes to implement the loop VS the time it takes to use KT2. KT2 is a time and thought saver, with a brilliantly straight forward interface.

The only time I would ever use a struct/stack loop is when I know a piece of code is likely to be executing with 50 instances at once.
I don't have time to look at your test right now. I will later.

Edit: I reviewed your test. I didn't bother to run it, because:
1. It doesn't attach data.
2. Your "TU" test has nothing to do with TU.
3. You run the timers on different periods.
4. You haven't commented your code and you used one letter global variables, so no one even knows what it does or what you're trying to test.
To be honest, I don't really care anyway. I already benchmarked. Results are in the graph I referred to at the start of this post.
 
Last edited:
Level 8
Joined
Aug 6, 2008
Messages
451
Have to say that KT2 is really impressive. :thumbs_up:

But how fast is that linked list stuff in Hibbernate Timer?

If I only need few instances of, lets say 3.5 sec timers, is it better to use KT2 or TimerUtils?
 
Level 3
Joined
Apr 13, 2004
Messages
28
>If I only need few instances of, lets say 3.5 sec timers, is it better to use KT2 or TimerUtils?

Whichever you like the interface of more. If you're asking about speed, the answer is TU red. But you will NEVER get a tangible benefit in a scenario like that.

KT2's handling for periods over 0.3 seconds is "slow" in the autolink call. It is called every time a period over 0.3 seconds expires. Now, it's not as slow as something like, say, ye old generic native function like DestroyTrigger or CreateTrigger or something, unless you have maybe 50 instances of periods over 0.3 seconds (and you can imagine why that's unlikely). Since it only expires every 3.5 seconds, the question is do you care about that fragment of overhead, or about how quick it is to code with? But if you're asking which is faster, TU red is (although I haven't tested this, and thinking about it KT2 may even be faster for a low number).

Obviously KT2 is faster for low periods. But using TU red for higher periods is technically a good idea. My theory is if you implemented both in your maps, after a while you'd get sick of TU red having to declare a local timer and set it and whatever for no visible advantage. So you can be slack instead and use KT2. :p

>You don't have to attach data if you loop through the structs.
>I totally agree with Deaod.

Good. Go do that. It takes another 10 minutes of coding, executes slower if you have 2 instances max, and otherwise only saves an overhead equivalent to DestroyTrigger. For people who have better things to do with their time... KT2.

>Also, why should you want a system faster than TU? You will never notice any lag.

For very low periods. And it has a nicer interface, and is (arguably) more stable.
 
Level 11
Joined
Nov 4, 2007
Messages
337
This took me 3 minutes:
JASS:
library Test initializer Init
    globals
        private constant real FPS = 32.00
        private timer Ex = CreateTimer()
        private What array WhatData
        private integer count = 0
    endglobals

struct What
    string wtf
    integer bla
    
    method ClearInstance takes integer iterator returns nothing
        call WhatData[iterator].destroy()
        set WhatData[iterator] = WhatData[count]
        set count = count - 1
    endmethod
    
    static method NewInstance takes nothing returns What
        local What w = What.create()
        set count = count + 1
        set WhatData[count] = w
        return w
    endmethod
endstruct

private function Loop takes nothing returns nothing
    local integer i = 0
    local What temp
    loop
        set i = i + 1
        exitwhen i > count
        set temp = WhatData[i]
        // Do whatever you want.
        
        set temp.bla = temp.bla + 1
        if temp.bla > 500 then
            call temp.ClearInstance(i)
        endif
        
    endloop
endfunction

function InsertData takes nothing returns nothing
    call What.NewInstance()
endfunction

private function Init takes nothing returns nothing
    call TimerStart(Ex,(1./FPS),true,function Loop)
endfunction
endlibrary

And: You can copy and paste this system, and you'd just have to change the Parameters of Insert code, the loop thing, the library name and replace What with another name or make the struct private.

And it's very probable for this kind of libraries to have more than 2 instances.
They are actually only used to move things, for example for knockbacks.
 
Level 3
Joined
Apr 13, 2004
Messages
28
This took me 10 seconds:
KT_Add(function x, d, 0.03125)

But a few of points to consider.

1. You could've used KT1 (note KT1 not KT2) and implemented that in 20 seconds (and KT1 is more efficient at a glance).
2. You can hang your loop off KT2 anyway, then you can make it even faster. I'm planning to write a module to do this, once modules are in common use. ;)
3. Your speed advantage is a few nanoseconds (actually, you may not even have one). A speed advantage so small serves very little practical purpose.

On the one hand you say:
>Also, why should you want a system faster than TU? You will never notice any lag.
And then you go and write something to gain a ridiculously small advantage on something already faster than TU.

The reason for KT2 is not efficiency, it's the awesome interface. KT2 just also happens to also be the fastest timer system in existance for small periods (where it is nice to have the speed).
 
Level 11
Joined
Nov 4, 2007
Messages
337
Your KT_Add(function x, d, 0.03125) is like
call TimerStart(Ex,(1./FPS),true,function Loop)
call What.NewInstance()

Write the whole library!

Edit:

JASS:
library ATest requires KT
    globals
        private constant real FPS = 32.00
    endglobals

struct KTWhat
    string wtf
    integer bla
endstruct

private function Loop takes nothing returns nothing
    local integer i = 0
    local KTWhat temp = KT_GetData
    
    set temp.bla = temp.bla + 1
    if temp.bla > 500 then
        call temp.destroy()
    endif
endfunction

public function InsertData takes nothing returns nothing
    local KTWhat w = KTWhat.create()
    call KT_Add(function Loop,integer(w),(1./FPS))
endfunction
endlibrary


That's it with KT. But I can't imagine it's fast enough.. It uses TriggerEvaluate, doesn't it?
I'll make some speed tests.
 
Level 3
Joined
Apr 13, 2004
Messages
28
I think you're still missing the point. This example has shown that code written with KT2 is also easier to maintain, as it is much smaller and reduces repitition.

Edit: Gah. Take your time and write proper posts. Stop making edits so much, please.

>It uses TriggerEvaluate, doesn't it?
It only uses one TriggerEvaluate to execute all code. Not one per piece of attached code. That's why it's so fast.

Fixed your posted code a bit. -.-
JASS:
library ATest requires KT
globals
    private constant real FPS = 32.00
endglobals

struct KTWhat
    string wtf
    integer bla
endstruct

private function Loop takes nothing returns boolean
    local KTWhat temp = KT_GetData()
    set temp.bla = temp.bla + 1
    if temp.bla > 500 then
        call temp.destroy()
        return true
    endif
    return false
endfunction

public function InsertData takes nothing returns nothing
    call KT_Add(function Loop,KTWhat.create(),(1./FPS))
endfunction
endlibrary

>I'll make some speed tests.
Cool. Please post them so I can see what you're testing.
 
Last edited:
Level 11
Joined
Nov 4, 2007
Messages
337
8 Instances
KT: 27%
Iterating: 100%

30 Instances
KT: 16%
Iterating: 100%

90 Instances
KT: 24%
Iterating: 100%


Note: I inlined the test functions directly into KT.
[hidden ="Test System"]
JASS:
library Benchmark initializer Init
    globals
        private real array Time
        private integer array Runs
        private real t0
        private real t1
        private integer Clock
        private integer System
        private boolean Running = false
        private integer sysc = 0
        private string array sysn
        private integer array sysm
    endglobals
    
struct Test
    static method Declare takes string name, integer Max returns nothing
        set sysc = sysc + 1
        set sysn[sysc] = name
        set sysm[sysc] = Max
    endmethod
    
    static method Convert takes string name returns integer
        local integer i = 0
        loop
            set i = i + 1
            exitwhen i > sysc
            if name == sysn[i] then
                return i
            endif
        endloop
        call BJDebugMsg("TEST: System "+name+" is not registered.")
        return -1
    endmethod
    
    static method Start takes string Name returns nothing
        if Running == false then
            set Running = true
            set System = Test.Convert(Name)
            set Runs[System] = Runs[System] + 1
            set t0 = StopWatchMark(Clock)
        else
            call BJDebugMsg("TEST: "+sysn[System]+" is still running.")
        endif
    endmethod
    
    static method End takes nothing returns nothing
        set t1 = StopWatchMark(Clock)
        set Time[System] = Time[System] + (t1-t0)
        set System = 0
        set Running = false
    endmethod
    
    static method Display takes string Name returns nothing
        local integer sys = Test.Convert(Name)
        call BJDebugMsg(Name+ " took "+R2S(Time[sys])+ " seconds for "+I2S(Runs[sys])+ " executions." )
    endmethod
    
    static method Compare takes nothing returns nothing
        local integer i = 0
        local real highest = Time[1]
        loop
            set i = i + 1
            exitwhen i > sysc
            if Time[i] < highest then
                set highest = Time[i]
            endif
        endloop
        
        set i = 0
        loop
            set i = i + 1
            exitwhen i > sysc
            call BJDebugMsg(sysn[i]+": "+R2S(highest/Time[i]*100.)+"%")
        endloop
    endmethod
endstruct

private function Init takes nothing returns nothing
    set Clock = StopWatchCreate()
endfunction
endlibrary
[/hidden]

Tested 500 times.
With 90 Instances for example, the code was calles 90*500 times.
 
Level 3
Joined
Apr 13, 2004
Messages
28
>It would be nice to have an overview of the functions.
>How do I stop a KT Timer?

The function overview is at the top of the system. To stop you return true instead of false, as it says.

Your test is looking a little shaky. It shouldn't vary by 40% like that. And I can't actually see any KT code in your test whatsoever. This is a problem. Attach the map, or your test results hold no value.

My expected result is 33%. I'd say your tests came close if they didn't swing out so badly for no reason. So instead I'd be more inclined to say they're unstable.

I'd like to see three things:
1. The actual test, rather than half of it. So I can actually run it, for example.
2. The difference in nanoseconds of the overhead of each "system" per instance (stating the nanosecond overhead of both).
3. A test for 1 and 2 instances of 10 different structs on one period, since you tried a test that grandly favours your method the first time. :)

If you do #3, you should end up with something like:
Test 3
KT: 123%
Iterating: 100%

And my tests don't have a 40% margin of error. -.-

Cheers.
 
Level 14
Joined
Nov 18, 2007
Messages
816
1. It doesn't attach data.
2. Your "TU" test has nothing to do with TU.
3. You run the timers on different periods.
4. You haven't commented your code and you used one letter global variables, so no one even knows what it does or what you're trying to test.
To be honest, I don't really care anyway. I already benchmarked. Results are in the graph I referred to at the start of this post.
1.) Of course it doesnt. Its senseless to attach data. I showed you a "real world" application of direct timer usage.
2.) It did use TU earlier, but because i hit the op limit when initializing with 1000 timers, i switched to plain timers.
3.) then thats a leftover, period on all tested systems should be 0.
4.) i replicated your tests, testing KT and TT against direct timer usage.
For all i care: Your claim with 2 instances is bullshit, because TriggerEvaluate is slow. The loop overhead makes direct timer usage slower for one instance.
But for two instances running off of one timer, i get an execution count of 94k (when running 500 timers), while with 1000 KT instances i get an execution count of 65k.

Edit: attached current test map.
 

Attachments

  • KeyTimersTest.w3m
    52.5 KB · Views: 89
Last edited:
Level 3
Joined
Apr 13, 2004
Messages
28
>For all i care: Your claim with 2 instances is bullshit, because TriggerEvaluate is slow.
For 1000 instances on one KT2 period there should be exactly 1 TriggerEvaluate. So 2 instances of 10 difference pieces of code running on 1 period = 1 TriggerEvaluate, as opposed to 10 timers looping through 2 instances.

I finally got an idea of what your test does. :)

Fallacy:
JASS:
        if t-s-c>=0 then
            set r[c]=i
            set c=c+1
            set i=0
        endif
For the direct usage test this overhead is divided between two "instances". For the KT2 test this is for every instance.

Furthermore, when looping through your instances on direct timer usage, you will need to load the attached data from an array. The KT2 test has this overhead already added with your KT_GetData() calls, which makes this unfair.

C'mon. Cut us a fair test. Make it call the following, once per instance, passing in an attached struct (or integer, but the point is it must be attached, not an index, because in your system the indexes will change whenever an instance is removed from the middle somewhere) of some sort:
JASS:
function DoSomething takes integer data returns boolean
return false
endfunction
 
Level 3
Joined
Apr 13, 2004
Messages
28
MALTO:

You certainly didn't do any of what I requested except upload the map, and you combined testing the Add function with the actual loop which is silly, but that's where the ~6% disparity must be between our tests, so I say... GOOD TEST! :D

If you remove the "Add" and test the loop separately, you'll probably get even closer to my 33%.

Which is exactly what I said. 12 nanosec vs 4 nanosec; looping directly is 3x faster. But telling people this percentage doesn't tell them anything useful. Tell us how many nanoseconds they add as overhead to each instance! On my very slow computer, I got 8, as said. Which is completely useless as a speed advantage.

10 instances... 30 instances... 90 instances... you'll find that 30% to 100% you clocked will be constant. UNTIL! You make the test more realistic and do 2 instances for 10 different pieces of code, and then you will clock KT2 beating the hell out of your hard coded struct/stack loop. Because your code cannot handle a low number of instances for many different pieces of code running on the same period. ;) So you have tested a situation which is unrealistically advantageous to your code, and found that it is insignificantly better. You stated it as a percentage to make it look big.

But thanks for the test. You've prooved that my tests are accurate. :D

DEAOD:
You're treating this like some kind of joke. As I said, make it hold some data (an attached struct or integer) and call some function and pass this in. Currently your "system" is useless. It DEFINITELY doesn't acomplish what KT2 does (not that it ever could, but you could at least make it fairer by clearly defining where a user's code would go, having access to an attached struct/integer).

We have already proven that you wrote a rigged test last time. So write a proper test, with valid variable names, that actually tells the viewer what it does. Your tests should have some clear consistency and meaning, or you're wasting your time.

PS. Wouldn't mind seeing the results for 1 instance too. :)
 
Level 11
Joined
Nov 4, 2007
Messages
337
I'll test it again.
But the test I made now is valid.
It puts an error out, if you make something like this:

JASS:
function A takes nothing returns nothing
    call Test.Start("A")
    // CODE ==> ERROR!
    // One Thread open.
    call Test.End("A")
endfunction

function B takes nothing returns nothing
    call Test.Start("A")
    call A()
    // REST CODE
    call Test.End("A")
endfunction

So if you put the Test thing correctly into each function, you get the exact speed of a system.

I'll also test it with many structs ;)
I think, direct usage is still much faster.


Edit: 2 Instances

KT: 14% ==> Poor xD
 
Level 3
Joined
Apr 13, 2004
Messages
28
Honestly, I liked the way you set out your test, Malte. But I don't feel the Add function is relevant to the speed of the system, because for one Add call you may (and are even likely to) get 500 loops... All I care about in systems is the speed of the loop.

Anyway. My expectations, once again:

For 1 instance of each of 10 different pieces of code running on the same period, KT2 should be faster by some percentage. I think it was 26%.

For 2, I haven't tested, to be honest. My point was that if a spell has 2 instances max, it's unlikely to get past 1 instance.

Remember, the point of this system is to be able to code quickly, and to be faster than other timer systems. So far what you guys are writing aren't timer systems. They're chunks of code that run one specific chunks of code. This means you need to copy/paste it each time to use it. And I'm not denying it's faster for things that are likely to run many instances at once. I'm just saying that speed advantage is insignificant compared to the size of the code they're probably running, and the time and thought it takes to implement. :p

So Malte:
2. The difference in nanoseconds of the overhead of each "system" per instance (stating the nanosecond overhead of both).
3. A test for 1 and 2 instances of 10 different structs on one period, since you tried a test that grandly favours your method the first time. :)

Edit for your edit:
>KT: 14% ==> Poor xD
Yeah, for 2 instances of one piece of code I'd expect that. My point is (2 instances)x10. KT2 gets faster as you add more items of code on the same period. :D That's why it's faster than a direct struct/stack loop.

Can't we do something more fun, like benchmark KT2 against some timer system (ie. isn't implemented via copy/paste/modules/textmacros)? You know, a real system? KT2 trashes those. :p
 
Last edited:
Level 14
Joined
Nov 18, 2007
Messages
816
new map, added some comments.
timers are still faster: 75k vs 68k

for one instance KT is faster: 59k vs 68k

four instances per timer: 88k vs 68k

Do i have to mention i can run every timer on another frequency without adding a HUGE overhead (that is: no new overhead)?
 

Attachments

  • KeyTimersTest.w3m
    52.8 KB · Views: 89
Level 11
Joined
Nov 4, 2007
Messages
337
882 nanoseconds vs 233 Nanoseconds...
We didn't make systems, because they're not necessary.
But you know what'd be useful?
To create a Module with the methods

.include
.exclude
.forIncluded (static)

Thats creates a thisstruct array and does all the stuff automatically.

implement StructIterator
 
Level 3
Joined
Apr 13, 2004
Messages
28
Those nanosecond counts CANNOT be the overhead per instance. I got 4 and 12 on a 5 year old computer.

Well, as for modules... I'm sure someone will do that some day. I mean, Vexorian pretty much already did it. But really it's the same as KT1.
JASS:
//! textmacro KeyTimer takes SUBJECT, PERIOD
globals
	private timer $SUBJECT$Timer=CreateTimer()
	private integer $SUBJECT$TimerMax=0
	private integer array $SUBJECT$TimerKey
	private constant real $SUBJECT$Period=$PERIOD$
endglobals
private function $SUBJECT$TimerLoop takes nothing returns nothing
	local integer i=$SUBJECT$TimerMax
	loop
		exitwhen i==0
		if Do$SUBJECT$($SUBJECT$TimerKey[i]) then
			set $SUBJECT$TimerKey[i]=$SUBJECT$TimerKey[$SUBJECT$TimerMax]
			set $SUBJECT$TimerKey[$SUBJECT$TimerMax]=0
			set $SUBJECT$TimerMax=$SUBJECT$TimerMax-1
		endif
		set i=i-1
	endloop
	if $SUBJECT$TimerMax==0 then
		call PauseTimer($SUBJECT$Timer)
	endif
endfunction
function Add$SUBJECT$ takes integer x returns nothing
	set $SUBJECT$TimerMax=$SUBJECT$TimerMax+1
	set $SUBJECT$TimerKey[$SUBJECT$TimerMax]=x
	if $SUBJECT$TimerMax==1 then
		call TimerStart($SUBJECT$Timer,$PERIOD$,true,function $SUBJECT$TimerLoop)
	endif
endfunction
//! endtextmacro
If you want to port that over to modules, feel free... It just calls DoSUBJECT(data) for all attached integers every PERIOD. You add them with AddSUBJECT. I assure you it's very nicely optimised.

Lol, it's actually secretly what I use. But I'm gonna switch most of my stuff over to KT2 anyway because it's better.

Deaod:
>for one instance KT is faster: 59k vs 68k

I knew you'd come to this conclusion if you fixed your test.
Thankyou.

Now let's all go use KT1 for things that do more than one instance on average until we get sick of the crappy interface, and realise there's no point in not switching to KT2. Or something like that.

PS. Thanks for updating your test. Looks nice, now. :)
 
Level 3
Joined
Apr 13, 2004
Messages
28
Mmmhmm. Always nice to see numbers. But you're right, I missed that.
(Lol, even re-reading the post I missed that twice until I did a search on the phrase. It's 3:30 am here.)
 
Level 11
Joined
Nov 4, 2007
Messages
337
New Test.
I tested how you said.
KT: 38%

Here the new Testing library (im suggesting to present it);
JASS:
library Emmeasure initializer Init
    globals
        private real array Time
        private integer array Runs
        private real t0
        private real t1
        private integer Clock
        private integer System
        private boolean Running = false
        private integer sysc = 0
        private string array sysn
        private integer array sysm
        private string array StableWord
    endglobals

function Int2Hex takes integer int returns string
    local string charMap = "0123456789ABCDEF"
    local string hex = ""
    local integer index = 0
    if ((int < 0) or (int > 0x7FFFFFFF)) then
        return "|cffff0000Invalid argument in function Int2Hex()!|r"
    endif
    loop
        set index = ModuloInteger(int, 0x10) + 1
        set int = int / 0x10
        set hex = SubStringBJ(charMap, index, index) + hex
        exitwhen (int == 0)
    endloop
    return hex
endfunction


function Per2Clr takes string text, integer r, integer g, integer b, integer alpha returns string
    local string array str 
    if ((r >= 0) and (r <= 100) and (g >= 0) and (g <= 100) and (b >= 0) and (b <= 100) and (alpha >= 0) and (alpha <= 100)) then
        set r = R2I(0xFF / 100.000 * r)
        set g = R2I(0xFF / 100.000 * g)
        set b = R2I(0xFF / 100.000 * b)
        set alpha = R2I(0xFF / 100.000 * alpha)
        if (alpha <= 0xF) then
            set str[0] = "0" + Int2Hex(alpha)
        else
            set str[0] = Int2Hex(alpha)
        endif
        if (r <= 0xF) then
            set str[1] = "0" + Int2Hex(r)
        else
            set str[1] = Int2Hex(r)
        endif
        if (g <= 0xF) then
            set str[2] = "0" + Int2Hex(g)
        else
            set str[2] = Int2Hex(g)
        endif
        if (b <= 0xF) then
            set str[3] = "0" + Int2Hex(b)
        else
            set str[3] = Int2Hex(b)
        endif 
        return "|c" + str[0] + str[1] + str[2] + str[3] + text
    else
        return "|cffff0000Invalid arguments in function Per2Clr()!|r"
    endif        
endfunction

private function CreatePercentageBar takes real Percent returns string
    local string ret = Per2Clr("",100-R2I(Percent),R2I(Percent),0,0)
    local integer i = 0
    local boolean b = true
    loop
        set i = i + 1
        exitwhen i == 100
        if i-1 == R2I(Percent) and  b then
            set ret = ret + "|cff808080"
            set b = false
        endif
        set ret = ret + "|"
    endloop
    return ret
endfunction

struct Test
    static method Declare takes string name, integer Max returns nothing
        set sysc = sysc + 1
        set sysn[sysc] = name
        set sysm[sysc] = Max
    endmethod
    
    static method Convert takes string name returns integer
        local integer i = 0
        loop
            set i = i + 1
            exitwhen i > sysc
            if name == sysn[i] then
                return i
            endif
        endloop
        call BJDebugMsg("TEST: System "+name+" is not registered.")
        return -1
    endmethod
    
    static method Start takes string Name returns nothing
        if Running == false then
            set Running = true
            set System = Test.Convert(Name)
            set Runs[System] = Runs[System] + 1
            set t0 = StopWatchMark(Clock)
        else
            call BJDebugMsg("TEST: "+sysn[System]+" is causing an open thread.")
        endif
    endmethod
    
    static method End takes string s returns nothing
        set t1 = StopWatchMark(Clock)
        set Time[System] = Time[System] + (t1-t0)
        set System = 0
        set Running = false
    endmethod
    
    static method Display takes string Name returns nothing
        local integer sys = Test.Convert(Name)
        call BJDebugMsg(Name+ " took "+R2S(Time[sys])+ " seconds for "+I2S(Runs[sys])+ " executions." )
    endmethod
    
    static method Compare takes nothing returns nothing
        local integer i = 0
        local real highest = Time[1]
        local real perc
        local real Exec = 0.
        local integer Temp
        loop
            set i = i + 1
            exitwhen i > sysc
            if Time[i] < highest then
                set highest = Time[i]
            endif
        endloop
        
        set i = 0
        call BJDebugMsg("================================")
        loop
            set i = i + 1
            exitwhen i > sysc
            set Exec = Exec + Runs[i]
            set perc = highest/Time[i]*100.
            call BJDebugMsg(sysn[i]+": "+R2S(perc)+"%")
            call BJDebugMsg(CreatePercentageBar(perc))
        endloop
        set Exec = Exec / sysc
        set Temp = R2I(Exec/150)
        if Temp > 7 then
            set Temp = 7
        endif
        call BJDebugMsg("Testing Quality: "+StableWord[Temp])
    endmethod
endstruct

private function Init takes nothing returns nothing
    set Clock = StopWatchCreate()
    set StableWord[0] = Per2Clr("unacceptable",100,0,0,0)
    set StableWord[1] = Per2Clr("poor",86,14,0,0)
    set StableWord[2] = Per2Clr("lacking",71,28,0,0)
    set StableWord[3] = Per2Clr("not bad",50,50,0,0)
    set StableWord[4] = Per2Clr("fine",30,70,0,0)
    set StableWord[5] = Per2Clr("safe",20,80,0,0)
    set StableWord[6] = Per2Clr("Great",13,87,0,0)
    set StableWord[7] = Per2Clr("Excellent",0,100,0,0)
endfunction
endlibrary
 

Attachments

  • Wc3 test.jpg
    Wc3 test.jpg
    561.1 KB · Views: 86
Level 3
Joined
Apr 13, 2004
Messages
28
Eh? Sorry but Daeod already ran my Test #3 and found KT2 to be faster, like I said. I'm sure you put a lot of work into that code, but honestly, there is no reason for it to be that big. I don't have time to read through it and figure it out, considering two of us have already done the test and gotten the same result. Honestly, I thought we were done with the benchmarking. Everything I said has been proven by you two.

>Ok, this system is well made.
Well, thanks.

>For low periods you don't need attaching.
Look, the point of KT2 is it saves you time. It's not worthwhile to code a loop every time you need a timed effect. KT2 will hands-down beat any other time system for small periods, as I'm sure you've proven to yourselves (because it's even comparable to a loop) and it's quicker to code with. It allows you to add throwaway eye candy to a spell without thinking about it. The efficiency difference between KT2 and a loop is insignificant.

>We have everything we need...
You sure do. Such as too much time on your hands. Of course you don't see any point in using KT2. You'd rather spend 10 minutes beating a couple of nanoseconds out of your code. :p

I think other people may find this useful, though.

Edit:
>I tested how you said.
If you mean testing the loop function and not the add function, then 38% is brilliant. ^_^
 
Last edited:
Level 11
Joined
Nov 4, 2007
Messages
337
I mean with many structs..
With loop and Add.. that's the real system's speed: 27%

So.. I have made the library for looping, and it is FAST and you can make a new one like that in 20 seconds.. You don't have to save time! There is no reason.
You can c'n'paste this.

Example for module implementation:
JASS:
module Container
private static thistype Data
private static integer DataCount = 0
private static trigger TempTrig = CreateTrigger()
private static thistype Struct
private integer thisIndex = 0

method Include takes nothing returns nothing
    set DataCount = DataCount + 1
    set Data[DataCount] = this
    set .thisIndex = DataCount
endmethod

method exclude takes nothing returns nothing
    set Data[.thisIndex] = Data[DataCount]
    set DataCount = DataCount - 1
    set .thisNumber = DataCount
endmethod

static method forContained takes code iterate returns nothing
    local integer i = 0
    call TriggerAddAction(TempTrig,iterate)
    loop
        set i = i + 1
        exitwhen i > DataCount
        set Sttruct = Data[i]
        call TriggerEvaluate(TempTrig)
    endloop
    call TriggerClearActions(TempTrig)
endmethod
endmodule
 
Last edited:
Level 3
Joined
Apr 13, 2004
Messages
28
Your example is poor. I'm gonna port KT1 over myself. Your code is SLOWER than KT2. And I don't mean by just a little bit. And that's if it worked. Which your code doesn't actually do. You add actions and then call evaluate. Even if it did work, it'd be bugged because of the way you add. Instances added from within the loop would execute immediately.

Edit: Key Timers 1 in a module form. This is ACTUALLY fast. I couldn't actually have the latest version of jasshelper syntax check it successfully due to "unknown errors", but it should work from my limited understanding, as I got it to work via copy/paste on the previous version of jasshelper. Please note that I actually worked on KT1 for weeks. I've carefully optimised it and put a lot of thought into the way it works.

JASS:
module KeyTimer // Module of KT1. Implement a timerAction method that returns true on release.
    private static timer KT_Timer=CreateTimer()
    private static integer KT_DataMax=0
    private static thistype array KT_Data
    private static real KT_Period=0.03125
    private static thistype KT_Current
    
    private static method KeyTimerLoop takes nothing returns nothing
        local integer i=.KT_DataMax
        loop
            exitwhen i==0
            if thistype(.KT_Data[i]).timerAction() then
                set .KT_Data[i]=.KT_Data[.KT_DataMax]
                set .KT_DataMax=.KT_DataMax-1
            endif
            set i=i-1
        endloop
        if .KT_DataMax==0 then
            call PauseTimer(.KT_Timer)
        endif
    endmethod

    method addToKT takes nothing returns nothing
        set .KT_DataMax=.KT_DataMax+1
        set .KT_Data[.KT_DataMax]=this
        if .KT_DataMax==1 then
            call TimerStart(.KT_Timer,.KT_Period,true,function thistype.KeyTimerLoop)
        endif
    endmethod
    
    static method setKTPeriod takes real period returns nothing // Only call on intialisation.
        set .KT_Period=period
    endmethod
endmodule

Please note that this is a terrible "system", but it's slightly faster than KT2 in some rarer circumstances, and is indeed the "fastest possible" "system".

>it is FAST
What a lie.
 
Last edited:
Level 3
Joined
Apr 13, 2004
Messages
28
>Edit: TT is faster.
That's not what your test says?
But if TT is faster, then KT2 is also. KT2 is faster than TT.

>No, my code is faster
I'm sorry but there is simply no way your code is faster than KT2. Don't gamble your credibility by making claims like that without testing it. I would never use your module simply because I could just use KT2 instead for extra speed and a simpler interface.

Besides, it's obvious you still haven't tested your code because it's still broken. Terribly. And it's still slow.

I think you still don't appreciate exactly how fast KT2 is. -.-
(Although of course the point is its simple interface.)

PS. Are you are still bench testing the "Add" speed of these systems? If you are, you're just blatantly rigging the tests in your favour. The loop overhead will come up 500 times realistically for each Add call, so unless you divide the Add difference by 500 your test is meaningless.
 
Level 3
Joined
Apr 13, 2004
Messages
28
>TT is faster than KT
Something is seriously wrong with you. You can't even read your own test results.

Your test says KT2 is ~20% faster than TT, just like every other test. I read it off your screenshot. o.o

You do realise that if something takes longer to execute that it's slower? o.o;

Edit: Your test should test the systems evenly (the same number of times). And running them in the same map can effect the speed of eachother, even if they're not run at the same time. It's because of the handle count, I hear.

We thoroughly tested the speed of KT2 vs TT on TheHelper. As in, the moderators ran the tests. Find them and run those.
 
Last edited:
Level 11
Joined
Nov 4, 2007
Messages
337
The systems don't use handle IDs.
I run them the same amount of times. 'Executions' means only, how often a test was started for a system, that means, how often system code was called.
Actually each system was tested 60*5000 times.

Edit: I mean, the higher the '5000' times, the lower the speed advantage of KT over TT.


Edit: Ok, I jsut found out that KT isn't faster than TT, but TT starts at map init and KT if you call it the first time...
 
Last edited:
Level 3
Joined
Apr 13, 2004
Messages
28
KT has a faster loop speed than TT with the same number of instances on a single period.

Stop randomly stating contrary conclusions. You don't make sense.
 
Level 3
Joined
Apr 13, 2004
Messages
28
Hey, because you like "SI" so much, can you compare it to PM?

To make the test fair, if SI inlines user code, please inline the user code for PM too. I want a test of just the quality of the loop.

If you could post your results here (dispite the fact that it's off topic) that would be great. I'm just curious. :)

>Guy, learn to read.
"the lower the speed advantage of KT over TT"
"Ok, I jsut found out that KT isn't faster than TT"
You randomly were stating contrary conclusions. But we've agreed on the important points so we needn't discuss that further.
 
Level 11
Joined
Nov 4, 2007
Messages
337
What's SI?
I just updated Emmeasure, now you can Enable a option that tests the speed of single functions. Do you mean that?

Edit: Ahh, Struct Iterating. Sorry, I can't. Because the JassNewGen doesn't support modules yet, and the JassHelper doesn't support grimoire.
But...
Wait, I can inline it by hand.
But later, I got to make a cake now.
 
Last edited:
Top