TimerUtilsEx - A reinvention

Status
Not open for further replies.
Hello everyone.

I recently thought of replicating the library [vJASS] - [Snippet] MasterTimer, along with keeping the precision of timers to a tee. The result isn't much, but I hope it can prove to be useful to some.

For some reason, I decided to merge the one-timer concept in MasterTimer with data attachment capabilities in TimerUtils, and the idea of queued timer mechanisms, and thus, the reinvention was born.

As for the requirements, they are found within the map. I apologize for the inconvenience with downloading it just for extraction (there are extra libraries).

Preview: (Don't copy it raw, some requirements are found only in-map:
JASS:
library TimerUtilsEx /*

    */ requires /*
  
        --------------
        */ Table    /*
        --------------
            -   Bribe
          
            link: https://www.hiveworkshop.com/threads/snippet-new-table.188084/#post-1835068
          
        --------------
        */ ListT    /*
        --------------
            #? Alloc
            #? Table
          
            -   Bannar
          
            link: https://www.hiveworkshop.com/threads/containers-list-t.249011/page-2#post-3271599
      
        --------------
        */ StdInit  /*
        --------------
            #? Init
          
            -   MyPad
            -   Found within attached map
      
        --------------
        */ Eval     /*
        --------------
            #   Table
            #   StdInit
            #   StdAlloc
            #   StdList
          
            -   MyPad
            -   Found within attached map
          
         -------------------------------------
        |
        |   TimerUtilsEx
        |       - MyPad's version
        |
        |   v.1.1.00
        |
         -------------------------------------
        
         -------------------------------------
        |
        |   TimerUtilsEx
        |       - A timer utility library
        |       that allows data attaching
        |       and such.
        |
         -------------------------------------
        
         -------------------------------------
        |
        |   How this works:
        |       - All timeouts are handled
        |       such that they are just
        |       as precise as regular
        |       timers.
        |
        |       - While longer intervals are
        |       treated based only on the
        |       Timer that's first to expire,
        |       shorter intervals get their
        |       own timer workspace.
        |      
        |       - The main timer handles all
        |       of the longer intervals,
        |       while ones that are about to
        |       expire within a time period
        |       shorter than the interval
        |       will be handled by individual
        |       timers. They will be generated
        |       only as necessary.
        |
        |       - In addition, once there
        |       are no more timer instances
        |       associated with a certain
        |       time stamp (must be lower than
        |       Timer.INTERVAL), the timer
        |       object associated with it
        |       is destroyed, as well as the
        |       list which it holds.
        |
        |       - All instances are processed
        |       in queues for very short intervals
        |       and in sorted lists for longer
        |       intervals.
        |
        |       - Time stamps are handled via
        |       integer ticks and an epsilon
        |       value, which represents the
        |       remainder value of the stamp
        |       specified.
        |
         ------------------------------------
        
         ------------------------------------------------------
        |
        |   Required functions:
        |
        |       In your ListT library, please include the
        |       following methods and members within the
        |       textmacro DEFINE_LIST:
        |
        |       Members:
        |           readonly $NAME$Item lastAdded
        |
        |       Methods:
        */
        //! novjass
        method append takes $TYPE$ value, $NAME$Item node returns thistype
            local $NAME$Item entry = 0
          
            if getNodeOwner(node) == this then
                set entry = createNode(value)
                set count = count + 1
                if .first == node then
                    set entry.prev = node.prev
                    set entry.next = node
                    set node.prev  = entry
                    set .first     = entry
                else
                    set entry.prev = node.prev
                    set entry.next = node
                    set node.prev.next = entry
                    set node.prev      = entry
                endif
            endif
            return this
        endmethod
        //! endnovjass
        /*
        |
         ------------------------------------------------------
      
         ------------------------------------------------------
        |
        |   API:
        |
        |       struct Timer extends array
        |           static method create() -> Timer
        |               - Returns a new Timer object.
        |
        |           static method getExpired() -> Timer
        |               - Returns the currently-expired Timer
        |               object. Only works within
        |               handler functions.
        |
        |           method destroy()
        |               - Destroys a timer object.
        |               - Double-destroy safety. (Protected
        |               from double-free errors as a result)
        |
        |           method start(real r, code f)
        |               - Starts a timer instance, invoking
        |               the callback after a specified amount
        |               r.
        |               - Will not accept destroyed timer
        |               instances.
        |
        |           method startEx(real r, integer i, code f)
        |               - Starts a timer instance, which will
        |               repeat i times.
        |
        |           method startPeriodic(real r, code f)
        |               - Starts a timer instance, repeating
        |               until paused or stopped.
        |
        |           method pause()
        |               - Pauses a timer instance, if running.
        |
        |           method resume()
        |               - Resumes a timer instance, if it
        |               was running and now is paused.
        |          
        |           method stop()
        |               - Stops a timer instance.
        |               Clears out the time stamp
        |               and the remaining time information.
        |
        |           method running() -> bool
        |               - Returns true for its' namesake.
        |               (E.g. if a Timer instance is running)
        |
        |           method setData(int i) -> this
        |               - A simple cascading version of
        |               set this.data = i, allowing for the
        |               execution of another line within
        |               the same line.
        |               - Ex:
        |                   this.setData(i).destroy()
        |
        |           method getIterationCount() -> int
        |               - Returns the number of times a
        |               timer has been executed.
        |               - Pseudo-periodic Timers
        |               via resume would always return 1
        |               within an expiration callback.
        |               - Periodic Timers will always
        |               tick up.
        |               - Timers with a set number of
        |               iterations will count up to
        |               the defined number.
        |
        |           method getRemaining() -> real
        |               - gets remaining time
        |
        |           method getTimeout() -> real
        |               - gets timeout
        |
        |           method getElapsed() -> real
        |               - gets elapsed time
        |
        |   function NewTimer() -> Timer
        |       - Timer.create()
        |
        |   function NewTimerEx(int i) -> Timer
        |       - Timer.create().data = i
        |
        |   function GetTimerData(Timer t) -> int
        |       - Returns t.data
        |
        |   function SetTimerData(Timer t, int i)
        |       - Sets t.data to specified value i
        |
        |   function ReleaseTimer(Timer t) -> int
        |       - Based on Magtheridon's TimerUtilsEx,
        |       release returns the last data value
        |       of the released timer.
        |
        |   function GetExpiredTimerEx() -> Timer
        |       - Returns the currently-expiring
        |       Timer instance.
        |
         ------------------------------------------------------
        
         ------------------------------------------------------
        |
        |   Credits:
        |
        |       - Bannar,       for ListT
        |       - Bribe,        for Table
        |       - Nestharus,    for Queued Timer Systems.
        |       -
        |
         ------------------------------------------------------
    */

//! runtextmacro DEFINE_LIST("", "RealList", "real")

static if DEBUG_MODE  then
private function Echo takes string s returns nothing
    call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "\n" + s + "\n")
