System Timer32

Jesus4Lyf

Good Idea™
Reaction score
397
Timer32​
Version 1.06​

Requirements:
- Jass NewGen

JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ Timer32 ~~ By Jesus4Lyf ~~ Version 1.06 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is Timer32?
//         - Timer32 implements a fully optimised timer loop for a struct.
//         - Instances can be added to the loop, which will call .periodic every
//           PERIOD until .stopPeriodic() is called.
//
//    =Pros=
//         - Efficient.
//         - Simple.
//
//    =Cons=
//         - Only allows one period.
//         - The called method must be named ".periodic".
//
//    Methods:
//         - struct.startPeriodic()
//         - struct.stopPeriodic()
//
//         - private method periodic takes nothing returns nothing
//
//           This must be defined in structs that implement Periodic Module.
//           It will be executed by the module every PERIOD until .stopPeriodic() is called.
//           Put "implement T32x" BELOW this method.
//
//    Modules:
//         - T32x
//           Has no safety on .stopPeriodic or .startPeriodic (except debug messages
//           to warn).
//
//         - T32xs
//           Has safety on .stopPeriodic and .startPeriodic so if they are called
//           multiple times, or while otherwise are already stopped/started respectively,
//           no error will occur, the call will be ignored.
//
//         - T32
//           The original, old version of the T32 module. This remains for backwards
//           compatability, and is deprecated. The periodic method must return a boolean,
//           false to continue running or true to stop.
//
//  Details:
//         - Uses one timer.
//
//         - Do not, within a .periodic method, follow a .stopPeriodic call with a
//           .startPeriodic call.
//
//  How to import:
//         - Create a trigger named T32.
//         - Convert it to custom text and replace the whole trigger text with this.
//
//  Thanks:
//         - Infinitegde for finding a bug in the debug message that actually altered
//           system operation (when in debug mode).
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library T32 initializer OnInit
    globals
        public constant real PERIOD=0.03125
        public constant integer FPS=R2I(1/PERIOD)
        public integer Tick=0 // very useful.
        
//==============================================================================
        private trigger Trig=CreateTrigger()
    endglobals
    
    //==============================================================================
    // The standard T32 module, T32x.
    //
    module T32x
        private thistype next
        private thistype prev
        
        private static method PeriodicLoop takes nothing returns boolean
            local thistype this=thistype(0).next
            loop
                exitwhen this==0
                call this.periodic()
                set this=this.next
            endloop
            return false
        endmethod

        method startPeriodic takes nothing returns nothing
            debug if this.prev!=0 or thistype(0).next==this then
            debug   call BJDebugMsg("T32 ERROR: Struct #"+I2S(this)+" had startPeriodic called while already running!")
            debug endif
            set thistype(0).next.prev=this
            set this.next=thistype(0).next
            set thistype(0).next=this
            set this.prev=thistype(0)
        endmethod
        
        method stopPeriodic takes nothing returns nothing
            debug if this.prev==0 and thistype(0).next!=this then
            debug   call BJDebugMsg("T32 ERROR: Struct #"+I2S(this)+" had stopPeriodic called while not running!")
            debug endif
            // This is some real magic.
            set this.prev.next=this.next
            set this.next.prev=this.prev
            // This will even work for the starting element.
            debug set this.prev=0
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(Trig,Condition(function thistype.PeriodicLoop))
        endmethod
    endmodule
    
    //==============================================================================
    // The standard T32 module with added safety checks on .startPeriodic() and
    // .stopPeriodic(), T32xs.
    //
    module T32xs
        private thistype next
        private thistype prev
        private boolean runningPeriodic
        
        private static method PeriodicLoop takes nothing returns boolean
            local thistype this=thistype(0).next
            loop
                exitwhen this==0
                call this.periodic()
                set this=this.next
            endloop
            return false
        endmethod

        method startPeriodic takes nothing returns nothing
            if not this.runningPeriodic then
                set thistype(0).next.prev=this
                set this.next=thistype(0).next
                set thistype(0).next=this
                set this.prev=thistype(0)
                
                set this.runningPeriodic=true
            endif
        endmethod
        
        method stopPeriodic takes nothing returns nothing
            if this.runningPeriodic then
                // This is some real magic.
                set this.prev.next=this.next
                set this.next.prev=this.prev
                // This will even work for the starting element.
                
                set this.runningPeriodic=false
            endif
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(Trig,Condition(function thistype.PeriodicLoop))
        endmethod
    endmodule
    
    //==============================================================================
    // The original T32 module, for backwards compatability only.
    //
    module T32 // deprecated.
        private thistype next
        private thistype prev
        
        private static method PeriodicLoop takes nothing returns boolean
            local thistype this=thistype(0).next
            loop
                exitwhen this==0
                if this.periodic() then
                    // This is some real magic.
                    set this.prev.next=this.next
                    set this.next.prev=this.prev
                    // This will even work for the starting element.
                    debug set this.prev=0
                endif
                set this=this.next
            endloop
            return false
        endmethod

        method startPeriodic takes nothing returns nothing
            debug if this.prev!=0 or thistype(0).next==this then
            debug   call BJDebugMsg("T32 ERROR: Struct #"+I2S(this)+" had startPeriodic called while already running!")
            debug endif
            set thistype(0).next.prev=this
            set this.next=thistype(0).next
            set thistype(0).next=this
            set this.prev=thistype(0)
        endmethod
        
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(Trig,Condition(function thistype.PeriodicLoop))
        endmethod
    endmodule
    
    //==============================================================================
    // System Core.
    //
    private function OnExpire takes nothing returns nothing
        set Tick=Tick+1
        call TriggerEvaluate(Trig)
    endfunction
    
    private function OnInit takes nothing returns nothing
        call TimerStart(CreateTimer(),PERIOD,true,function OnExpire)
    endfunction
