• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Snippet] TimedStack

JASS:
// --------------------------------------------------------------------------------------------------------------------
//  
//  TimedStack
//  =====
// 
// 	Version:	1.3.0
// 	Author:		Anachron
// 
// 	Requirements:
// 		(New) Table [by Bribe] (v. 3.1) [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
//      Stack [by Anachron] (v. 1.5)
//      IndexStack [by Anachron] (v. 1.3)
//  Optional:
//      TimerUtils [by Vexorian] (v. 2.0) [url]http://www.wc3c.net/showthread.php?t=101322[/url]
// 
// 	Description:
// 		TimedStack is a library to destroy redundant code based on timers.
//      Any code can be executed either once or periodic for every instance.
//      You can choose between a global timer or a local timer (so you can have different periods too).
//      This is all bound to a textmacro so you can put them into modules and structs as you wish.
//      Timer handling / recycling is all done by the system.
//  
// 	History:
// 		1.0.0: Initial Release
//      1.1.0: Fixed problem in onPeriodic method and renamed it, changed pause to stop, TimerUtils is optional now
//      1.2.0: Added checks in start + stop to skip when called twice, added clear, changed active to be method operator,
//             replaced finished with iterate, added a lot of documentation/comments, added TimerUtils in optional
//      1.3.0: Periodic and delay are now constant in global timers, used active instead of checking table directly (still inline),
//             Added limited intervals, added API for TextMacro, removed duplicated if
//
//  API:
//      ------------------------------------------------------------------------------------------------
//          Basics
//      ------------------------------------------------------------------------------------------------
//      .active                 --> Boolean whether the instance has active timer or not (only readable)
//      .iterate                --> Boolean whether the instance should be removed from iteration or not
//      .intervals              --> Integer amount of times the instance should be iterated (-1 = endless)
//      .start()                --> Add the current instance to running stack
//      .stop()                 --> Remove the current instance from running stack
//      thistype.clear()        --> Remove all instances from running stack
//
//      ------------------------------------------------------------------------------------------------
//          Advanced
//      ------------------------------------------------------------------------------------------------
//      [thistype].clock        --> Timer instance for running
//      [thistype].periodic     --> Boolean whether timers are periodic or once
//      [thistype].delay        --> Delay / Period of timer
//      thistype.myself         --> Current iterated instance
// 
//      ------------------------------------------------------------------------------------------------
//          Instance Getting
//      ------------------------------------------------------------------------------------------------
//      call .onPeriod()
//          Method:     (public/private) method onPeriod takes nothing returns nothing
//          This way you already have the local instance in the method (this)
//      call thistype.onPeriod()
//          Method:     (public/private) static method onPeriod takes nothing returns nothing
//          If you do it like this, use thistype.myself
//      call thistype.onPeriod(thistype.myself or this)
//          Method:     (public/private) static method onPeriod takes thistype this returns nothing
//          Just like the first example, you already have this in the method
//
//      ------------------------------------------------------------------------------------------------
//          Textmacro API
//      ------------------------------------------------------------------------------------------------
//      1.  GLOBAL              --> Boolean whether timer should be used globally for all instances or not
//      2.  PERIODIC            --> Boolean whether to run the timer in intervals or not
//      3.  DELAY               --> Real amount of time between each iteration
//      4.  TIMES               --> Integer how many iterations one instance can last (-1 for endless)
//      5.  ACTION              --> Some code to do, use a string to pass code ("call .onPeriod()")
//
//      ------------------------------------------------------------------------------------------------
//          Examples
//      ------------------------------------------------------------------------------------------------
//      Global timer, periodic, 0.03175, good for missile engines or smooth fading/effects
//          //! runtextmacro TimedStack("true", "true", "0.03175", "-1", "call .onPeriod")
//      Global timer, 1., once, good for non critical actions, just like repick
//          //! runtextmacro TimedStack("true", "false", "1.", "-1", "call .onActivate")
//      Local timer, once, good for delayed stuff just like effects
//          //! runtextmacro TimedStack("false", "false", "1.", "-1", "call .cleanSpell")
//      Local timer, periodic, custom time, good for anything that is spell level related
//          //! runtextmacro TimedStack("false", "true", "1.0", "-1", "call .onFinish")
// --------------------------------------------------------------------------------------------------------------------------
library TimedStack requires Stack, IndexStack, optional TimerUtils 

    //! textmacro TimedStack takes GLOBAL, PERIODIC, DELAY, TIMES, ACTION
    private static Stack        running = 0
    public  static thistype     myself  = 0
    
    public  boolean     iterate     = true
    public  integer     intervals   = $TIMES$ 
    
    static if $GLOBAL$ then
        private static  constant    real        delay       = $DELAY$
        private static  constant    boolean     periodic    = $PERIODIC$
        private static  timer       clock       = null
    else
        private static  IndexStack  timers      = 0
    
        private timer               clock       = null
        private real                delay       = $DELAY$
        private boolean             periodic    = $PERIODIC$
    endif
    
    public method operator active takes nothing returns boolean
        return thistype.running.has(this)
    endmethod
    
    private static method TS_PERIODIC takes nothing returns nothing
        local timer expired = GetExpiredTimer()
        local thistype this = 0
        local boolean once = false
        
        static if $GLOBAL$ then
            set once = not thistype.periodic
            call thistype.running.reset()
            loop
            exitwhen not thistype.running.hasNext()
            set this = thistype.running.getNext()
        else
            set once = not .periodic
            set this = thistype.timers[GetHandleId(expired)]
        endif
        if .active then
            set thistype.myself = this
            if .intervals != 0 then
                $ACTION$
            endif
            if .intervals > 0 then
                set .intervals = .intervals -1
            endif
            if .intervals == 0 then
                set .iterate = false
            endif
        endif
        if ((not .iterate or once) and active) or this == 0 then
            call .stop()
            static if not $GLOBAL$ then
                call thistype.timers.delete(GetHandleId(expired))
                call PauseTimer(expired)
                static if LIBRARY_TimerUtils then
                    call ReleaseTimer(.clock)
                else
                    call DestroyTimer(.clock)
                endif
            endif
        endif
        static if $GLOBAL$ then
            endloop
            if thistype.running.count == 0 then
                call PauseTimer(thistype.clock)
                static if LIBRARY_TimerUtils then
                    call ReleaseTimer(thistype.clock)
                else
                    call DestroyTimer(thistype.clock)
                endif
            endif
        endif
        
        set expired = null
    endmethod
    
    public method start takes nothing returns nothing
        if .active then
            return
        endif
        set .iterate = true
        static if $GLOBAL$ then
            if thistype.clock == null then
                static if LIBRARY_TimerUtils then
                    set thistype.clock = NewTimer()
                else
                    set thistype.clock = CreateTimer()
                endif
            endif
            if thistype.running.count == 0 then
                call TimerStart(thistype.clock, thistype.delay, thistype.periodic, function thistype.TS_PERIODIC)
            endif
        else
            if .clock == null then
                static if LIBRARY_TimerUtils then
                    set .clock = NewTimer()
                else
                    set .clock = CreateTimer()
                endif
            endif
            call TimerStart(.clock, .delay, .periodic, function thistype.TS_PERIODIC)
            if thistype.timers == 0 then
                set thistype.timers = IndexStack.create()
            endif
            set thistype.timers[GetHandleId(.clock)] = this
        endif
        if thistype.running == 0 then
            set thistype.running = Stack.create()
        endif
        call thistype.running.add(this)
    endmethod
    
    public method stop takes nothing returns nothing
        if not .active then
            return
        endif
        call thistype.running.delete(this)
        static if $GLOBAL$ then
            if thistype.running.count == 0 then
                call PauseTimer(thistype.clock)
            endif
        else
            call PauseTimer(.clock)
        endif
    endmethod
    
    public static method clear takes nothing returns nothing
        local thistype this = 0
        
        call thistype.running.reset()
        loop
            exitwhen not thistype.running.hasNext()
            set this = thistype.running.getNext()
            call .stop()
        endloop
    endmethod
    //! endtextmacro

endlibrary
 
Last edited:
1.0.0: Initial Release
1.1.0: Fixed problem in onPeriodic method and renamed it, changed pause to stop, TimerUtils is optional now
1.2.0: Added checks in start + stop to skip when called twice, added clear, changed active to be method operator, replaced finished with iterate, added a lot of documentation/comments, added TimerUtils in optional
1.3.0: Periodic and delay are now constant in global timers, used active instead of checking table directly (still inline), added limited intervals, added API for TextMacro, removed duplicated if
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
I don't see so much benefits on a first view. At least not regarding performance or the amount of generated code.
I think there are only 3 types of useful timer system philosophies.

1. Merging timers on a short timer interval, for instance every 0.03125 seconds
2. Attaching data on single timers
3. Merging any timer interval to one or few timers ( theoretically good, in pratise not really required )

For 1 and 2 well known snippets already exists.
 
So it's another timer system? I thought this would be more similar to destroying handles after X amount of time (which we have systems for as well). As BPower has stated, TimerUtils and CTL/Timer32 already exist. The third thing he mentioned, Nestharus and I each coded the fastest way to do that based on our own philosophies - and regular timers are faster.

Graveyarding.
 
Top