• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] TimerUtilsEx

Level 16
Joined
Aug 7, 2009
Messages
1,403
What about a NewTimerEx() function that takes an integer argument? This would make starting timers easier:

JASS:
local timer t=NewTimer()
call SetTimerData(t,this)
call TimerStart(t,T32_PERIOD,true,function thistype.callback)
set t=null

-->

JASS:
call TimerStart(NewTimerEx(this),T32_PERIOD,true,function thistype.callback)

EDIT: and to be honest I'd separate the Hashtable and Array versions (just like Vexorian did). The bunch of static ifs and the fact that TESH is bugged and doesn't fold static ifs properly is annoying IMO.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I would use "static if DEBUG_MODE" instead of all those debug statements, it would make it way more readable.

Luorax, what would be the point of code folding? I have never used it and I never have used it.

I also support a wrapper for returning a new timer and assigning it data, as well as a single function that releases the timer and returns its data. That's why I made the NewTimerData snippet.

http://www.hiveworkshop.com/forums/graveyard-418/snippet-newtimerdata-184289/
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
And just the same ReleaseTimerEx.

I also think that the QUANTITY should be applied no matter what. Similar to what I did with the projectile recycling system you should set a limit to the maximum amount of timers that can be stored, that way a sudden spike in the game will not cause that number to overflow into undesirable quantities. 256 timers is a fine enough number to use as a recyce cap but of course it'd be configurable.
 
You know what, how about I just make ReleaseTimer return an integer?
It won't break this:
call ReleaseTimer(t)
Plus, I'll have less code ;D
And hashtable lookup from every ReleaseTimer call shouldn't be a problem at all.

I also think that the QUANTITY should be applied no matter what. Similar to what I did with the projectile recycling system you should set a limit to the maximum amount of timers that can be stored, that way a sudden spike in the game will not cause that number to overflow into undesirable quantities. 256 timers is a fine enough number to use as a recyce cap but of course it'd be configurable.

But the overhead will cripple the function D:
Plus, a spike shouldn't create anything more than 256 timers ;D
Spike:
- 30 spells casting (assume 2 timers each)
- 40 systems running (assume 2 timers each)
- Assume 50 other timers being used.

That's like 190 timers and I was exaggerating the map conditions :p
Spikes are no problem ^_^
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
That should be cool. Then to make sure that no one tries to use the old TimerUtils that doesn't have the NewTimerEx and the ReleaseTimer that returns an integer, perhaps this should have the library name TimerUtilsEx and make this snippet underneath:

JASS:
library TimerUtils requires TimerUtilsEx
endlibrary

That way people who want the new functionality should require TimerUtilsEx, and for the older libraries or for people who don't care can just require TimerUtils.

I think this will be better as well because it doesn't make the same mistakes I made with Table, what with breaking backwards compatibility and all.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
return GetTimerData(t) will return 0 in debug mode because you erased its data with a boolean then.

You'd want to cache the return value of GetTimerData(t) before proceeding with deallocating it, and then return that cached value. It'd look something like this:

JASS:
local integer d = GetTimerData(t)
//Run debug deallocate code
return d
 
Level 7
Joined
Oct 11, 2008
Messages
304
Magtheridon96, I'll introduce you to a friend
resurrection.jpg
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Luorax, you should have more faith in people. I have even been able to get Nestharus to change his mind sometimes.

edit:

Magtheridon96, you need to update this. It doesn't even compile.

I have re-written the library as well to fix some other issues. You can't offset all 256 handles by one of the handle ID's because the handle counter resets every 2000 or so handles, and you might get into the negative indices for arrays. I changed it to just use 0x100000. The handle count during module init wouldn't be too high for it to be a problem anyway.

I advise you to use this re-written system, I have tested it in all three modes and it works just fine. It destroys timers if QUANTITY is too high in USE_HASH mode (with array mode it still displays the debug message that QUANTITY needs to be increased). I also added more in-game safety in the NewTimer/ReleaseTimer function which make them slightly slower than before, but it is needed because this is a highly-modular public resource and you have to think that 90% of TimerUtils users are newbies because this is usually the first library someone uses.

