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

Timer (easier than TimerUtils)

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
Timer - an easy way to use (attaching struct instances/data to timers) and implement timers

Probably many people know/use TimerUtils which provides a nice way to work (attaching struct instances/data) with timers and it's also flexible/configurable. But I think this (the Timer struct) is even nicer (in my opinion =)) to use and in terms of implementation. I see that TimerUtil's implementation complexity comes from it's configurability but it's also a bit confusing, i.e which flavor should I use?

timers.j:
JASS:
library Timers

// Credits:
//     the guys from xgm.guru (the russian modding community) for finding (back in 2007?) the method
//     of attaching data to timers used by this library: http://xgm.guru/p/wc3/timer-exploit
//
//     back in late 2006 Captain Griffen shows this method as well: http://www.wc3c.net/showpost.php?p=832021&postcount=3

//! novjass

// starting/creating a timer
//
static method start takes integer user_data, real timeout, code callback returns Timer

// stopping/releasing a timer
//
method stop takes nothing returns nothing

// pausing a timer
//
method pause takes nothing returns nothing

// there are two methods which give access to the data within the body of the callback function:
//
// the first is:
//    Timer.get_expired_data, which is intended to be used by oneshot timers
//
// the second is:
//    Timer.get_expired, which is intended to be used by periodic timers
//

// returns the data and automatically stops/releases the timer
//
static method get_expired_data takes nothing returns integer

// returns the timer instance which has the .data member with which you can access the data,
// but does NOT automatically stop/release the timer, i.e you have to do it manually (at some point)
//
static method get_expired takes nothing returns Timer

// this method needs to be called for periodic timers
// NOTE: the callback has to be passed again (because we can't store code variables in arrays, this is a Jass2 limitation)
//
method restart takes code callback returns nothing

//! endnovjass

struct Timer extends array
    readonly static integer max_count = 0 // the maximum number of timers we've ever had ticking
    readonly static integer curr_count = 0 // the current number of timers ticking

    private static Timer head = 0 // a free list of timers
    private Timer next

    private timer t
    integer data
    real timeout

    static method start takes integer user_data, real timeout, code callback returns Timer
        local Timer this

        if head != 0 then
            set this = head
            set head = head.next

        else
            set max_count = max_count + 1
            if max_count > 8190 then
static if DEBUG_MODE then
                call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: could not allocate a Timer instance|r")
endif
                return 0
            endif

            set this = max_count
            set this.t = CreateTimer()
        endif
        set curr_count = curr_count + 1

        set this.next = 0
        set this.data = user_data
        set this.timeout = timeout

        call TimerStart(this.t, this, false, null)
        call PauseTimer(this.t)
        call TimerStart(this.t, timeout, false, callback)

        return this
    endmethod

    method stop takes nothing returns nothing
        if this == 0 then
static if DEBUG_MODE then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: cannot stop null Timer instance|r")
endif
            return
        endif

        if this.next != 0 then
static if DEBUG_MODE then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: cannot stop Timer(" + I2S(this) + ") instance more than once|r")
endif
            return
        endif

        set curr_count = curr_count - 1

        call TimerStart(this.t, 0.0, false, null)
        set this.next = head
        set head = this
    endmethod

    method pause takes nothing returns nothing
        call TimerStart(this.t, 0.0, false, null)
    endmethod

    static method get_expired takes nothing returns Timer
        return R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5)
    endmethod

    static method get_expired_data takes nothing returns integer
        local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
        local integer data = t.data
        call t.stop()
        return data
    endmethod

    method restart takes code callback returns nothing
        call TimerStart(this.t, this.data, false, null)
        call PauseTimer(this.t)
        call TimerStart(this.t, this.timeout, false, callback)
    endmethod

endstruct

endlibrary

demo (worst demo ever):
JASS:
struct Message
    string message
    integer repeat_count
endstruct

function print_message takes nothing returns nothing
    local Message msg = Timer.get_expired_data()
    call BJDebugMsg(msg.message)
    call msg.destroy()
endfunction

function print_message_once takes string message, real delay returns nothing
    local Message msg = Message.create()
    set msg.message = message
    call Timer.start(msg, delay, function print_message)
endfunction

