Periodic Timer Modules

emjlr3

Change can be a good thing
Reaction score
395
What ever happened to this as a viable option?

as compared to T32, which has, per timer expiration, a (trigger evaluate)+(function call/instance). In the periodic method, it loops through current instances, and swaps in place the next instance if the current instance is over.

in the aforementioned TimedLoop module, for each timer expiration, you have (function call/instance). It loops through each instance periodically and swaps the last in for the current when that instance is over.

the only thing I can see as being an improvement is the fact that T32 has slightly less overhead in the loop function (the heaviest of which being simple arithmetic), since it uses a function call to remove instances, rather than a boolean return from that instances function call, but then again there is also the added triggerevaluate.

anywho - does this make that large a difference in performance - or is there something else I am totally missing?
 

Solmyr

Ultra Cool Member
Reaction score
30
Timer32 runs all instances on one timer and one trigger (the trigger is, as you said, evaluated each time the timer expires), while TimedLoop uses one timer per struct that implements its module.

TimedLoop may actually be faster if we're talking about a very small amount of struct implementing its module (which is, well, quite unlikely), but in all other cases, Timer32 should win by a small, yet important margin.
 

emjlr3

Change can be a good thing
Reaction score
395
ah ok that makes sense then - I didn't catch that at first

I guess, for some reason I don't quite understand, that 1 global timer wouldn't work, with an integer array rather than a struct array
 

emjlr3

Change can be a good thing
Reaction score
395
would not something like this work???

JASS:
globals
	private constant real TIMEOUT=.03125

	private timer T=CreateTimer()
	private integer N=0
	private integer array DATA
endglobals
private interface face
	method onPeriodicLoop takes nothing returns boolean defaults true
endinterface
struct PeriodicLoop extends face	
	private static method onExpire takes nothing returns nothing
		local integer i=1
		local thistype this
		
		loop
			exitwhen i>N
			
			set this=DATA<i>
			if .onPeriodicLoop() then
				set i=i+1
			else			
                                call .destroy()
				set DATA<i>=DATA[N]
				set N=N-1
			endif
		endloop
		if N==0 then
			call PauseTimer(T)
		endif
	endmethod
	method startPeriodicLoop takes thistype this returns nothing
		set N=N+1
		set DATA[N]=this
		if N==1 then
			call TimerStart(T,TIMEOUT,true, function PeriodicLoop.onExpire)
		endif
	endmethod
endstruct
</i></i>


where you would call
JASS:
.startPeriodicLoop(myStruct)
to add your struct to the loop stack. Obviously your struct would have to extend
JASS:
PeriodicLoop


Your struct would have to contain
JASS:
method onPeriodicLoop takes nothing returns boolean
where true=continue and false=release

for example
JASS:
struct myStruct extends PeriodicLoop
	private integer i=0
	
	method onPeriodicLoop takes nothing returns boolean	
		if .i==10 then
			return false // destroy instance
		else
			set .i=.i+1
		endif
		return true // continue with instance
	endmethod
	private static method onInit takes nothing returns nothing
		local myStruct this=myStruct.create()
		
		call .startPeriodicLoop(m) // add struct to loop stack
	endmethod
endstruct


no idea if this would even compile, but it seems viable to me....
 

Darthfett

Aerospace/Cybersecurity Software Engineer
Reaction score
615
no idea if this would even compile, but it seems viable to me....

I think it would definitely compile, but I think your onExpire method would just be calling the PeriodicLoop's onPeriodicLoop method. Either way, you don't really want to force someone to extend your struct just for timer use.
 

emjlr3

Change can be a good thing
Reaction score
395
I think you are wrong :)

I am going to try this tonight!

What is so wrong with a struct requiring an extension? can a struct extend multiple structs/interfaces?
 

Sevion

The DIY Ninja
Reaction score
413
AFAIK, a single struct can only extend a single struct/interface.

Delegates could be used in stead of that.
 

Nestharus

o-o
Reaction score
84
Since nobody has brought this up (was hoping that somebody would).

You are comparing x trigger evaluations + 2x function calls to x function calls. Now, what I'm starting to think would be a good approach to timers would be trigger conditions added to a trigger to avoid op limit (so that each instance can have max op limit). However, this would only be necessary for some types of functions (like functions that have large operation counts or timers that have large numbers of instances). In this case, it would be a triggercondition + 2x function calls (one function call to increment instance counter and call the other function with the instance and the other function being the actual function). A set of trigger conditions do run faster than a set of trigger evaluations (proven in Event at THW).
 

tooltiperror

Super Moderator
Reaction score
231
I thought a cool way to make a timer system would be to attach boolexprs to a trigger and evaluating it at a constant period, so like a baby between KT2 and T32.
 

