1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

TimerUtilsEx - A reinvention

Discussion in 'The Lab' started by MyPad, Oct 12, 2018.

  1. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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:
    Code (vJASS):

    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:
    Code (vJASS):

    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.
     

    Attached Files:

    Last edited: Oct 28, 2018
  2. Daffa the Mage

    Daffa the Mage

    Map Moderator

    Joined:
    Jan 30, 2013
    Messages:
    7,685
    Resources:
    27
    Packs:
    1
    Maps:
    8
    Spells:
    16
    Tutorials:
    2
    Resources:
    27
    Not sure if I ask anything regarding this to be honest, must be bad memorization on me :D
     
  3. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    I've been really busy lately but I might look at this at a later time. Haven't touched the editor for more than 3 weeks now :(.
     
  4. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,622
    Resources:
    18
    Maps:
    1
    Spells:
    11
    Tutorials:
    6
    Resources:
    18
  5. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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: Oct 28, 2018
  6. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,842
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    It only uses one timer? That will have performance issues. I would still prefer TimerTools' merging algorithm rather than a one timer system. I doubt it can handle 8000 instances in stress test

    nestharus/JASS
     
  7. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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.
     
  8. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Don't try to reinvent the wheel. Timer systems have been engineered to death and with the implementation of hashtables, there isn't even a real need to have them anymore, as the speed difference of having a timer system doesn't really offset the inconvenience and restrictions they impose.
     
  9. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,622
    Resources:
    18
    Maps:
    1
    Spells:
    11
    Tutorials:
    6
    Resources:
    18
    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
     
  10. Zwiebelchen

    Zwiebelchen

    Joined:
    Sep 17, 2009
    Messages:
    6,789
    Resources:
    12
    Models:
    5
    Maps:
    1
    Spells:
    1
    Tutorials:
    1
    JASS:
    4
    Resources:
    12
    Maybe I phrased that wrong. I mainly prefer standard natives by how they make use of proper syntax highlighting in TESH.
     
  11. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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.