JASS:
library TimerUtilsEx requires optional Table
/*************************************************
*
*   TimerUtilsEx
*   v2.1.0.0
*   By Magtheridon96
*
*   Original version by Vexorian.
*
*   Flavors:
*       Hashtable:
*           - RAM:              Minimal
*           - Get/SetTimerData: Slow
*
*       Array:
*           - RAM:              Maximal
*           - Get/SetTimerData: Fast
*
*   All the functions have O(1) complexity.
*   The Array version is the fastest, but the hashtable
*   version is the safest. The Array version is still
*   quite safe though, and I would recommend using it.
*   The system is much slower in debug mode.
*
*   Optional Requirement:
*       - Table by Bribe
*           hiveworkshop.com/forums/showthread.php?t=188084
*
*   API:
*   ----
*       - function NewTimer takes nothing returns timer
*           - Returns a new timer from the stack.
*       - function NewTimerEx takes integer i returns timer
*           - Returns a new timer from the stack and attaches a value to it.
*       - function ReleaseTimer takes timer t returns integer
*           - Throws a timer back into the stack. Also returns timer data.
*       - function SetTimerData takes timer t, integer value returns nothing
*           - Attaches a value to a timer.
*       - function GetTimerData takes timer t returns integer
*           - Returns the attached value.
*
*   Special Thanks To:
*       - Bribe
*       - Vexorian
*       - Troll-Brain
*
*/
    globals //Configuration
        private constant boolean USE_HASH = false
        private constant integer QUANTITY = 256 //The max number of timers that can be stored.
    endglobals

    globals
        private timer array tT
        private integer tN = 0
    endglobals

    //=======================================================================
    // Module to initialize the system.
    //
    private module Init
        private static method onInit takes nothing returns nothing
            static if not USE_HASH then
                local integer i = QUANTITY
                loop
                    set i = i - 1
                    set tT[i] = CreateTimer()
                    exitwhen i == 0
                endloop
                set tN = QUANTITY
            elseif LIBRARY_Table then
                set .tb = Table.create()
            endif
        endmethod
    endmodule

    //=======================================================================
    // A struct is needed to static-if out unwanted globals and to initialize
    // the system in the highest priority (by implementing the module).
    //
    private struct Data extends array
        static if not USE_HASH then
            static integer array data
        endif
        static if LIBRARY_Table then
            static Table tb = 0
        else
            static hashtable ht = InitHashtable()
        endif
        implement Init
    endstruct

    //=======================================================================
    // "Is Timer Valid" function for debugging and double-free safety.
    //
    private function ITV takes integer i returns boolean
        static if LIBRARY_Table then
            return Data.tb.boolean[-i]
        else
            return LoadBoolean(Data.ht, i, 1)
        endif
    endfunction

    //=======================================================================
    // "Get Timer Data" function because the system already has the handle ID
    // when it calls it. It inlines when not compiled in debug mode.
    //
    private function GTD takes integer id returns integer
        debug if not ITV(id) then
            debug call BJDebugMsg("[TimerUtils]Error: Tried to get data from invalid timer.")
        debug endif
        static if USE_HASH then
            static if LIBRARY_Table then
                return Data.tb[id]
            else
                return LoadInteger(Data.ht, id, 0)
            endif
        else
            return Data.data[id - 0x100000]
        endif
    endfunction

    //=======================================================================
    // "Set Timer Data" function because the system already has the handle ID
    // when it calls it. It inlines when not compiled in debug mode.
    //
    private function STD takes integer id, integer data returns nothing
        debug if not ITV(id) then
            debug call BJDebugMsg("[TimerUtils]Error: Tried to attach data to invalid timer.")
        debug endif
        static if USE_HASH then
            static if LIBRARY_Table then
                set Data.tb[id] = data
            else
                call SaveInteger(Data.ht, id, 0, data)
            endif
        else
            set Data.data[id - 0x100000] = data
        endif
    endfunction

    //=======================================================================
    // Assign an integer to the timer. It inlines when not compiled in debug
    // mode.
    //
    function SetTimerData takes timer t, integer data returns nothing
        call STD(GetHandleId(t), data)
    endfunction

    //=======================================================================
    // Get the integer from the timer. It inlines when not compiled in debug
    // mode.
    //
    function GetTimerData takes timer t returns integer
        return GTD(GetHandleId(t))
    endfunction

    //=======================================================================
    // Returns a new timer with the specified integer attached to it.
    //
    function NewTimerEx takes integer data returns timer
        local integer id
        if tN == 0 then
            static if USE_HASH then
                set tT[0] = CreateTimer()
            else
                debug call BJDebugMsg("[TimerUtils]Error: No Timers In The Stack! You must increase 'QUANTITY'")
                return null
            endif
        else
            set tN = tN - 1
        endif
        set id = GetHandleId(tT[tN])
        static if LIBRARY_Table then
            set Data.tb.boolean[-id] = true
        else
            call SaveBoolean(Data.ht, id, 1, true)
        endif
        call STD(id, data)
        return tT[tN]
    endfunction

    //=======================================================================
    // Returns a new timer.
    //
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction

    //=======================================================================
    // Release the timer when you're finished with it.
    //
    function ReleaseTimer takes timer t returns integer
        local integer id = GetHandleId(t)
        local integer data = 0

        //Pause the timer just in case.
        call PauseTimer(t)

        //Make sure the timer is valid.
        if ITV(id) then

            //Get the timer's data.
            set data = GTD(id)

            //Unmark handle id as a valid timer.
            static if LIBRARY_Table then
                call Data.tb.boolean.remove(-id)
            else
                call RemoveSavedBoolean(Data.ht, id, 1)
            endif

            //If it's not run in USE_HASH mode, this next block is useless.
            static if USE_HASH then

                //At least clear hash memory while it's in the recycle stack.
                static if LIBRARY_Table then
                    call Data.tb.remove(id)
                else
                    call RemoveSavedInteger(Data.ht, id, 0)
                endif

                //Enforce the timer recycle limit.
                if tN == QUANTITY then
                    call DestroyTimer(t)
                    return data
                endif
            endif

            //Recycle the timer.
            set tT[tN] = t
            set tN = tN + 1

        //Tried to pass a bad timer.
        debug else
            debug call BJDebugMsg("[TimerUtils]Error: Tried to release non-active timer!")
        endif

        //Return the timer data. This is perfect for one-shot timers.
        return data
    endfunction