emjlr3

Change can be a good thing
Reaction score
395
FYI this approach works

this is the code I have atm. I had to change the argument name in the startPeriodicLoop method. For some reason it would not compile as "this".

JASS:
library PeriodicLoop

globals
    private constant real TIMEOUT=.03125

    private timer T=CreateTimer()
    private integer N=0
    private integer array DATA
endglobals

private interface face
    method onPeriodicLoop takes nothing returns boolean defaults true
endinterface
struct PeriodicLoop extends face
	
    private static method onExpire takes nothing returns nothing
        local integer i=1
        local thistype this
            
        loop
            exitwhen i&gt;N
                        
            set this=DATA<i>
            if .onPeriodicLoop() then
                set i=i+1
            else			
                call .destroy()
                set DATA<i>=DATA[N]
                set N=N-1
            endif
        endloop
        if N==0 then
            call PauseTimer(T)
        endif
    endmethod
    method startPeriodicLoop takes thistype t returns nothing
        set N=N+1
        set DATA[N]=t
        if N==1 then
            call TimerStart(T,TIMEOUT,true,function PeriodicLoop.onExpire)
        endif
    endmethod
endstruct

endlibrary</i></i>


I don't understand what you are saying Neth. Which was this?

I can foresee this being fast...

to test:

JASS:
struct myStruct extends PeriodicLoop
    private integer i=0
        
    method onPeriodicLoop takes nothing returns boolean	
        if .i&gt;10 then
            call BJDebugMsg(&quot;Over&quot;)
            return false // destroy instance
        else
            call BJDebugMsg(I2S(.i))
            set .i=.i+1
        endif
        return true // continue with instance
    endmethod
    private static method onInit takes nothing returns nothing
        local myStruct this=myStruct.create()                
        call .startPeriodicLoop(this) // add struct to loop stack
    endmethod
endstruct

// Displays 1,2,3,4,5,6,7,8,9,10,Over


works with multiple instances of various structs simultaneously...

this is exactly a struct stack loop, with every single instance on one lonely timer. could it be near as fast as T32?

**UPDATE**

versus T32, with the following code:

JASS:
globals
    constant integer COUNT=2300
endglobals

struct myStruct extends PeriodicLoop
    private integer i=0
    
    private method periodic takes nothing returns nothing
        if .i&gt;1000 then
            call BJDebugMsg(&quot;Over&quot;)
            call this.stopPeriodic()
            call this.destroy()
        else            
            set .i=.i+1
        endif
    endmethod
    implement T32x
        
    method onPeriodicLoop takes nothing returns boolean	
        if .i&gt;1000 then
            call BJDebugMsg(&quot;Over&quot;)
            return false // destroy instance
        else            
            set .i=.i+1
        endif
        return true // continue with instance
    endmethod
    private static method onInit takes nothing returns nothing
        local integer i=0
        local myStruct this
        
        loop
            exitwhen i&gt;COUNT
            
            // T32
            call myStruct.create().startPeriodic()
            
            // PeriodicLoop
            set this=myStruct.create()                
            call .startPeriodicLoop(this) // add struct to loop stack
            
            set i=i+1
        endloop
        call BJDebugMsg(&quot;Start&quot;)
    endmethod
endstruct


T32 keeps chugging away at op-limit # of instances (max fps)

@1000 instances, PeriodicLoop is fine, it starts to drop fps @ 1500 instances, and by 2300 (T32 tested @ above), the fps are down around 8.

For some reason the fps slowly drops over the course of the trial. Even at 2300 it starts at max, and slowly drops to 8 by about .i=600 or so.