endlibrary


Demonstration:
JASS:
scope test initializer dotest
    private struct teststruct
        private integer endTick
        private method periodic takes nothing returns nothing
            call BJDebugMsg(I2S(this))
            
            if this.endTick == T32_Tick then // no more this.ticksLeft stuff and incrementing.
                call this.stopPeriodic() // never follow with a .startPeriodic() call
                call this.destroy()      // of any sort (when in the .periodic method).
            endif
        endmethod
        implement T32x
        static method create takes real duration returns thistype
            local thistype this = thistype.allocate()
            set this.endTick = T32_Tick + R2I(duration / T32_PERIOD)
            return this
        endmethod
    endstruct
    private function dotest takes nothing returns nothing
        // Creates structs which display their integer id 32 times a second for x seconds.
        call teststruct.create(3.0).startPeriodic()
        call teststruct.create(5.0).startPeriodic()
    endfunction
endscope
Should be faster than a struct/stack loop.
Actually, it should be faster than anything. The only downside is it only allows one period. So this can't make multi-period timer systems redundant.

Updates:
- Version 1.06: Deprecated T32 module, added T32x module with .stopPeriodic instead of return true/false stopping. Added T32xs module, which is similar to T32x but has .stopPeriodic/.startPeriodic safety, meaning these can be called regardless of the state of the struct, and if it is already stopped/started respectively, the call will be ignored.
- Version 1.05: Fixed a minor bug that occurred in debug mode (returning true would terminate the struct type's loop for that tick).
- Version 1.04: Added T32_FPS constant and a debug message for calling startPeriodic twice on a struct.
- Version 1.03: Optimised loop algorithm. The fastest struct looping ever.
- Version 1.02 (Release): Cleaned up code a lot. Simplified implementation.
- Version 1.01 (BETA): Finished drafting functionality.
- Version 1.00 (BETA): Release.
 

Gwypaas

hook DoNothing MakeGUIUsersCrash
Reaction score
50
If this faster than struct stack loop that might be right but how much load does this add? To me it seems like this somehow processes more code than a struct stack but the call goes of earlier? So, this is "faster" but takes more load on the CPU because of other shit being done after the call was done.
 

Viikuna

No Marlo no game.
Reaction score
265
Pretty cool. If this is faster than struct stack, ill probably use it, since I only need .025 period anyways.

( + TimerStack or TimerUtils. Ive learned to like one timer per instance style after I studied these slow time spell thingies. )

Remember to post some benchmarking when you finish the code.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>What actually makes this different from TimedLoop?
I don't know what that is.

>this is "faster" but takes more load on the CPU because of other shit being done after the call was done.
Err. Faster basically means less load on the CPU. You lost me.

>How can we be sure about this?
Well, at it's core, this is a fully optimized struct/stack loop, but instead of running on a timer per instance, each loop is a condition on a core trigger. So it blends KT2 and the original struct/stack loop principle to make things that bit faster (which is useful if you have a lot of 1 instance things).

>similar working style with KT2...
Yepp.

>Remember to post some benchmarking when you finish the code.
Yeah, I intend to get around to that.
 

Jesus4Lyf

Good Idea™
Reaction score
397
>This one still uses 1 timer + trigger.
In total. Not per struct like whatever else.

>Faster or slower than KT2 ?
Faster. Faster than fast. Faster than everything. If anything is faster than this, this is pointless.

The point of this was basically to produce something that can't be beaten in speed. So things that use this will be written on the lightest timer system possible. :thup:

Edit: Version 1.01 (BETA) released. This actually should be finished, I think. But I haven't tested it enough to put my word to it being stable. But by all means test it, and bug report to your heart's content should anything arise. I'll thank you for it. :p
 

Jesus4Lyf

Good Idea™
Reaction score
397
Edit:
Some benchmarks:
KT2 and Timed Loop: 36,700 execs/sec (same speed, so there's no point using timed loop as it doesn't allow period).
Timer32: 48,000 execs/sec (about 31% faster).

So that's where we're headed. I'm not really happy until I see about 52k execs/sec though. Even if that is a fairly small gain.

TU Red, by the way, would still be around 31,000 execs/sec, but I didn't remeasure it for this test. Just stating for reference.

All tests were run using 10 instances of each of 10 different pieces of code running on one period (obviously one period, lol).

Edit: The thing that's annoying me is that PeriodicModule is still faster (50,000 execs/sec). Perhaps this idea is flawed. :(

Edit: Nup. Tested PM ported over to this idea, at best it gains 1% speed!... Meh, perfect is perfect. I'm going to have to reinvestigate this, I think. Oh, and I'm sure that speed gain is much higher for 1 instance or something, too. :)