function print_message_many_times takes nothing returns nothing
    local Timer t = Timer.get_expired()
    local Message msg = t.data

    call BJDebugMsg(msg.message + " (" + I2S(msg.repeat_count) + ")")

    set msg.repeat_count = msg.repeat_count - 1
    if msg.repeat_count > 0 then
        call t.restart(function print_message_many_times)
    else
        call t.stop()
        call msg.destroy()
    endif
endfunction

function spam_message takes string message, real delay, integer repeat_count returns nothing
        local Message msg = Message.create()
        set msg.message = message
        set msg.repeat_count = repeat_count
        call Timer.start(msg, delay, function print_message_many_times)
endfunction
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
cool trick

1, why the hell not
JASS:
       call TimerStart(.t, this, false, null)
       call PauseTimer(.t)
       call TimerStart(.t, .timeout, false, callback)
this instead of this.data?
I guess that is a bug.

2, where is .resume()?

3, set this.t = CreateTimer()
I would still prefer a stack.
I would also use the normal constuctors/destructors instead of extends array.
If people dont like it, they can implement their own constructor/destructor but I think you shouldnt do it as a standard.
 
Level 13
Joined
Nov 7, 2014
Messages
571
1, why the hell not
JASS:
       call TimerStart(.t, this, false, null)
       call PauseTimer(.t)
       call TimerStart(.t, .timeout, false, callback)
this instead of this.data?
Because the user_data might not be a struct instance, i.e it could be player-id(s), texttag-id(s), etc.

2, where is .resume()?
I've never used ResumeTimer, have you? But sure it's missing.

3, set this.t = CreateTimer()
I would still prefer a stack.
Why? It would be more/code and/or less safe?

I would also use the normal constuctors/destructors instead of extends array.
Well this does use vanilla vJass allocate, minus destructor, plus different error messages, but it enables the Timer.max_count "introspection" thingie while using the default construcotr won't.

This is proven to be slower than using a hashtable.

Do you mean that local integer data = R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) is slower than local integer data = LoadInteger(ht, 0, GetHandleId(GetExpiredTimer()))?
If so then I am not so sure that's the case (did a FPS benchmark =)).
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Because the
user_data
might not be a struct instance, i.e it could be player-id(s), texttag-id(s), etc.

You use this:
call TimerStart(this.t, user_data, false, null)
Which saves the user_data and returns that when you do "TimerGetRemaining()"
However, that means that this:
local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )
Is bullshit because t now is NOT the instance but the user_data.
So t.data is NOT the user_data in most cases.
What you have to do is start the timer with "this" as duration so that the conversion will find that instance again.
Then you can load the data.

Using a stack wouldnt really make it less safe.

And you dont use the standard allocation, which means that the struct loses a lot of features.
Only looking at "extends array" already proves that.
 
Level 13
Joined
Nov 7, 2014
Messages
571
You use this:
call TimerStart(this.t, user_data, false, null)

Which saves the user_data and returns that when you do "TimerGetRemaining()"
However, that means that this:
local Timer t = Timer( R2I(TimerGetRemaining(GetExpiredTimer()) + 0.5) )

Is bullshit because t now is NOT the instance but the user_data.
So t.data is NOT the user_data in most cases.
What you have to do is start the timer with "this" as duration so that the conversion will find that instance again.
Then you can load the data.

Ah, right. Thanks =)!

Using a stack wouldnt really make it less safe.
Show, don't tell =).

And you dont use the standard allocation, which means that the struct loses a lot of features.
Only looking at "extends array" already proves that.
I don't understand what you are saying...
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
The standard allocation is the .allocate() method that comes with normal structs.
When you extend array, you lose that... including all classifiers, polymorphism and all other things that structs create.

It is completely fine to not use it, but some people might find those useful... like me.
If you dont like them, you simply make a module with the allocators how you like them and implement that and add the extends array.
But having it as default doesnt really sound nice, because it is harder to revert and the people that prefer normal structs are ussually less experienced with it.

Safe stack usage requires two things.
1, you need a stack storage:
JASS:
    private static method loadTimer takes nothing returns timer
        if .STACK_SIZE > 0 then
            set .STACK_SIZE = .STACK_SIZE -1
            return .STACK[.STACK_SIZE]
        endif
        return CreateTimer()
    endmethod
   
    private static method storeTimer takes timer t returns nothing
        set .STACK[.STACK_SIZE] = t
        set .STACK_SIZE = .STACK_SIZE +1
    endmethod
That wasnt that hard.
2, You are only allowed to have one reference to the handle (timer).
Which also already happens in your script.
... Wait... you are missing a destructor.
private timer t + proper constructor and destructor + nulling inside the destructor prevents the handle from being referenced twice.
But you dont need to reference the timer, you just need to reference the Timer (struct).

That is the problem of TimerUtils, the library doesnt control a private timer, instead it hands you a timer, in which case it is possible (through a bit bad code) to store a timer multiple times... which causes one timer to be used by multiple instances which bugs the game.
But in this case, you shouldnt have any problems with it.
 
Why using a timer system at all when it's just about attaching data? It's not hard to attach data manually by simply using a single global hashtable. Like, literally, what's the point of timer systems? To save three extra lines of code for a significant code bloat behind the scenes?

Really, the only reason ever to use a timer system is for running multiple instances of code on a single 32 fps timer. Everything else is irrelevant and just false promises of convenience.
Note: it's not convenience if it saves two lines at the cost of syntax highlighting and horrible API naming conventions.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Like, literally, what's the point of timer systems?

You "need" a timer system when you have things that have different durations, e.g: sounds, special effects, ressurecting units, etc.
If all the durations are the same (1.0 / 32.0, or whatever) you would just use a single timer and loop through all your instances instead.

Well you don't really "need" a timer system, like you said, you can just attach data via a single hashtable, but you also have to destroy or leak or recycle the timers yourself.

So what timer systems do is: recycle timers (which is the biggest win, in my opinion), potentially faster attaching than the hashtable (this is very minor thing), and using a "horrible API naming conventions" they could shorten this:
JASS:
function callback takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer data = LoadInteger(ht, 0, GetHandleId(t))
    // use data
    call PauseTimer(t)
    call DestroyTimer(t)
    set t = null
endfunction

function use_timer takes nothing returns nothing
    local timer t = CreateTimer()
    local integer data = 42
    call SaveInteger(ht, 0, GetHandleId(t), data)
    call TimerStart(t, 6.28, false, function callback)
    set t = null
endfunction

to this:
JASS:
function callback takes nothing returns nothing
    local integer data = Timer.get_expired_data()
    // use data
endfunction
function use_timer takes nothing returns nothing
    local integer data = 42
    call Timer.start(data, 6.28, function callback)
endfunction
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
and horrible API naming conventions.
Have to give you that, but I created my own Timer system in struct syntax because I like OOP syntax.
Difference is that I dont upload it :D

@Aniki
The naming convention that I use is:
camelCase names for fields(and parameters), functions and globals.
UPPER_CASE_TEXT (dunno the name, but I would name it BIG_SNAKE_CASE) for constants.
And PascalCase for classes/structs.
(There isnt much else.)

"get_expired" and "get_expired_data" is snake_case which isnt really bad, but imho it is ugly... as is the opinion of probably about 90% of the programmers.
 
@Aniki: The extra "Pause Timer" line is useless in the expiration callback. You only have to pause when you destroy a timer that is currently running.

So, yeah, all that is added in manually running a timer is leak prevention. However, even those extra lines don't matter much as soon as you want to run the timer again in the callback. Which happens quite frequently from my experience.
Also, when using a hashtable instead of a timer system, I also have to freedom to directly attach whatever data type I want to it without having to encapsule everything in a struct.
Structs can be great, but in many cases, you want quick and easy timer attachment of only very few variables. Having to instanciate everything with structs sucks.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Imho that's a really terrible practice to rely on a bug, when you have a decent reliable alternative.
Here this timer trick bug VS hashtable usage.

Even if i recon that is very unlikely to happen, if the bug is fixed, then your map is broken.
 
Level 13
Joined
Nov 7, 2014
Messages
571
if the bug is fixed, then your map is broken.

While there are at it (fixing bugs), they could just as well add Get|SetTimerUserData, or better yet, make an AccurateTriggerSleepAction.

PS: even "cooler" way of attaching struct instances to timers (from this thread):
JASS:
library Timers

//# +nosemanticerror
private function int_to_real takes integer i returns real
    return i
endfunction

//# +nosemanticerror
private function real_to_int takes real r returns integer
    return r
endfunction

private function clean_real takes real r returns real
    return r
    return 0.0 // prevent jasshelper from inlining
endfunction

private function clean_int takes integer i returns integer
    return i
    return 0 // prevent jasshelper from inlining
endfunction

private function i2r takes integer i returns real
    return clean_real(int_to_real(i))
endfunction

private function r2i takes real i returns integer
    return clean_int(real_to_int(i))
endfunction

struct Timer extends array
    readonly static integer max_count = 0 // the maximum number of timers we've ever had ticking
    readonly static integer curr_count = 0 // the current number of timers ticking

    private static Timer head = 0 // a free list of timers
    private Timer next

    private timer t
    integer data
    private real timeout

    static method start takes integer user_data, real timeout, code callback returns Timer
        local Timer this

        if head != 0 then
            set this = head
            set head = head.next

        else
            set max_count = max_count + 1
            if max_count > 8190 then
static if DEBUG_MODE then
                call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: could not allocate a Timer instance|r")
endif
                return 0
            endif

            set this = max_count
            set this.t = CreateTimer()
        endif
        set curr_count = curr_count + 1

        set this.next = 0
        set this.data = user_data
        set this.timeout = i2r( r2i(timeout) / 8192 * 8192 + this )

        call TimerStart(this.t, this.timeout, false, callback)

        return this
    endmethod

    method stop takes nothing returns nothing
        if this == 0 then
static if DEBUG_MODE then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: cannot stop null Timer instance|r")
endif
            return
        endif

        if this.next != 0 then
static if DEBUG_MODE then
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 1000.0, "|cffFF0000[Timer] error: cannot stop Timer(" + I2S(this) + ") instance more than once|r")
endif
            return
        endif

        set curr_count = curr_count - 1

        call TimerStart(this.t, 0.0, false, null)
        set this.next = head
        set head = this
    endmethod

    method pause takes nothing returns nothing
        call TimerStart(this.t, 0.0, false, null)
    endmethod

    static method get_expired takes nothing returns Timer
        local integer i = r2i( TimerGetTimeout(GetExpiredTimer()) )
        return Timer( i - i / 8192 * 8192 )
    endmethod

    static method get_expired_data takes nothing returns integer
        local Timer t = get_expired()
        local integer data = t.data
        call t.stop()
        return data
    endmethod

    method restart takes code callback returns nothing
        call TimerStart(this.t, this.timeout, false, callback)
    endmethod

endstruct

endlibrary
 
Level 9
Joined
Jun 21, 2012
Messages
432
I found this Timer Exploit from xgm, seems to be the same:
JASS:
function TimerStartEx takes timer whichTimer, real period, boolean isPeriodic, code handlerFunc, integer userData returns nothing
   call TimerStart(whichTimer, I2R(userData), false, null) // timer exploit, xgm 2007
    call PauseTimer(whichTimer)
    call TimerStart(whichTimer, period, isPeriodic, handlerFunc ) // start timer
endfunction
function TimerGetUserData takes timer whichTimer returns integer
    return R2I(TimerGetRemaining(whichTimer )+0.5) // get user data from timer
endfunction
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
It's because hashtables are terrible. Having a built-in, easy to use, way of mapping from any variable type to any variable type, is totally a useless feature, and it's slower by about 0.00000001 seconds (number taken out of my behind) than arrays, and you might end up with buckets filling up and collisions, since any decent Jass code creates millions of keys, and so on.

So yeah, never use hashtables - old hacks that only exist because hashtables did not exist back then are way better.
 
Last edited:
  • Like
Reactions: AGD
Level 19
Joined
Dec 12, 2010
Messages
2,069
It's because hashtables are terrible. Having a built-in, easy to use, way of mapping from any variable type to any variable type, is totally a useless feature, and it's slower by about 0.00000001 seconds (number taken out of my behind) than arrays, and you might end up with buckets filling up and collisions, since any decent Jass code creates millions of keys, and so on.