endfunction
endif

//  Basically <condition>? <stmt1>:<stmt2>
private function RealTertiaryOp takes boolean flag, real r1, real r2 returns real
    if flag then
        return r1
    endif
    return r2
endfunction

//  Converts a certain real value to an index.
private function F2I takes real dur returns integer
    return R2I(RMaxBJ(dur, 0)*100000)
endfunction

//  Converts an index back to the real value.
private function I2F takes integer i returns real
    return I2R(i)/100000.
endfunction

struct Timer extends array
    implement Alloc
  
    //  You may choose any value for this.
  
    //  It is recommended to keep it low so as
    //  to not create too many timers that end
    //  up defeating the purpose of this system
    //  in the first place.
    private static constant real INTERVAL   = 0.03
  
    private static Timer interval           = 0
  
    private static code uniqueEx            = null
    private static code commonEx            = null
  
    private static real totalDur            = 0.
  
    readonly static Timer curTimer          = 0
  
    private static Table tickerMap          = 0
    private static Table listMap            = 0
    private static Table listTickMap        = 0
  
    private static RealList timeStamp
  
    private boolean hasTimerExpired
  
    private IntegerList     uniqueListPointer
    private IntegerListItem uListPointer
  
    private RealListItem    stampPointer
  
    private Event handlerEv

    private boolean paused  
    private boolean looped
    private boolean isOnCallback
    private boolean wasIterated
    private boolean toExpire
    private boolean exists
  
    private integer durCount
    private integer count
    private integer totalCount
  
    private real    epsilon
    private real    stamp
    private real    remaining
  
    integer data
  
    static method getExpired takes nothing returns Timer
        return Timer.curTimer
    endmethod