Word! :( very disappointing.
 

Nestharus

o-o
Reaction score
84
Bet you this'll work better for you. I use trigger actions here so that timer destruction is easy. This also avoids op limit. I think that T32 should be this type of style (avoiding op limit) and T32x should be how it is right now (not avoid op limit). T32x would be good for things with little instances or small methods whereas T32 would be good for larger thingies ;D.
JASS:

library T32
    globals
        private timer t = CreateTimer()
        private trigger tr = CreateTrigger()
    endglobals

    private module Init
        private static method onInit takes nothing returns nothing
            call TriggerRegisterTimerExpireEvent(tr, t)
            call TimerStart(t, .031250000, true, null)
        endmethod
    endmodule

    module T32
        private static triggeraction array a    //periodic code
        private static thistype array n         //next
        private static thistype array p         //previous
        private static thistype r = 0           //recycler
        private static integer c = 0            //instance count
        private static thistype e = 0           //current expired
        
        private static method run takes nothing returns nothing
            call e.peridoic()
            set e = n[e]
        endmethod

        static method allocate takes nothing returns thistype
            local thistype this

            //allocate
            if (r == 0) then
                set this = c + 1
                set c = this
            else
                set this = r
                set r = n[this]
            endif
            
            //add to periodic list
            if (e == 0) then
                set n[this] = this
                set p[this] = this
                set e = this
            else
                set n[this] = n[e]
                set p[this] = e
                set n[e] = this
                set p[n[this]] = this
            endif
            
            set a[this] = TriggerAddAction(t, function thistype.run)

            return this
        endmethod

        method deallocate takes nothing returns nothing
            set n[p[this]] = n[this]
            set p[n[this]] = p[this]
            set n[this] = r
            set r = this
            
            if (e == this) then
                set e = n[this]
            endif
            
            call TriggerRemoveAction(t, a[this])
            set a[this] = null
        endmethod
    endmodule
endlibrary



I've been meaning to do this to Tq (timer queue) as well (an op limit safe version and a super speed version). I've also been meaning to make it so that each module gets its own instance stuffs ^)^.
 

Bribe

vJass errors are legion
Reaction score
67
Nestharus, KeyTimers2 will also reset the op limit for each instance. Don't see why you're trying to reinvent the wheel on that one.
 

Nestharus

o-o
Reaction score
84
Nestharus, KeyTimers2 will also reset the op limit for each instance. Don't see why you're trying to reinvent the wheel on that one.

T32 and KT2 do totally different things, and the version I wrote is a safe T32 : ).
 

emjlr3

Change can be a good thing
Reaction score
395
I like my little code :X

I want to test some more to see why its so damned slow - and yes its not op-safe

I can see it hitting the op limit at some point

assuming 20 spells at a time max, each with 150 lines of code executing, plus the system overhead, and you are probably getting close (can't remember the magic number).

Could .execute the currently boolean function, but then I can't get its return. I will play around a bit.
 

Nestharus

o-o
Reaction score
84
I think that magic number was 300000 (was mentioned on some thread). However, do you see why mine will end up running faster than yours? You could make mine run even faster by making it a trigger condition (although the speed increase would be tiny and destruction would have to run on timers + a stack to avoid removing conditions while the trigger was running). I think the thing I put up is the best implementation possible : \. Run that on your computer and see how long it takes before it starts to lag.

And here is it w/o typos and wrong var names ^_^
JASS:

library T32
    globals
        private timer t = CreateTimer()
        private trigger tr = CreateTrigger()
    endglobals

    private module Init
        private static method onInit takes nothing returns nothing
            call TriggerRegisterTimerExpireEvent(tr, t)
            call TimerStart(t, .031250000, true, null)
        endmethod
    endmodule

    private struct Inits extends array
        implement Init
    endstruct

    module T32
        private static triggeraction array a    //periodic code
        private static thistype array n         //next
        private static thistype array p         //previous
        private static thistype r = 0           //recycler
        private static integer c = 0            //instance count
        private static thistype e = 0           //current expired
        
        private static method run takes nothing returns nothing
            call e.periodic()
            set e = n[e]
        endmethod

        static method allocate takes nothing returns thistype
            local thistype this

            //allocate
            if (r == 0) then
                set this = c + 1
                set c = this
            else
                set this = r
                set r = n[this]
            endif
            
            //add to periodic list
            if (e == 0) then
                set n[this] = this
                set p[this] = this
                set e = this
            else
                set n[this] = n[e]
                set p[this] = e
                set n[e] = this
                set p[n[this]] = this
            endif
            
            set a[this] = TriggerAddAction(tr, function thistype.run)

            return this
        endmethod

        method deallocate takes nothing returns nothing
            set n[p[this]] = n[this]
            set p[n[this]] = p[this]
            set n[this] = r
            set r = this
            
            if (e == this) then
                set e = n[this]
            endif
            
            call TriggerRemoveAction(tr, a[this])
            set a[this] = null
        endmethod
    endmodule
endlibrary


edit
Mine (2300): 14
Yours (2000): 3

Your test code
JASS:

struct Tester extends PeriodicLoop
    private static constant integer COUNT = 2000
        
    private method onPeriodicLoop takes nothing returns boolean	
        return true // continue with instance
    endmethod
    private static method onInit takes nothing returns nothing
        local integer i = COUNT
        local thistype this
        
        loop
            exitwhen i == 0
            
            // T32
            set this = allocate()
            call startPeriodicLoop(this)
            
            set i = i - 1
        endloop
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, &quot;Done&quot;)
    endmethod
endstruct


My test code
JASS:

struct Tester extends array
    private static constant integer COUNT = 2300
    
    private method periodic takes nothing returns nothing
    endmethod
    
    implement T32
    
    private static method onInit takes nothing returns nothing
        local integer i = COUNT
        local thistype this
        
        loop
            exitwhen i == 0
            
            call allocate()
            
            set i = i - 1
        endloop
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, &quot;Done&quot;)
    endmethod
endstruct
 

emjlr3

Change can be a good thing
Reaction score
395
there is obviously a lot less overhead in the periodic function - which has been migrated over to the one-out allocate and deallocate functions.

that said, you never pause your trigger
 

emjlr3

Change can be a good thing
Reaction score
395
more testing, more confusion and dissapointment

JASS:
library PeriodicLoop

globals
    //==PeriodicLoop&#039;s Timer Interval==\\
    private constant real TIMEOUT=.03125

    //==REQUIRED GLOBALS==\\
    private timer TIMER=CreateTimer()
    private trigger TRIG=CreateTrigger()
    
    private integer N=0 // Instance counter
endglobals

private interface face
    method onPeriodicLoop takes nothing returns nothing defaults nothing
endinterface
struct PeriodicLoop extends face   
    private thistype next
    private thistype prev

    private static method onExpire takes nothing returns boolean
        local thistype this=thistype(0).next
        
        loop
            exitwhen this==0
            call this.onPeriodicLoop()
            set this=this.next
        endloop
        
        return false
    endmethod
    
    public static method stopPeriodicLoop takes thistype t returns nothing
        set t.prev.next=t.next
        set t.next.prev=t.prev
        call t.destroy()
        
        set N=N-1
        if N==0 then
            // No more instances
            call PauseTimer(TIMER)
        endif
    endmethod
    public static method startPeriodicLoop takes thistype t returns nothing
        set thistype(0).next.prev=t
        set t.next=thistype(0).next
        set thistype(0).next=t
        set t.prev=thistype(0)
    
        set N=N+1
        if N==1 then
            // First instance established
            call TimerStart(TIMER,TIMEOUT,true,function PeriodicLoop.tick)
        endif
    endmethod
    
    // Inexplicably, evaluating a trigger periodically following a timer expiration
    // is faster than trigger actions/conditions following a trigger timer event.
    // And even more so then basic timer expiration handlerFunc execution.  Go figure.
    private static method tick takes nothing returns nothing
        call TriggerEvaluate(TRIG)
    endmethod
    private static method onInit takes nothing returns nothing
        call TriggerAddCondition(TRIG,Condition(function PeriodicLoop.onExpire))
    endmethod
endstruct

endlibrary


seems to be about as optimized as I can get it, barring implementing Neths triggeraction removal process. But then that would just be copying.

As far as testing is concerned, @ 2300 instances:

  1. T32x=63fps
  2. T32n=57.5fps
  3. PeriodicLoop=47.5fps

Some general obsverations:

  • Switching from a struct array to linked lists added ~4fps to PeriodicLoop.
  • TriggerEvalute() following a Timer Expiration>TriggerActions>TriggerConditions~Timer Expiration
  • T32>T32Neth
  • Increasing the Timeout to .04 (from .03125) increases PeriodicLoop's fps to ~57
  • T32x may be just about as fast as can be

Interfaces are the only logical reasoning behind what is slowing down PeriodicLoop. I figured vJASS implemented extensions similar to how it dealt with modules. With modules extra code is stuck in where they are implemented, with interfaces, I had assumed it compiled the structs to contain the additional members/methods required to function. But I'm not so sure anymore.

Switching over to a module would make this exactly T32 :X.
 

Nestharus

o-o
Reaction score
84
your new version isn't op limit safe : (.


Also, you saying that timer expiring evaluating a trigger is faster than a trigger registered to that timer with conditions?
 

emjlr3

Change can be a good thing
Reaction score
395
yea its not - but I wasn't worried about that to begin with

I think so. - all my tests point to yes - doesn't make any sense to me, but then a lot of things don't. to reiterate, based off my tests

fastest to slowest

TriggerEvalute() following a Timer Expiration>TriggerActions from TimerEvent>TriggerConditions from TimerEvent~Timer Expiration function

I did not try a TriggerRegisterTimerExpireEvent - but I can't see if being any faster than a TriggerRegisterTimerEvent - even so, it seemed like triggeractions were faster then the triggerconditions. But then again who knows.

Its the method T32 uses, so I guess Jesus did so for a reason, because any of the above would have worked just the same.

just to note - my sample collection isn't exactly fool proof - I basically took a random sampling of fps readings over about a 1 minute time frame and averaged them out. probably 20-25 data points. the differences were minor, a few fps at most, which theoretically could have simply resulted from random variances in that sample batch. however, I propose otherwise.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top