So yeah, never use hashtables - old hacks that only exist because hashtables did not exist back then are way better.
literally u wot mate?
handle ID is enough unique for any sane person, can't see how global IDs used for array is ANY better, yet even more restricted in terms of variety.
and nope, hashtables AREN'T fucking slow. stop spreading this misinformation. it's slower than native types, sure, but still inhumanly fast and totally cover ANY usage you may come with.
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
Sorry, if I came across as serious, let me say I was definitely not, and everyone should definitely use hashtables when object->object mapping is needed.

Why have a "timer system", when you can simply use your timer as a key in a hashtable, and store anything you want without any kind of restrictions, is beyond me.

And I just keep seeing this, people that write code which eventually needs some mapping that is not integer->object (aka arrays), but will force themselves to use arrays, because so and so said hashtables are slow, or some other silly reason.

Hashtables, also known as maps, are the norm anywhere outside of Jass, when you want to map data with the keys not being integers. Only here, in the world of special snowflakes that require code that is 100% optimal, and so name all of their variables with 1 or 2 letters, this practice is left alone (and don't for one second think their code is optimal, it just looks like a pile of spaghetti instead).
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
Having a built-in, easy to use, way of mapping from any variable type to any variable type, is totally a useless feature,
and it's slower by about 0.00000001 seconds (number taken out of my behind) than arrays,
and you might end up with buckets filling up and collisions,
How can that not be sarcastic?

Still, I wont use hashtables unless it is logical to use one.
Aka, value storage with 2 keys.
 

Deleted member 219079

D

Deleted member 219079

Hashtables are useful when fiddling with id's :)

I think it was found that hashtable lookup is 6 times slower than array one. Scary shit.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Why have a "timer system", when you can simply use your timer as a key in a hashtable, and store anything you want without any kind of restrictions, is beyond me.
In my opnion using structs results in smaller, more readable and faster scripts.

Suppose we wanted to write a simple damage over time spell, assume this block is above all examples that follow:
JASS:
globals
    private constant boolean MELEE_ATTACK = false
    private constant boolean RANGE_ATTACK = true
    private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL // spell
    private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_UNIVERSAL // ignore armor value
    private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals

We could use TriggerSleepAction with this spell but in general we wouldn't be able to because its not accurate enough.
JASS:
// wait - an accurate TSA
function damage_over_time takes unit caster, unit target, real damage, real delay, integer damage_apply_count returns nothing
    loop
        exitwhen damage_apply_count <= 0
        set damage_apply_count = damage_apply_count - 1
        wait delay
        call UnitDamageTarget(caster, target, damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
    endloop
endfunction

Here's an example implementation using structs and the timer library from the first post of this thread:
JASS:
struct DamageOverTime
    unit caster
    unit target
    real damage
    real delay
    integer damage_apply_count

    static method do_damage takes nothing returns nothing
        local Timer t = Timer.get_expired()
        local DamageOverTime this = t.data

        if this.damage_apply_count <= 0 then
            call t.stop()
            call this.destroy()
            return
        endif
        set this.damage_apply_count = this.damage_apply_count - 1

        call UnitDamageTarget(this.caster, this.target, this.damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
        call t.restart(function thistype.do_damage)
    endmethod

    static method create takes unit caster, unit target, real damage, real delay, integer damage_apply_count returns thistype
        local thistype this = allocate()

        set this.caster = caster
        set this.target = target
        set this.damage = damage
        set this.delay = delay
        set this.damage_apply_count = damage_apply_count

        call Timer.start(this, delay, function thistype.do_damage)

        return this
    endmethod
endstruct

and for comparison an implementation using a hashtable:
JASS:
globals
    private hashtable ht = InitHashtable()
    private key kcaster
    private key ktarget
    private key kdamage
    private key kdelay
    private key kdamage_apply_count
endglobals

private function do_damage takes nothing returns nothing
    local timer tt = GetExpiredTimer()
    local integer t = GetHandleId(tt)
    local unit caster = LoadUnitHandle(ht, t, kcaster)
    local unit target = LoadUnitHandle(ht, t, ktarget)
    local real damage = LoadReal(ht, t, kdamage)
    local integer damage_apply_count = LoadInteger(ht, t, kdamage_apply_count)

    if damage_apply_count <= 0 then
        call PauseTimer(tt)
        call DestroyTimer(tt)
        set tt = null
        set caster = null
        set target = null
        return
    endif
    // fields that we need to mutate over the course of the spell have to be resaved in ht
    call SaveInteger(ht, t, kdamage_apply_count, damage_apply_count - 1)

    call UnitDamageTarget(caster, target, damage, MELEE_ATTACK, RANGE_ATTACK, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
    set tt = null
    set caster = null
    set target = null
endfunction

function damage_over_time takes unit caster, unit target, real damage, real delay, integer damage_apply_count returns nothing
    local timer tt = CreateTimer()
    local integer t = GetHandleId(tt)

    call SaveUnitHandle(ht, t, kcaster, caster)
    call SaveUnitHandle(ht, t, ktarget, target)
    call SaveReal(ht, t, kdamage, damage)
    call SaveInteger(ht, t, kdamage_apply_count, damage_apply_count)

    call TimerStart(tt, delay, true, function do_damage)
    set tt = null
endfunction

Notice how we have to unpack the instance data in the do_damage function and resave all (in this spell 1) mutable fields.
Using structs the unpack is 1 line (local DamageOverTime this = t.data) and we simply mutate whichever field of the struct that we have to.
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
Hashtables are useful when fiddling with id's :)

I think it was found that hashtable lookup is 6 times slower than array one. Scary shit.
Hashtable vs array
Sorry, if I came across as serious, let me say I was definitely not, and everyone should definitely use hashtables when object->object mapping is needed.
nah didnt really bothered indepth reading, sry bruh
 
Level 29
Joined
Jul 29, 2007
Messages
5,174
How is using a hashtable related at all to being able to attach struct indices? You can attach anything to a hashtable, that's the whole point of it.
It's like you purposefully wrote longer code for hashtables to prove that they require longer code.

I have nothing to say about performance. This topic is too stupid to talk about in this context.

It's really simple, I don't get why it's such a problem for people in this community to understand.
When you want to map some key to a value, what type is your key, and is it going to be consecutive?
  1. If the type is integer and consecutive, then you use arrays.
  2. If the type is integer and non-consecutive, then it's a case by case situation (but you should probably use hashtables).
  3. If the type is anything but an integer, you use a hashtable.

This isn't specific to Jass or anything, this is just common sense - if you want to map from some object to some object, then you use structures that were meant to do just that...
 
Last edited:
Level 24
Joined
Aug 1, 2013
Messages
4,657
That recursion doesnt really make sense though.

My rules of choosing between a various storage stuff is pretty simple:
If I need to have a value with 2 keys, then I use a hashtable.
If I need a list of values with 1 key, then I use a linked list under that key.
If I need a value with 1 key, then I use arrays (which are ussually done in terms of structs).
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Back to topic, an alternative version, like good old TimerUtils using a stock but just structified. What do you guys think?
JASS:
library Timer


    private keyword Init

    struct Timer extends array

        private static integer startHandle
        private static thistype alloc
        private timer timers
        private timer T
        private thistype recycler
        thistype data

        debug private static method debug takes string msg returns nothing
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00[Timer]|R " + msg)
        debug endmethod

        static method operator new takes nothing returns thistype
            set alloc = thistype(0).recycler
            set thistype(0).recycler = alloc.recycler
            set alloc.T = alloc.timers
            debug call debug("Allocating Timer [" + I2S(alloc) + "]")
            return alloc
        endmethod

        static method newEx takes integer data returns thistype
            set new.data = data
            return alloc
        endmethod

        method free takes nothing returns nothing
            debug if .T == null then
                debug call debug("ERROR: Attempt to double free Timer [" + I2S(this) + "]")
                debug return
            debug endif
            set .recycler = thistype(0).recycler
            set thistype(0).recycler = this
            call PauseTimer(.T)
            set .T = null
            set .data = 0
            debug call debug("Freeing Timer [" + I2S(this) + "]")
        endmethod

        method start takes real timeout, boolean periodic, code c returns nothing
            call TimerStart(.T, timeout, periodic, c)
        endmethod

        method stop takes nothing returns nothing
            call TimerStart(.T, 0, false, null)
        endmethod

        method pause takes nothing returns nothing
            call PauseTimer(.T)
        endmethod

        method resume takes nothing returns nothing
            call ResumeTimer(.T)
        endmethod

        static method operator expired takes nothing returns thistype
            return GetHandleId(GetExpiredTimer()) - startHandle
        endmethod

        implement Init

    endstruct

    private module Init
        private static method onInit takes nothing returns nothing
            local thistype this = thistype(1)
            set thistype(1).timers = CreateTimer()
            set startHandle = GetHandleId(thistype(1).timers) - 1
            set thistype(0).recycler = 1
            set thistype(1).recycler = 2
            set thistype(8191).recycler = 0
            loop
                set this = this + 1
                set .timers = CreateTimer()
                set .recycler = this + 1
                exitwhen this == 8190
            endloop
            debug call debug("TimerStruct ready!")
        endmethod
    endmodule


endlibrary
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
What do you guys think?

I think preloading 8K timers is an overkill for most/all maps.
I think it breaks save games set thistype(8191).recycler = 0.

I think it could lead to bugs:
JASS:
Suppose we use a Timer(1), say for counting the number of seconds since the game started.
And during the course of the game we cycle trough all the Timers and back to Timer(1) and we use it for something else.
This would stop our counting of seconds since the game started.

Maybe we could use a dedicated timer for the above exampe but this "bug" could happen if
the user uses a Timer for a period longer than it takes for us (static method operator new) to cycle back to it.

PS: you don't need both?:
JASS:
    private timer timers
    private timer T
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Maybe we could use a dedicated
timer
for the above exampe but this "bug" could happen if
the user uses a Timer for a period longer than it takes for us (
static method operator new
) to cycle back to it.
When cycling back, allocated Timers will be skipped.No, it will not cycle back unless all the timers are currrently allocated, which is very unlikely.
JASS:
        static method operator new takes nothing returns thistype
            set alloc = thistype(0).recycler
            set thistype(0).recycler = alloc.recycler
            //...
        endmethod

        method free takes nothing returns nothing
            set .recycler = thistype(0).recycler
            set thistype(0).recycler = this
            //...
        endmethod

you don't need both?:
I used two in order for deallocated Timer instances to be unusable by users.
JASS:
        method start takes real timeout, boolean periodic, code c returns nothing
            call TimerStart(.T, timeout, periodic, c)
        endmethod

        method stop takes nothing returns nothing
            call TimerStart(.T, 0, false, null)
        endmethod

        method pause takes nothing returns nothing
            call PauseTimer(.T)
        endmethod

        method resume takes nothing returns nothing
            call ResumeTimer(.T)
        endmethod
result is
JASS:
local Timer t = Timer.new
call t.pause()
call t.free()
call t.pause()//does nothing
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
Did not see the free method ;D... thus my rambling about "cycling back" to a Timer.

I used two in order for deallocated Timer instances to be unusable by users.
Well if you really want to prevent use after free you might as well inform the user instead of silently doing nothing (similarly to how the free method does it) .

I still prefer to lazily create as much timers as needed rather than preload 8K.
In my opinion the speed difference between retrieving the data from a preloaded timer and non-preloaded one is negligible.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Well if you really want to prevent use after free you might as well inform the user instead of silently doing nothing (similarly to how the free method does it) .
Yes, I guess it's better

I still prefer to lazily create as much timers as needed rather than preload 8K.
In my opinion the speed difference between retrieving the data from a preloaded timer and non-preloaded one is negligible.
I prefer to just lower the amount, 500 would be enough I guess =) or better I'll put it in user configuration.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
The 8k initial timers are because of how his timer -> Timer typecast is designed.
Aka, the timer handles are all within x and x + 8192. Then he substracts the handle id by x and get a number between 0 and 8192.

In this case (to avoid using a hashtable), you could do it with the pause trick from the original post.
Then the handles do not have to be in line.
(If you dont care about avoiding hashtables, then you can use a hashtable instead.)
 
Status
Not open for further replies.
Top