Edit: Release 1.02 (no longer BETA). So the code got simpler. MEH!
Don't worry, I'll make it complicated again some time probably. This is probably 50,600 execs/sec, making it faster than fast. Problem is, the "timer" for each struct is always running and doesn't get paused. Not that it matters much, but I hope to fix this some time. 38% faster than KT2 and that fail TimedLoop thing. 1% faster than Periodic Module (joy). Probably grows a lot more efficient in comparison when you have less instances of a struct time firing periodically (the reverse is not true).
 

Kenny

Back for now.
Reaction score
202
KT2 and Timed Loop: 36,700 execs/sec (same speed, so there's no point using timed loop as it doesn't allow period).

LOL! Massive fail by Timed Loop. A system with that many drawbacks is still only equal in speed to KT2.

Anywho, this is looking mad. Hope you get that extra optimisation you wanted. But right now it seems lightning fast. No competiton.

EDIT:

Hahaha, I just saw the 1.02 release, back to VERY simple methods.

Oh and why Timer32? Does it have anything to do with 0.03125 as the period? :p
 

Jesus4Lyf

Good Idea™
Reaction score
397
>LOL! Massive fail by Timed Loop. A system with that many drawbacks is still only equal in speed to KT2.
Actually, it's worse than that... For spells (which is what it is written for) KT2 will actually be faster for low numbers of instances. So that thing is TOTALLY useless. T32 doesn't suffer the same symptoms, because it uses a single trigger. No timers at all, right now! :D

>No competiton.
Yeah. The idea of this is to finally create something final in terms of speed, really.