static if DEBUG_MODE then
    static method displayStamp takes integer whichList returns nothing
        local string s              = ""
        local integer i             = 1
        local RealListItem iter    
      
        if whichList == 0 then
            set iter = Timer.timeStamp.first
            set s    = "Time stamps: \n"
        endif
      
        loop
            exitwhen iter == 0
          
            if whichList == 0 then
                set s    = s + "Time stamp["+I2S(i)+"]: " + R2S(iter.data) + "\n"
            endif
          
            set iter = iter.next
            set i    = i + 1
        endloop
      
        if whichList == 0 or whichList == 1  then
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, s)
        endif
    endmethod
endif

    private method operator ticker takes nothing returns timer
        return Timer.tickerMap.timer[this]
    endmethod
  
    private method operator ticker= takes timer t returns nothing
        set Timer.tickerMap.timer[this] = t
    endmethod
  
    private method operator uniqueList takes nothing returns IntegerList
        return Timer.listTickMap[this]
    endmethod

    private method operator uniqueList= takes IntegerList val returns nothing
        set Timer.listTickMap[this] = val
    endmethod

    private method getRemainingEx takes nothing returns real
        local Timer genR        = Timer(Timer.listMap[this.uniqueListPointer])
        local real elapsed
        local real remaining    = 0.
      
        if genR == Timer.interval then
            set elapsed     = RealTertiaryOp(genR.hasTimerExpired, Timer.totalDur, Timer.totalDur + TimerGetElapsed(genR.ticker))
            set remaining   = this.stampPointer.data - elapsed
        elseif integer(genR) != 0 then
            set elapsed     = RealTertiaryOp(genR.hasTimerExpired, TimerGetTimeout(genR.ticker), TimerGetElapsed(genR.ticker))
            set remaining   = this.remaining - elapsed
        endif
      
        return remaining
    endmethod
  
    private method pauseEx takes nothing returns nothing
        local Timer that
        local real remaining
        local real total
      
        if (this.paused) or (this.uListPointer == 0) then
            debug if this.paused then
                debug call Echo("Timer("+I2S(this)+").pause: Cannot pause the Timer instance (PauseStateError)")
            debug else
                debug call Echo("Timer("+I2S(this)+").pause: Cannot pause the Timer instance (TimerStateError)")
            debug endif
            return
        endif
      
        set remaining = this.getRemainingEx()
        if this.stampPointer != 0 then
            if this.uListPointer.next == 0 then
                //  Last instance, do nothing
            else
                set that            = Timer(this.uListPointer.next)
                set that.epsilon    = that.epsilon + this.epsilon
                set that.durCount   = that.durCount + this.durCount + R2I(that.epsilon/Timer.INTERVAL)
                set that.epsilon    = ModuloReal(that.epsilon, Timer.INTERVAL)
            endif        
          
            set this.durCount   = 0
            set this.epsilon    = 0.
          
            call Timer.timeStamp.erase(this.stampPointer)
            set this.stampPointer = 0
        endif
      
        call this.uniqueListPointer.erase(this.uListPointer)
      
        set this.uListPointer       = 0
        set this.uniqueListPointer  = 0
      
        set this.remaining = remaining
        set this.paused    = true
    endmethod
  
    private method stopEx takes nothing returns nothing
        call this.pauseEx()
      
        set this.remaining  = 0.
        set this.stamp      = 0.
        set this.count      = 0
        set this.looped     = false
    endmethod
  
    private method destroyEx takes nothing returns nothing
        if this.isOnCallback then
            set this.toExpire = true
            return
          
        elseif not this.exists then
            debug call Echo("Timer("+I2S(this)+").destroy: Cannot destroy a null Timer object!")
            return
        endif
      
        call this.stopEx()
      
        call this.handlerEv.destroy()
      
        set this.data           = 0
        set this.handlerEv      = 0
        set this.exists         = false
        set this.wasIterated    = false
        set this.toExpire       = false
      
        call this.deallocate()
    endmethod
  
    private method pushStamp takes real r returns nothing
        local Timer genR    = Timer.interval
        local Timer that    = 0
      
        local RealListItem iter
      
        local integer array pos
      
        local real elapsed  = 0.
        local real lastR    = r
        local real comp
      
        if genR.uniqueList.empty() then
            set this.durCount   = R2I(r/Timer.INTERVAL)
            set this.epsilon    = ModuloReal(r, Timer.INTERVAL)
                      
            set this.uListPointer           = genR.uniqueList.push(this).last
            set this.uniqueListPointer      = genR.uniqueList
          
            set this.stampPointer   = Timer.timeStamp.push(r + Timer.totalDur).last
          
            //  If called while the flag for expiration is true...
            if genR.hasTimerExpired then
                set this.wasIterated = true
            else
                call TimerStart(genR.ticker, Timer.INTERVAL, false, Timer.commonEx)
            endif
            return          
        endif
              
        set elapsed = TimerGetElapsed(genR.ticker)
        set r       = RealTertiaryOp(genR.hasTimerExpired, r + Timer.totalDur, r + Timer.totalDur + elapsed)
        set lastR   = RealTertiaryOp(genR.hasTimerExpired, lastR, lastR + elapsed)

        if r < Timer.timeStamp.first.data then
            set comp                        = Timer.timeStamp.first.data
          
            set this.uListPointer           = genR.uniqueList.unshift(this).first
            set this.uniqueListPointer      = genR.uniqueList
          
            set this.stampPointer           = Timer.timeStamp.unshift(r).first
          
            set this.epsilon                = ModuloReal(lastR, Timer.INTERVAL)
            set this.durCount               = R2I(lastR/Timer.INTERVAL)
          
            set that                        = Timer(this.uListPointer.next.data)
            set that.epsilon                = ModuloReal(comp - r, Timer.INTERVAL)
          
            set that.durCount               = R2I((comp - r)/Timer.INTERVAL)
            return
          
        elseif r >= Timer.timeStamp.last.data then
            set comp                        = Timer.timeStamp.last.data
            set that                        = Timer(genR.uniqueList.last.data)
          
            set this.uListPointer           = genR.uniqueList.push(this).last
            set this.uniqueListPointer      = genR.uniqueList
          
            set this.stampPointer           = Timer.timeStamp.push(r).last
          
            set this.epsilon                = ModuloReal(r - comp, Timer.INTERVAL)
            set this.durCount               = R2I((r - comp)/Timer.INTERVAL)
            return
        endif
      
        set pos[0] = 1
        set pos[2] = Timer.timeStamp.size()
        set pos[1] = (pos[0] + pos[2])/2
        set pos[3] = 0
      
        set iter   = Timer.timeStamp[pos[1]]
      
        loop
            if r >= iter.data then
                set pos[0] = pos[1]
                set pos[1] = (pos[0] + pos[2])/2
                set pos[3] = pos[1] - pos[0]
            elseif r < iter.data then
                set pos[2] = pos[1]
                set pos[1] = (pos[0] + pos[2])/2
                set pos[3] = pos[1] - pos[2]          
            endif
          
            exitwhen pos[3] == 0
            loop
                exitwhen pos[3] == 0
                if pos[3] > 0 then
                    set iter = iter.next
                    set pos[3] = pos[3] - 1
                elseif pos[3] < 0 then
                    set iter = iter.prev
                    set pos[3] = pos[3] + 1
                endif
            endloop
        endloop      
        //  Easy part
        set this.uListPointer       = genR.uniqueList.append(this, genR.uniqueList[pos[1] + 1]).lastAdded
        set this.uniqueListPointer  = genR.uniqueList
      
        set that                    = Timer(this.uListPointer.prev.data)
        set comp                    = iter.data
        set this.epsilon            = ModuloReal(r - comp, Timer.INTERVAL)
        set this.durCount           = R2I((r - comp)/Timer.INTERVAL)
      
        //  Adjust
        set that                    = Timer(this.uListPointer.next.data)
        set comp                    = iter.next.data
        set that.epsilon            = ModuloReal(comp - r, Timer.INTERVAL)
        set that.durCount           = R2I((comp - r)/Timer.INTERVAL)
      
        set this.stampPointer       = Timer.timeStamp.append(r, iter.next).lastAdded
    endmethod
  
    private method startInternalEx takes real r, boolean looped, integer ticks returns nothing
        local Timer genR    = Timer(F2I(RMinBJ(r, Timer.INTERVAL)))
      
        //  Timer has already started.
        if (this.uListPointer != 0) or (not this.exists) then
            debug if this.uListPointer != 0 then
                debug call Echo("Timer("+I2S(this)+").startInternalEx: Timer instance already started!")
            debug elseif not this.exists then
                debug call Echo("Timer("+I2S(this)+").startInternalEx: Cannot start destroyed timer!")
            debug endif
            return
        endif
              
        set this.stamp      = r
        set this.remaining  = r
        set this.looped     = looped
        set this.count      = IntegerTertiaryOp(looped, 0, this.count + ticks)
        set this.paused     = false
  
        if genR == Timer.interval then
            call this.pushStamp(r)
        else
            if genR.ticker == null then
                set genR.ticker         = CreateTimer()
              
                if genR.uniqueList == 0 then
                    set genR.uniqueList     = IntegerList.create()
                    set Timer.listMap[genR.uniqueList] = genR
                endif
            endif

            if genR.uniqueList.empty() then              
                if not genR.hasTimerExpired then
                    call TimerStart(genR.ticker, r, false, Timer.uniqueEx)
                endif
            endif

            set this.uListPointer       = genR.uniqueList.push(this).last
            set this.uniqueListPointer  = genR.uniqueList
        endif
    endmethod
  
    private method startInternal takes real r, boolean looped, integer ticks, code func returns nothing
        if not this.exists then
            debug call Echo("Timer("+I2S(this)+").startInterval: Cannot start a non-existent Timer object!")
            return
        endif
      
        set this.handlerEv.code = func
      
        call this.startInternalEx(r, looped, ticks)
    endmethod
  
    private method execute takes nothing returns nothing
        if not this.looped then
            set this.count = this.count - 1
            if this.count <= 0 then
                set this.paused = true
            endif
        endif
      
        set this.totalCount = this.totalCount + 1
      
        if this.count >= 0 then
            set this.isOnCallback = true
            set Timer.curTimer = this
            call this.handlerEv.run()
            set this.isOnCallback = false                  
        endif
      
        if this.paused and not (this.looped) then
            set this.totalCount = 0
        endif
      
        if this.toExpire then
            call this.destroyEx()
        else
            if ((not this.looped and (this.count > 0)) or this.looped) and this.uListPointer == 0 then
                call this.startInternalEx(this.stamp, this.looped, 0)
            endif
        endif
    endmethod
  
    private static method uniqueExpiration takes nothing returns nothing
        local Timer genR            = Timer(F2I(TimerGetTimeout(GetExpiredTimer())))
        local Timer this
        local IntegerListItem   iter
      
        local boolean continue      = false
      
        local real timeout          = I2F(genR)
        local real lastStamp
      
        set genR.hasTimerExpired    = true
              
        set iter = genR.uniqueList.first
        loop
            exitwhen genR.uniqueList.empty() or iter == 0
          
            set this = Timer(iter.data)
            set iter = iter.next
                      
            if not this.wasIterated then
                set this.wasIterated = true
              
                call this.uniqueListPointer.erase(this.uListPointer)
              
                set this.uListPointer       = 0
                set this.uniqueListPointer  = 0
            else
                set this.wasIterated = false
                set continue = true
            endif
          
            if not continue then
                set this.remaining = this.remaining - timeout
                if this.remaining*10000. < 1. then
                    call this.execute()
                    set this.wasIterated = false
                else
                    //  False alarm.
                    set this.wasIterated = false

                    set lastStamp   = this.stamp
                    call this.startInternalEx(this.remaining, this.looped, 0)
                    set this.stamp  = lastStamp
                endif
            else
                set continue = false
            endif
        endloop
        set genR.hasTimerExpired    = false
        set Timer.curTimer          = 0

        if not genR.uniqueList.empty() then
            call TimerStart(genR.ticker, timeout, false, Timer.uniqueEx)
        else
            call DestroyTimer(genR.ticker)
            call genR.uniqueList.destroy()
            call Timer.listMap.remove(genR.uniqueList)
          
            set genR.ticker         = null
            set genR.uniqueList     = 0
        endif
    endmethod
  
    private static method commonExpiration takes nothing returns nothing
        local Timer genR            = Timer.interval
        local Timer this
        local Timer that
        local RealListItem   iter
      
        local boolean continue      = false
      
        local real timeout          = Timer.INTERVAL
        local real lastStamp
      
        set Timer.totalDur          = Timer.totalDur + timeout
        set genR.hasTimerExpired    = true
        loop
            exitwhen genR.uniqueList.empty()
          
            set this = Timer(genR.uniqueList.first.data)
            if this.wasIterated then
                set this.wasIterated = false
                set continue         = true
            else
                set this.durCount = this.durCount - 1
            endif
                      
            exitwhen this.durCount > 0 or continue
          
            set this.wasIterated = true

            if this.uListPointer.next != 0 then
                set that             = Timer(this.uListPointer.next.data)
              
                set that.epsilon     = that.epsilon + this.epsilon
                set that.durCount    = that.durCount + R2I(that.epsilon/timeout) + 1
                set that.epsilon     = ModuloReal(that.epsilon, timeout)
            endif
          
            call Timer.timeStamp.erase(this.stampPointer)
            call this.uniqueListPointer.erase(this.uListPointer)
          
            set this.uListPointer       = 0
            set this.uniqueListPointer  = 0
          
            set this.stampPointer       = 0
          
            set this.remaining = this.epsilon
            if this.remaining*10000. < 1. then
                call this.execute()
                if this.uniqueListPointer != genR.uniqueList then
                    set this.wasIterated    = false
                endif
            else
                set this.wasIterated        = false
              
                set lastStamp   = this.stamp
                call this.startInternalEx(this.remaining, this.looped, 0)
                set this.stamp  = lastStamp
            endif
        endloop
        set genR.hasTimerExpired    = false
        set Timer.curTimer          = 0
      
        if not genR.uniqueList.empty() then
            call TimerStart(genR.ticker, timeout, false, Timer.commonEx)
        else
            set Timer.totalDur = 0.
        endif
    endmethod
  
    method running takes nothing returns boolean
        return (not this.paused) and (this.uListPointer != 0)
    endmethod

    method resume takes nothing returns nothing
        local real lastStamp
      
        if this.running() then
            debug call Echo("Timer("+I2S(this)+").resume: Cannot resume a currently running Timer instance!")
            return
        endif
      
        if not (this.stamp != this.remaining) and not (this.stamp != 0.) then
            debug call Echo("Timer("+I2S(this)+").resume: Cannot resume a stopped Timer instance!")
            return
        endif
      
        if this.isOnCallback then
            call this.startInternalEx(this.stamp, this.looped, IntegerTertiaryOp(not this.looped and this.count <= 0, 1, 0))
        else          
            set lastStamp   = this.stamp
            call this.startInternalEx(this.remaining, this.looped, IntegerTertiaryOp(not this.looped and this.count <= 0, 1, 0))
            set this.stamp  = lastStamp
        endif
    endmethod
  
    method pause takes nothing returns nothing
        call this.pauseEx()
    endmethod
  
    method stop takes nothing returns nothing
        call this.stopEx()
    endmethod
  
    method startEx takes real r, integer ticks, code func returns nothing
        call this.startInternal(RMaxBJ(r, 0), false, IMaxBJ(1, ticks), func)
    endmethod

    method startPeriodic takes real r, code func returns nothing
        call this.startInternal(RMaxBJ(r, 0), true, 0, func)
    endmethod
  
    method start takes real r, code func returns nothing
        call this.startInternal(RMaxBJ(r, 0), false, 1, func)
    endmethod
      
    method getRemaining takes nothing returns real
        return this.getRemainingEx()
    endmethod
  
    method setData takes integer data returns Timer
        set this.data = data
        return this
    endmethod
  
    method getElapsed takes nothing returns real
        return this.stamp - this.getRemainingEx()
    endmethod
  
    method getTimeout takes nothing returns real
        return this.stamp
    endmethod
  
    method getIterationCount takes nothing returns integer
        return this.totalCount
    endmethod
  
    method destroy takes nothing returns nothing
        call this.destroyEx()
    endmethod
  
    static method create takes nothing returns Timer
        local Timer this = Timer.allocate()
        set this.handlerEv = Event.create()
        set this.exists    = true
        return this
    endmethod

    private static method init takes nothing returns nothing
        set Timer.tickerMap     = Table.create()
        set Timer.listMap       = Table.create()
        set Timer.listTickMap   = Table.create()
        set Timer.timeStamp     = RealList.create()
      
        set Timer.uniqueEx  = function Timer.uniqueExpiration
        set Timer.commonEx  = function Timer.commonExpiration
      
        set Timer.interval  = Timer(F2I(Timer.INTERVAL))
        set Timer.interval.ticker       = CreateTimer()
        set Timer.interval.uniqueList   = IntegerList.create()
        set Timer.listMap[Timer.interval.uniqueList] = Timer.interval
    endmethod
  
    implement Initializer