endlibrary

library TimerUtils requires TimerUtilsEx
endlibrary
 
You can't say "soon", it's copyrighted by Blizzard.

LOL

The new NewTimerEx and ReleaseTimer functions are useful for sure, I'm glad you liked my idea.

And if you have any more, I'll gladly add them in ;D

edit
Updated.
I also updated the Jass version in favor of all those Vanilla Jassers out there.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Magtheridon, like I said, you can't use "baseIndex". You have to use 0x100000 because of handle fragmentation. Sure it will work most of the time but if there were a lot of handles cycled through already during initialization it will break, and it will break badly.

It is also faster to use 0x100000.

Just use the script I gave you (and to quote a smilie by Nestharus: >.>)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
@ Mag: Call it a joint project then, you & me.

I have edited your post to remove a bug because you didn't initialize the "data" variable in the ReleaseTimer function, so if it was not a valid timer it would crash the thread which sucks.

I also removed those return statements you added to the get/set functions because debug mode should really just be for printing error messages. It shouldn't have different return results.

Edit: Approved. This is completely backwards-compatible and works great.
 
Last edited:
Level 4
Joined
Jun 19, 2010
Messages
49
ended up here while searching for TimerUtils to find an answer about timer limit, as someone claimed that even TimerUtils has a limit of 256 simultaneous timers. it seems that i have to use 2 timers for each of my towers, so i could theoretically end up breaking the 256 timers limit, hence the research...

now i asked myself why this resource here was put into graveyeard. also other users like "kStiyl" didn't understand. after some research, i found out, that Vexorian really updated his good old TimerUtils library again in 2011 and i missed it! you can only see it in the line:
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
before it was:
//* TimerUtils (red+blue+orange flavors for 1.24b+)
here is the link to the original thread from Vexorian with current TimerUtils version, last updated on October 31st, 2011:
Chromatic TimerUtils 2.0 [for 1.24b+] - Wc3C.net

updated my map now with his current version, but still not sure about the 256 timer instance limit...
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Since wc3c.net has been deprecated, I thought it best to include links for legacy purposes:
1) Original TimerUtils on archive.org: Chromatic TimerUtils 2.0 [for 1.24b+] - Wc3C.net
2) The state-of-the-art approach using Table 6: [vJASS] - Timeout

Example Comparisons:
1) Vexorian's TimerUtils:
JASS:
function Second takes nothing returns nothing
   local timer t = GetExpiredTimer()
   local Table data = GetTimerData(t)
   call ReleaseTimer(t)
   set t = null
   //do stuff with data
   call data.destroy()
endfunction

function First takes nothing returns nothing
    local Table data = Table.create()
    local timer t = NewTimer()
    call SetTimerData(t, data)
    call TimerStart(t, 1, false, function fn)
    set t = null
    //assign stuff to data
endfunction
2) Mag's TimerUtilsEx (a version of [Snippet] NewTimerData that combined the functionality into a rewritten TimerUtils):
JASS:
function Second takes nothing returns nothing
   local Table data = ReleaseTimer(GetExpiredTimer())
   //do stuff with data
   call data.destroy()
endfunction

function First takes nothing returns nothing
    local Table data = Table.create()
    call TimerStart(NewTimerEx(data), 1, false, function fn)
    //assign stuff to data
endfunction

3) Bribe's Timeout:
JASS:
function Second takes nothing returns nothing
   local Table data = Timeout.getData()
   //do stuff with data
endfunction

function First takes nothing returns nothing
    local Table data = Timeout.start(1, false, function fn)
   //assign stuff to data
endfunction
 
Last edited:
Top