• 🏆 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

This is a fixed version of Vexorian's TimerUtils.
It allows you to:
- call NewTimer inside module initializers (Useful for elapsed game time events and other things)
- get the timer data from ReleaseTimer (Doesn't break backwards compatibility)
- call NewTimerEx to preset the timer data

This system is 100% backwards compatible.
Just paste this code inside your old TimerUtils library.

JASS:
library TimerUtilsEx requires optional Table
/*************************************************
*
*   TimerUtilsEx
*   v2.1.0.2
*   By Vexorian, Bribe & Magtheridon96
*
*   Original version by Vexorian.
*
*   Flavors:
*       Hashtable:
*           - RAM:              Minimal
*           - TimerData:        Slow
*
*       Array:
*           - RAM:              Maximal
*           - TimerData:        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.
*
*************************************************/
    // Configuration
    globals
        // Use hashtable, or fast array?
        private constant boolean USE_HASH = false
        // Max Number of Timers Held in Stack
        private constant integer QUANTITY = 256
    endglobals
    
    globals
        private timer array tT
        private integer tN = 0
    endglobals
    
    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
    
    // JassHelper doesn't support static ifs for globals.
    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
    
    // Double free protection
    private function ValidTimer takes integer i returns boolean
        static if LIBRARY_Table then
            return Data.tb.boolean[-i]
        else
            return LoadBoolean(Data.ht, i, 1)
        endif
    endfunction
    
    private function Get takes integer id returns integer
        debug if not ValidTimer(id) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[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
    
    private function Set takes integer id, integer data returns nothing
        debug if not ValidTimer(id) then
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[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
    
    function SetTimerData takes timer t, integer data returns nothing
        call Set(GetHandleId(t), data)
    endfunction
    
    function GetTimerData takes timer t returns integer
        return Get(GetHandleId(t))
    endfunction
    
    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 DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[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 Set(id, data)
        return tT[tN]
    endfunction
    
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction
    
    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 ValidTimer(id) then
            // Get the timer's data.
            set data = Get(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
                
                // If the recycle limit is reached
                if tN == QUANTITY then
                    // then we destroy the timer.
                    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 DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "[TimerUtils]Error: Tried to release non-active timer!")
        endif
        
        //Return Timer Data.
        return data
    endfunction

endlibrary

library TimerUtils requires TimerUtilsEx
endlibrary

Here's a strictly Jass version that runs on hashtables (no array version for simplicity... and the lack of static ifs in Jass)

JASS:
//*************************************************
//*
//*   TimerUtilsEx (Jass Version)
//*   v2.1.0.2
//*   By Vexorian, Bribe & Magtheridon96
//*
//*   Original version by Vexorian.
//*   All the functions have O(1) complexity.
//*
//*   Requires:
//*       - TimerUtils_hash (Hashtable)
//*       - TimerUtils_int (Integer)
//*       - TimerUtils_timer (Timer Array)
//*
//*   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 data to it.
//*       - function ReleaseTimer takes timer t returns integer
//*           - Throws a timer back into the stack. Also returns the 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.
//*
//*************************************************

    function SetTimerData takes timer t, integer value returns nothing
        call SaveInteger(udg_TimerUtils_hash,GetHandleId(t),0,value)
    endfunction
    
    function GetTimerData takes timer t returns integer
        return LoadInteger(udg_TimerUtils_hash,GetHandleId(t),0)
    endfunction
    
    function NewTimerEx takes integer i returns timer
        if 0==udg_TimerUtils_int then
            set udg_TimerUtils_timer[0] = CreateTimer()
        else
            set udg_TimerUtils_int = udg_TimerUtils_int - 1
        endif
        call SaveInteger(udg_TimerUtils_hash, GetHandleId(udg_TimerUtils_timer[udg_TimerUtils_int]), 0, i)
        return udg_TimerUtils_timer[udg_TimerUtils_int]
    endfunction
    
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction
    
    function ReleaseTimer takes timer t returns integer
        call PauseTimer(t)
        set udg_TimerUtils_timer[udg_TimerUtils_int] = t
        set udg_TimerUtils_int = udg_TimerUtils_int + 1
        return GetTimerData(t)
    endfunction
    
    function InitTrig_TimerUtils takes nothing returns nothing
        set udg_TimerUtils_hash = InitHashtable()
    endfunction

Feel free to comment.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
By the blue version i mean without timers already created, only one by one if needed.
So yeah an hashtable + GetHandleId (without timers already created, not like Vexorian did, i think that was Bribe meant) and with a blue version which is working (creates a new timer if no one is available)
 
So, NewTimer() would look like this:

JASS:
function NewTimer takes nothing returns timer
        local timer t
        if 0!=tN then
            set tN=tN-1
        else
            set t = CreateTimer()
            call SetTimerData(tT[tN],0)
            return t
        endif
        call SetTimerData(tT[tN],0)
        return tT[tN]
    endfunction

You know what, Creating new timers in game will totally ruin everything if you aren't using the hash, so I'm only creating one 'flavor':
- Use hashtable
- Create timers in-game when needed

edit
Tell me what you think of this:
JASS:
/*************************************************
*
*   TimerUtils (Special Thanks and Full Credit to Vexorian)
*   v1.1.0.0
*   By Magtheridon96
*
*   Original version by Vexorian.
*
*   This version of the library uses less RAM,
*   it is much cleaner, and supports calling 
*   NewTimer() inside a module initializer.
*
*   Requires:
*       - Table (By Bribe)
*
*   API:
*       - function NewTimer takes nothing returns timer
*           - Returns a new timer from the stack
*       - function ReleaseTimer takes timer t returns nothing
*           - Throws a timer back into the stack
*       - function SetTimerData takes timer t, integer value returns nothing
*           - Attatches a value to a timer
*       - function GetTimerData takes timer t returns integer
*           - Returns the attatched value
*
*************************************************/
library TimerUtils requires Table
    globals
        private timer array tT
        private integer tN = 4096
        private Table tb
    endglobals
    
    function SetTimerData takes timer t, integer value returns nothing
        set tb[GetHandleId(t)] = value
    endfunction
    
    function GetTimerData takes timer t returns integer
        return tb[GetHandleId(t)]
    endfunction
    
    function NewTimer takes nothing returns timer
        set tN=tN-1
        if tT[tN]==null then
            set tT[tN]=CreateTimer()
        endif
        call SetTimerData(tT[tN],0)
        return tT[tN]
    endfunction
    
    function ReleaseTimer takes timer t returns nothing
        call PauseTimer(t)
        set tT[tN]=t
        set tN=tN+1
    endfunction
    
    private module Init
        private static method onInit takes nothing returns nothing
            set tb = Table.create()
        endmethod
    endmodule
    private struct Inits extends array
        implement Init
    endstruct

endlibrary

This one uses much less RAM.

edit
meh, updated.
 
Last edited:

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
What's the point of library TimerUtils2? Just make it library_once TimerUtils.

Also, you have no safety whatsoever... if a user happened to double free a timer, he'd run into a lot of shit.
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
That's true, but it would be nice to have an error message when we pass the 128 timer limit in the array version.

EDIT: Oh, something like this would be nice:
JASS:
function NewTimer takes nothing returns timer
    set tN=tN-1
    debug if tN<0 then
    debug call BJDebugMsg("[TimerUtils Error]: Timer limit passed!\nOverflow: "+I2S(-tN))
    debug return null
    debug endif
    call SetTimerData(tT[tN],0)
    return tT[tN]
endfunction
 
Level 10
Joined
May 27, 2009
Messages
494
Perhaps just add the double free protection
since old people may not be able to address that their code contains double free especially to maps with lots of codes, checking each one by one if there are any double free, and in fact, there might come a time the code doesn't contain any double free but the function will be called another time (lets say same timer handle was used as input)

nothing will be gone if you add a double free protection, its just less than 10 lines of code

if you can't, get a cup of water, then take a good long rest
 
I cant use 0 :p
That would mean i'd be using negative indices in the array ;)
Im using 4096 because that's the maximum amount of timers id expect anyone to be using at once (worst case scenario: tons of spell spamming, hundreds of systems with active timers, really nooby vjasser :p)

Now when it comes to the double free protection everyone wants, i'll make it debug-only -.-
 
Level 3
Joined
Aug 27, 2011
Messages
41
Hey I'm looking over the source code for JassHelper, if I can figure it out, I'll try to fix static ifs for you. What would you like the static ifs to do? You need them to be checked first and then either comment the lines out or not copy them over at all? Also do you want the same command for static if that is process before globals or would you like it processed as a different command (although, I don't see much point in this other then someone bringing up backwards compatibility lol).
 
Level 3
Joined
Aug 27, 2011
Messages
41
lol nice, I was looking over the whole source code, It made my head spin. I don't know if it's all delphi, but good grief that was so messy. I was jumping through things in eight directions lol. I'm keep at it but I'm not promising quick results, I might have better luck if I started from scratch then using it lol. But glad you found a way to do it :)
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
JASS:
tN = 0 // initial value

function NewTimer takes nothing returns timer
    if tN==0 then
        set tT[0]=CreateTimer()
    else
        set tN = tN-1
    endif
    call SetTimerData(tT[tN],0)
    return tT[tN]
endfunction

function ReleaseTimer takes timer t returns nothing
    call PauseTimer(t)
    set tT[tN]=t
    set tN=tN+1
endfunction

There is nothing wrong with the "else", and this way you would use less ram, because of the array size.
That does not matter that much, but still it's more logical.
That's how stacks are build, as you may know it.
 
Level 3
Joined
Aug 27, 2011
Messages
41
Trust me, reading the JassHelper source code is like reading a fully optimized Jass map with no spacing, but even worse :p

Oh, I've read them before, but good grief i nearly died seeing how it was written, delphi seems complicated enough compared to any of the modern languages, but I mean come on almost nothing is commented, the file's naming scheme is not legible. I understood the actual code a little I could understand what was being done it just was so jumpy lol.

If I could re-write it up in C# it would be so much better and just about any programmer could read it I think. I mean If you can read java, C++ C, Visual Basic, Ruby, Lua then you can read C# lol. Heck even javascript is similar, all of today's main languages are written similar it makes it simpler that way.

I once took an entire java program and nearly just copied and pasted it into a C++ the only things I changed were the headers it was hillarious. It worked, plus I could edit it better lol.
 
@Troll-Brain:
I'll look into it ;)

@Fox536:
Could you PM some simple C# snippet? :D
I want to be familiar with the syntax :p
I know C++, so the syntax is likely the only thing I have to learn xD

edit
Troll-Brain, I just realized that your snippet would bug if you do this:
JASS:
call NewTimer()
call NewTimer()

But, for your sake, I'll change the default value of tN to 1024

edit
Updated! :D
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
No it won't bug.
tT[0] will just be overwritten (only if there is not any timer available, aka recycled), that's all.
But it doesn't matter, since when there is 0 timer available, tT[0] is just used as a temp variable, instead of it you could use an other one, but that is pointless.

You have to remember that a timer isn't available (i mean ready for a recycle) until it's recycled with the function ReleaseTimer.
So no, a new created timer isn't available in the stack, until you recycle it with the function ReleaseTimer.

And yes, tT[0] is correctly used as soon there is one or more timer recycled.
If you don't believe me, think more about it, or make tests with displaying texts.
 
Level 7
Joined
Oct 11, 2008
Messages
304
JASS:
        static if not USE_HASH then
            debug if Data.tN>Data.QUANTITY then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[TimerUtils]Error: You must only release timers created with NewTimer()")
            debug endif
        endif

// >>

        debug static if not USE_HASH then
            debug if Data.tN>Data.QUANTITY then
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"[TimerUtils]Error: You must only release timers created with NewTimer()")
            debug endif
        debug endif
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
I'm still talking about mapmaking. I have no idea how you went into the world of spells.

And when re-reading the code, QUANTITY MEMBER SHOULD HAVE A BIG ASS COMMENT SHOWING IT (but I guess only experienced users will ever change it and they will find it so whatever).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
The module should be private, and I am not sure why QUANTITY isn't in the globals block where USE_HASH boolean is.

The hashtable should be initialized from the module not from its declaration. Thread crashing of the main thread is the reason why, here. It's why Vexorian didn't initialize his "Table" hashtable from the globals block as well (though he was not caring enough to initialize in a module).

The practice of having a "null" value for the data is bad, it is in fact possible in some stretch of the imagination that someone actually uses that number. So for the array version you use a dedicated boolean array for this purpose, and for the hash version you can use "-GetHandleId(t)" for the key instead of "GetHandleId(t)" and store a boolean with that. "true" means it's a valid timer created with NewTimer and "false" means it's an already-released timer or just some random timer that had no business with TimerUtils.

Also, the two structs should be merged.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Hmm, hitting the op limit with only with global definitions, i highly doubt.
My therory is that the function "main" opens a new thread, i mean reset the limit op.

Let's make a cjass test. (i will edit the result)

EDIT : cJass currently doesn't support the vJass "//! inject" feature.

So i'm linking a map script instead (check the function main).
Originally there were 20 000 locations defined, i deleted more than 2000 of them and i got the same result.

In both cases the displayed message is : "42061"

So yes when the main function is called, the limit op is reset.

Note that 20 000 globals defined is such an unrealistic case and it still doesn't crash the thread, but it will with more (under 30 000, i haven't investigate more).

EDIT 2 : The limit could be even much more, i just remembered that i used the #for of cJass to generate the globals, but pjass runs out of memory when i used more than 20 000 globals, i confused with the usual limit op wich is near to 20 000 in a simple loop.
Also the first and the last global should be checked (valid value or not), since there is still the possibility that some globals definitions are ignored, even if in any real map, a such case would never happen.

Now if you are talking about arrays that's not the same thing, it's done in the function main with a custom function InitGlobals (talking about the GUI variables).
And of course if you initialize many indices of array (several thousands) the limit op will be reached.
 

Attachments

  • war3map.j.zip
    32.3 KB · Views: 104
Last edited:
The module should be private, and I am not sure why QUANTITY isn't in the globals block where USE_HASH boolean is

My bad :p
I'll make it private. It isn't supposed to be in the globals block because if USE_HASH = true, you don't need it ^^

The practice of having a "null" value for the data is bad, it is in fact possible in some stretch of the imagination that someone actually uses that number. So for the array version you use a dedicated boolean array for this purpose, and for the hash version you can use "-GetHandleId(t)" for the key instead of "GetHandleId(t)" and store a boolean with that. "true" means it's a valid timer created with NewTimer and "false" means it's an already-released timer or just some random timer that had no business with TimerUtils.

Sounds slow :eek:
But anyway, will do :D

Also, the two structs should be merged.

K (Not potassium, I meant: Okay) xD

The hashtable should be initialized from the module not from its declaration. Thread crashing of the main thread is the reason why, here. It's why Vexorian didn't initialize his "Table" hashtable from the globals block as well (though he was not caring enough to initialize in a module).

Will think about it :)

edit
Updated.
I even maintained speed ;D
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Nothing really useful but you could improve NewTimer and ReleaseTimer (hashtable version) :

Instead of :

JASS:
function NewTimer takes nothing returns timer
    if tN==0 then
        set tT[0]=CreateTimer()
    else
        set tN = tN-1
    endif
    call SetTimerData(tT[tN],0)
    return tT[tN]
endfunction

function ReleaseTimer takes timer t returns nothing
    call PauseTimer(t)
    set tT[tN]=t
    set tN=tN+1
endfunction

Do that :

JASS:
function NewTimer takes nothing returns timer
    if tN==0 then
        return CreateTimer()
    else
        set tN = tN-1
    endif
    return tT[tN]
endfunction

function ReleaseTimer takes timer t returns nothing
    call PauseTimer(t)
    call SetTimerData(t,0)
    set tT[tN]=t
    set tN=tN+1
endfunction

Just because a timer provided is a new one (created) or a recycled one, in both cases the data is supposed to be 0.
In fact you could even skip entirely the call of SetTimerData, or use it in debug mode, since it depends of the user which must use SetTimerData if he needs to link an integer to the timer.
The data integer argument would be better included directly in the function NewTimer though, but as you know then you will make a backward incompatibility with the old TimerUtils.
 
Last edited:
Top