endstruct

function NewTimer takes nothing returns Timer
    return Timer.create()
endfunction

function NewTimerEx takes integer data returns Timer
    return Timer.create().setData(data)
endfunction

function GetTimerData takes Timer whichTimer returns integer
    return whichTimer.data
endfunction

function SetTimerData takes Timer whichTimer, integer i returns nothing
    set whichTimer.data = i
endfunction

function ReleaseTimer takes Timer whichTimer returns integer
    local integer i = whichTimer.data
    call whichTimer.destroy()
    return i
endfunction

function GetExpiredTimerEx takes nothing returns Timer
    return Timer.getExpired()
endfunction

endlibrary

Demo:
JASS:
function bar takes nothing returns nothing
    call BJDebugMsg("Current timer instance: " + I2S(GetExpiredTimerEx()))
    call BJDebugMsg("Associated data: " + I2S(GetTimerData(GetExpiredTimerEx())))
endfunction

function foo takes nothing returns nothing
    call NewTimer().setData(1).startPeriodic(1, function bar)
endfunction

@AGD, what do you think of this re-skin, (as I apologize for the lack of a better term)?

@Daffa the Mage, this was what I was working on. It took me a while, but I finished it. :)

EDIT:

Melee map (2)Korea was used as a demonstration of the capabilities of the library, which also contains all of the relevant libraries. This is requirement-heavy.
 

