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

Array Number of Expired Timer?

Status
Not open for further replies.
Level 15
Joined
Nov 30, 2007
Messages
1,202
JASS:
private function Expires takes nothing returns nothing
    local integer x
    call BJDebugMsg("Timer: "I2S(x) " has ended...") 
    call DestroyTimer(Timer[x])
endfunction

    set Timer[f] = CreateTimer() 
    call TimerStart(Timer[f], 30., false, function Expires)

How do I know the array number of a expired timer?
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
1)You can loop through all Timer and check if expired timer equal to Timer

2)You can use something to attach data to timer.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
call SaveInteger(hash, GetHandleId(CreateTimer()), 0, yourInteger)
-->
call LoadInteger(hash, GetHandleId(GetExpiredTimer()), 0)

That's common way for attaching something to a timer.

Exactly what I was looking for, thanks! Can you create a timer in the SaveInteger( .... ) ? It looks like this at the moment:

JASS:
    set Timer[f] = CreateTimer()
    call TimerStart(Timer[f], 45, false, function Expires)
    call SaveInteger(hash, GetHandleId(Timer[f]), 0, f)

Also I read somewhere that timers have to be paused before they are destroyed, is that true and if so, why?
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
I also read periodic timers need to be paused before destroyed long time ago because sometimes they expire and crash game even you destroy them.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
I also read periodic timers need to be paused before destroyed long time ago because sometimes they expire and crash game even you destroy them.

But if they are not perodic i can skip that?

Also if I destroy the timer outside the expiration function, do I have to remove the data from the hash? Or will it just overwrite the old infromation if I createa a new one?
 
Last edited:
Your code looks ok. And yes, you also could write CreateTimer() into the GetHandleId() function, because the create function does return a valid type for parameter.

Yo, you don't need to pause one shot timers I think. The bug Ceday mentioned only can occur in a special case for periodic timers if I remember correctly. (have not found source now)

Edit:

For timer bug:
It appears that when a timer is destroyed in another function than the expiration function, it may still expire. The solution to this problem, is simply to pause the timer before destroying it.

Source: http://www.wc3c.net/showthread.php?t=80693
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
You can visit wc3c.net (down currently) or jass section (graveyard) for TimerUtils libraries. Use those as bastion of knowledge in regard to storing/retrieving data from timers. Their cores are pretty simple - there is more safety inside than actually code - and this might be just another valuable lesson for you.
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
There are 3 approaches.
1. Attach the index the timer is at to the timer handle and some constant in a hashtable. You can then look it up using the timer handle and the constant.
2. Compare every element of the array sequentially looking for the triggering timer. Once found stop looping and use found location as index.
3. Encode the index into the timeout of the timer as an insignificant duration. You can then retrieve the timeout when the timer expires and decode the index from it. This works because the floating point numbers used to represent timeout have data allocated for insignificant timing units. Obviously an error is added however for low period timers this is usually sub frame and can be ignored.

1 is currently the most popular method and is also probably the fastest. It came about after the type casting exploit was fixed and hashtables were added to WC3. It works perfectly for 1 to 1 mappings like you require but can be tricky for more complex mappings to avoid collision. Avoid using a data storage hash table (one that holds a lot of constant values) as that can degrade performance however there should be little problem sharing a hashtable with all abilities in the map as long as no collisions occur. Do remember that the hashtable entry has to be removed as part of the destructor process to avoid leaking mappings and potentially other problems.

2 is the lazy person approach. You can write it in a few lines of script with just simple key words and some integer operations and it will work perfectly correctly. The problem is that this approach is not scalable at all due to the O(n) complexity of linear search so can cause performance problems in speed critical code. However if multiple instances are an exception (99.9% of time is a single instance, occasionally only 2 etc) then this approach might be faster than method 1.

3 is an old hacky approach used by people like Vexorian back in the early days of WC3 during the time of the type casting exploit. Since gamecaches were used for their hashtable like functionality via a series of custom function calls (such as handlevars) and produced strings that could potentially be very unique people tried to avoid them where possible. This approach became common place for timers and was used by various timer tools for a long time. It seems to have been mostly obsoleted by hashtables since they do not introduce timing errors and probably have better performance.

I also read periodic timers need to be paused before destroyed long time ago because sometimes they expire and crash game even you destroy them.
The game does not crash as far as I am aware of. What happens is that destroying a timer does not unschedule the next execution of the callback so a destroyed timer will appear to run 1 last time. Obviously the expired timer will be null, this will propagate null and error values through the function resulting in strange behaviour and potentially a crash due to passing illegal parameters to certain natives (eg modifying a null unit).

Mostly you destroy a timer after it expires. Since one shot timers have already expired, there is no schedule for future execution at the time of destruction. On the other hand a periodic timer is scheduled for an execution in the future so when you destroy it, it will run one final time.

The solution is obvious. Pause the timer, forcing it to be unscheduled before destroying it. It should be noted that WC3 periodic timers are incomplete and very buggy. Pausing a periodic timer causes it to lose the periodic flag and become a single shot timer. Using the inbuilt save/load system will cause all periodic timers to become single shot. For these reasons it is often better to use single shot timers and restart them every time they expire, it also solves the pausing problem.

Instead of destroying timers, they can be "recycled" as part of a green coding style system. This prevents their handle indexes from being subject to reference counter leaks from the local handle bug saving you from nulling any local handles. It does pose a possible leak hazard in the case of once per session spike timer usage. For this reason you can often forget about such a system and simply just take the effort to null local timers.

Also if I destroy the timer outside the expiration function, do I have to remove the data from the hash? Or will it just overwrite the old infromation if I createa a new one?
It would be good practice to remove the data unless you are absolutely certain that it will be overwritten soon. Do remember that the handle index objects are assigned is allocated from a generic table with few restrictions so it may be possible for a handle index that was once used for a timer to now be a unit.
 
Status
Not open for further replies.
Top