>Hahaha, I just saw the 1.02 release, back to VERY simple methods.
Cute, isn't it?

>Oh and why Timer32? Does it have anything to do with 0.03125 as the period?
Yeah. 32 times a second. It also has a sweet acronym: T32. I believe that 0.03125 is the optimum period. It's smooth and doesn't fire too much. :thup:

I mean, it's obvious that this whole speed thing is a bit of a joke, right? Like the percentage gain evaluates to a very, very small amount of processing time (like half a native or something). But I wanted to, as well as KT2, create a perfectly fast single-period system.

Actually, the reality is that T32 is just a single case of KT2 using modules to hard code a little more than before. It just had to be done.
 

Renendaru

(Evol)ution is nothing without love.
Reaction score
309
Pretty minimal, but you didn't update your changelog.
 

chobibo

Level 1 Crypt Lord
Reaction score
48
from this system
JASS:
call TriggerRegisterTimerEvent(Trig,PERIOD,true)


from common.j
JASS:
// Creates it's own timer and triggers when it expires
native TriggerRegisterTimerEvent takes trigger whichTrigger, real timeout, boolean periodic returns event


from creator of the system
T32 doesn't suffer the same symptoms, because it uses a single trigger. No timers at all, right now!

No timers? A trigger that creates it's own (internal) timer has no timers?

A timer uses 1 handle, A trigger with a timer event uses 3; a timer, a trigger and an event. Now if used with code funcs you add 1 more to a timer, on your case, it uses a condition func, it uses 2 handles, a boolexpr and a triggercondition.
 

kingkingyyk3

Visitor (Welcome to the Jungle, Baby!)
Reaction score
216
A timer uses 1 handle, A trigger with a timer event uses 3; a timer, a trigger and an event.
Does it matter? It won't effect much unless you use H2I-0x10000 in your map..
 

Jesus4Lyf

Good Idea™
Reaction score
397
Lol, it's completely irrelevant, he's just saying "haha you're wrong" :D. I'll fix that.
It sounded novel at the time.

Anyway, success.

After a fresh reboot, the 1.02 algorithm gave me 51,800 execs/sec.
Here it is:
JASS:
        private static thistype array Data
        private static integer        DataMax=0
        
        private static method PeriodicLoop takes nothing returns boolean
            local integer i=.DataMax
            loop
                exitwhen i==0
                if .Data<i>.periodic() then
                    set .Data<i>=.Data[.DataMax]
                    set .DataMax=.DataMax-1
                endif
                set i=i-1
            endloop
            return false
        endmethod

        method startPeriodic takes nothing returns nothing
            set .DataMax=.DataMax+1
            set .Data[.DataMax]=this
        endmethod</i></i>

And the 1.03 algorithm... (which I'm very proud of, actually managing to optimise the above.)
JASS:
        private thistype next
        private thistype prev
        
        private static method PeriodicLoop takes nothing returns boolean
            local thistype this=thistype(0).next
            loop
                exitwhen this==0
                if this.periodic() then
                    // This is some real magic.
                    set this.prev.next=this.next
                    set this.next.prev=this.prev
                    // This will even work for the starting element.
                    call DisableTrigger(Trig) // For benching.
                endif
                set this=this.next
            endloop
            return false
        endmethod

        method startPeriodic takes nothing returns nothing
            set thistype(0).next.prev=this
            set this.next=thistype(0).next
            set thistype(0).next=this
            set this.prev=thistype(0)
        endmethod

Yep. 55,000 execs/sec. I removed a single subtraction. (That's how much of a joke the speed thing is at this point. That gave a 6% gain. In other words, this code is one of the most lightweight JASS things you'll ever see.)

I'll bring out 1.03 in a sec, probably. :)

Edit: Released 1.03 after testing.

>Huh? Much like TT now...
TT used the algorithm developed in KT1, basically. Cohadar helped me optimise it a lot when KT1 first came out. But in version 1.03 of T32 I've changed my implementation significantly (different underlying data structure), and I'm nearly convinced that it can no longer be made faster. :)
And speedwise this is something like twice as fast as TT.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top