Attachments

  • (2)Korea.w3x
    281.1 KB · Views: 70
Last edited:
And heavy it might just be. This is a timer system that makes sure to use at least one timer and one timer ideally for all periods. A similar purpose is fulfilled in MasterTimer, but I have not read it fully to be certain of its' differences.

Performance-wise, I'm not sure if this is any good, but precision-wise, it is, if it works optimally, on par with native timers. No GetHandleId lookups are present, or at least how I recall it to be.

It also has data attachment found in Timer, TimerUtils, and a somewhat complex time stamp management for long periods.

EDIT:

I have now updated it so that the time stamps will become even more precise than thought possible. Along with it came a lot more on documentation.
 
Last edited:
Well, there is a primary timer for longer time stamps and a generated timer for shorter time stamps.

I can't claim much about efficiency here, but the algorithm behind the system is this:

For a given time stamp greater than INTERVAL, the system decrements the remaining time of that time stamp by INTERVAL seconds. If the resulting remainder is less than INTERVAL, the instance is moved to another list, with the specified remaining time.

Now, given multiple time stamps greater than INTERVAL, it will only decrement the first time stamp's remaining time. The rest of the time stamps' remaining time can be derived via a subtraction process involving a global variable.

Once there are no more elements in the list holding time stamps greater than INTERVAL, the aforementioned global variable is nullified.
 
Inconvenient how?

I mostly use timerutils for easily attaching data to a timer, but I have never felt like it was any worse than using the standard natives
Maybe I phrased that wrong. I mainly prefer standard natives by how they make use of proper syntax highlighting in TESH.
 
Mmmmmh, but that is the intention behind this, in a way that utilizes somewhat convoluted lists, Tables, and minimal timer handles.

I agree, there is no need to reinvent the wheel, but new things can be learned sometimes by trying some old things in a new way, and in this case, a rather vague, relatively complex and known only by a few, kind of way.

That is the spirit of this reinvention, to be a Queued Timer/Time stamp/Timer data attachment version of TimerUtils, with additional features that may not natively exist.
 
Status
Not open for further replies.
Top