- Joined
- May 9, 2014
- Messages
- 1,807
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:
Demo:
@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.
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
Last edited: