One map, one Timer

Jass Theory & Questions

Not sure if this should be here or in Jass Theory. But anyway.

I have tried to see if it is possible to use just one timer for one map (in my case; all: attackfunctions, timed effects, spells, calculation for time before a projectile reaches its destination etc with just one timer) and got inspired by the (not as efficient as they say) KT2. Now, KT2 have been successfully used in a number of things, but it could not handle the attacked-triggered functions well at all. So I tried my own way to make something using the KT2 ideas (tick-periods with a period time of 0.00125 seconds and register functions as triggers in initialisations and so on) However, My idea was using a loop inside the function triggered by the timer to find the first function that should ran, then let that function call a continuer to check for next etc until al functions that should run at the moment have been executed. Check code

Code (jass) Select


/*Don't use/check this code, I got a WAY faster one further down in this thread*/
library TimeralaRV
globals
    constant timer SMALLTIMER = CreateTimer()
    constant timer LARGETIMER = CreateTimer() /*This timmer is added due to experiements with one low-freqency timer and one high-freqency timer*/
    constant integer SMALLTIMERARRAY = 80
    constant integer LARGETIMERARRAY = 180
    constant real SMALLTIMEOUT = 0.00125
    constant real LARGETIMEOUT = 0.2
    integer SmallTimerWhichArray = 0
    integer LargeTimerWhichArray = 0
    boolean array SmallTimerArrayUsed[SMALLTIMERARRAY]
    integer array SmallTimerArrayTicksLeft[SMALLTIMERARRAY]
    trigger array SmallTimerArrayTrigger[SMALLTIMERARRAY]
    unit array SmallTimerArrayDealer[SMALLTIMERARRAY]
    unit array SmallTimerArrayReci[SMALLTIMERARRAY]
    boolean array LargeTimerArrayUsed[LARGETIMERARRAY]
    integer array LargeTimerArrayTicksLeft[LARGETIMERARRAY]
    trigger array LargeTimerArrayTrigger[LARGETIMERARRAY]
endglobals

function TimeRunSmall takes nothing returns nothing
local integer i = 0
local boolean b = FALSE
if SmallTimerArrayTicksLeft[0] == 1 then
    call TriggerExecute(SmallTimerArrayTrigger[0])
    set SmallTimerArrayTicksLeft[0] = 0
    set SmallTimerArrayUsed[0] = FALSE
else
    if SmallTimerArrayTicksLeft[0] != 0 then
        set SmallTimerArrayTicksLeft[0] = SmallTimerArrayTicksLeft[0] - 1
    endif
    loop
        exitwhen b == TRUE
        set i = i + 1
        if SmallTimerArrayTicksLeft[i] == 1 then
            set SmallTimerWhichArray = i
            call TriggerExecute(SmallTimerArrayTrigger[i])
            set SmallTimerArrayTicksLeft[i] = 0
            set SmallTimerArrayUsed[i] = FALSE
            set b = TRUE
        else
            if SmallTimerArrayTicksLeft[0] != 0 then
                set SmallTimerArrayTicksLeft[i] = SmallTimerArrayTicksLeft[i] - 1
            endif
            if i > SMALLTIMERARRAY then
                set b = TRUE
            endif
        endif
    endloop
endif
endfunction


function NextFunction takes nothing returns nothing /* A bit simplified, in reality I would need a simple way of determine if it should use the small or the large timer, but it is not relevant for the moment*/
local integer i = SmallTimerWhichArray + 1
local boolean b
if SmallTimerArrayTicksLeft[i] == 1 then
    call TriggerExecute(SmallTimerArrayTrigger[0])
    set SmallTimerArrayTicksLeft[i] = 0
    set SmallTimerArrayUsed[i] = FALSE
else
    if SmallTimerArrayTicksLeft[i] != 0 then
        set SmallTimerArrayTicksLeft[i] = SmallTimerArrayTicksLeft[i] - 1
    endif
    loop
        exitwhen b == TRUE
        set i = i + 1
        if SmallTimerArrayTicksLeft[i] == 1 then
            set SmallTimerWhichArray = i
            call TriggerExecute(SmallTimerArrayTrigger[i])
            set SmallTimerArrayTicksLeft[i] = 0
            set SmallTimerArrayUsed[i] = FALSE
            set b = TRUE
        else
            if SmallTimerArrayTicksLeft[0] != 0 then
                set SmallTimerArrayTicksLeft[i] = SmallTimerArrayTicksLeft[i] - 1
            endif
            if i > SMALLTIMERARRAY then
                set b = TRUE
            endif
        endif
    endloop
endif
endfunction

endlibrary

However, when the NextFunction code is executed, the game shuts of. I don't know if it just is a protection against looping or if it actually creates an infinitive-loop here somewhere..

If someone have another way of using a singletimer for everything, or could tell me that their efficiency ain't much to talk about, I would like to hear ^^ (:

This topic isn't really a superimportant one for me, it's more a theory-question in that aspect.
This approach is very good in fact, because you reduce the handle count considerably. But this can bring issues in relation to the chances to have running at the same time several elements.

For example in my project PoC,  I did this approach and I had in some cases lags due too many instances happening at the same time (AI vs AI attacking each other with full army) this implied to use several timers (3 - 6) timers running at the same time distributing the load of the custom spells.

I separated them in this way:

1 timer for stuff with low frequency (period of 0.5s)
1 timer for hero abilities (high frequency)
1 timer for unit abilities (high frequency)

The load of the game was reduced.
Hmm, I dunno man, but don't you think it will just cause the game to be slow?
No, I run in my map 6 timers during all the game. Waht it makes slow the game is the amount of code running, like looping a big aount of data.

I remember when I used timerutils and from time to time the game got slowed, that's because it generates a lot of timers running at the same time. Timerutils works fine in AOS or games where custom spells and system not exceed more than 100 events or codes running at the same time.
I would really like to see the code of that function, cause, as said in another thread by me, I am using a jass-scripted attackfunction, which means that it have to handle the max-number of units on the map hitting a target at the same time (10players with a max of 12units inc. summons + 2computerplayers in dota or ts style, to be sure that I never reach the max-amount, I estimate about 200 timers would be needed JUST FOR THE ATTACKS in my current approach. With that, I estimated a max of about 150-200 timers for the spells/timed effects/some other misc stuff. Now, it have to be a very massive thing when all timers would be active at the same time, but still, 300-400 timers running can't be good on performance...

Now, as I said, by some reason, the wc3 wants to shut down if I register more than one attackfunction to the timer, probably cause it recognises some kind of loop, cause it SHOULD not run the exact same function with the exact same data in all infinity, but, well, it sure looks so..
You PoC aren't protected right? Would it be ok if I downloaded it and took some inpiration from your time-functions? (:

Off-topic question; It seems to me that you, Moyack, uses struct quite a lot, now, what I wonder is, are they in some way faster than array-variables, or do u just use them to simplifie code for you?
Quote from: rvonsonsnadtz on February 03, 2013, 12:32:53 PM
I would really like to see the code of that function, cause, as said in another thread by me, I am using a jass-scripted attackfunction, which means that it have to handle the max-number of units on the map hitting a target at the same time (10players with a max of 12units inc. summons + 2computerplayers in dota or ts style, to be sure that I never reach the max-amount, I estimate about 200 timers would be needed JUST FOR THE ATTACKS in my current approach. With that, I estimated a max of about 150-200 timers for the spells/timed effects/some other misc stuff. Now, it have to be a very massive thing when all timers would be active at the same time, but still, 300-400 timers running can't be good on performance...
Definitely. This is the code I use in my project PoC to distribute the game load:

Code (jass) Select
library Indexor2 initializer init requires MicroTable, Alloc
// the Alloc requirement is just to keep properly ordered the libraries, actually this library
// does not need Alloc, but the dependant libraries does.
globals
    private constant key A
    private constant integer Max = 5
endglobals

struct Indexor2 extends array
    timer t
    trigger tr
    real period
    integer count
    thistype next
    static integer counter = 1
    private static method evaluate takes nothing returns nothing
        call TriggerEvaluate(thistype(GetData(GetExpiredTimer(), A)).tr)
    endmethod
   
    static method create takes real period returns thistype
        local thistype this = thistype(thistype.counter)
        set thistype.counter = thistype.counter + 1
        set this.t = CreateTimer()
        set this.tr = CreateTrigger()
        set this.period = period
        set this.count = 0
        call StoreData(this.t, A, integer(this))
        call TimerStart(this.t, period, true, function thistype.evaluate)
        return this
    endmethod
   
    method AddCode takes code c returns nothing // this code should return a boolean to keep consistency
        if .count > Max then
            if .next == 0 then
                set .next = thistype.create(.period)
            endif
            call .next.AddCode(c)
        else
            set .count = .count + 1
            call TriggerAddCondition(.tr, Condition(c))
            debug call BJDebugMsg( "Indexor2 index: " + I2S(this) + " events added: " + I2S(.count))
        endif
    endmethod
endstruct

globals // here we'll set the indexor we'll use in the game.
    Indexor2 UnitIndexer // Manages units effects
    Indexor2 HeroIndexer // Manages Heroes effects
    Indexor2 TEIndexer   // Manages Timed Effects
endglobals

private function init takes nothing returns nothing
    set UnitIndexer = Indexor2.create(GetRandomReal(0.060, 0.069))
    set HeroIndexer = Indexor2.create(GetRandomReal(0.060, 0.069))
    set TEIndexer   = Indexor2.create(0.1)
endfunction

endlibrary


QuoteNow, as I said, by some reason, the wc3 wants to shut down if I register more than one attackfunction to the timer, probably cause it recognises some kind of loop, cause it SHOULD not run the exact same function with the exact same data in all infinity, but, well, it sure looks so..
You PoC aren't protected right? Would it be ok if I downloaded it and took some inpiration from your time-functions? (:
it seems like an infinite loop you got there :P

QuoteOff-topic question; It seems to me that you, Moyack, uses struct quite a lot, now, what I wonder is, are they in some way faster than array-variables, or do u just use them to simplifie code for you?
Structs are arrays with nicer representation. Doing a struct with 3 components is compiled as an array set.
I have created a efficient way of getting either a integer or a struct (atleast, it should work with a struct since they are just integers)

Code (jass) Select
library TickTack

/*Timefunction "TickTack" by RVonSonSnadtz

This is a simple, yet very effective timerfunction. However, it can't by itself handle things longer than 2seconds, so that is something you should learn to make in the function next. Give a PM at [url=http://www.wc3jass.com]www.wc3jass.com[/url] if you need help with that.

Pros: Very fast, safe and stable, it doesn't change much even if you would throw it ALOT of stuff to do.

Cons: All functions ran with this function needs to be pre-registered as a condition to a trigger. Best is to use a constant trigger for all functions that you know might run in this script. Also, it can't by itself handle stuff longer than 2secs, but on the other hand is that a simple issue to come around (:

This is version 1.0, hopefully I found this effective enough (: */

globals
    constant timer HIGHQ_TIMER = CreateTimer() //Needs to be started at MapInit using the HIGHQ_TIMEOUT, TRUE to periodic and the function HIGHQ_CALL
    constant real HIGHQ_TIMEOUT = 0.00125 //highq stands for high-freqency, with other words, the short intervall functions
    constant integer HIGHQ_MAXTICKS = 1601 //1601 works for everything <= 2secs, 801 for <=1sec. A higher value lessens the CPU-usage as the long-time functions doesn't need to change their calculations-integer as often, but it increases the RAM-usage because it will store more variables in the hashtable. Nulling the values would be quite ineffective.
    constant real HIGHQ_MAXROOF =  HIGHQ_TIMEOUT * I2R(HIGHQ_MAXTICKS) //Should NEVER be changed unless you know what you are doing. Only using for debugging purpose.
    constant hashtable HIGHQ_TABLE = InitHashtable() //The very heart of this system
    private integer array highq_nroffunctions[HIGHQ_MAXTICKS]
    private integer highq_whichchild = 0
    private integer highq_tick = 0
   

endglobals

function HighQ_Call takes nothing returns nothing //Needs to be used with the timer on startup
if highq_nroffunctions[highq_tick] > 0 then
    call TriggerEvaluate(LoadTriggerHandle(HIGHQ_TABLE, highq_tick, 0))
    set highq_whichchild = 0
endif
if highq_tick >= HIGHQ_MAXTICKS then
    set highq_tick = 0
else
    set highq_tick = highq_tick + 1
endif
endfunction

public function GetData takes nothing returns integer
local integer i = highq_whichchild
local integer data
if i < highq_nroffunctions[highq_tick] then
    set highq_whichchild = i + 1
    call TriggerEvaluate(LoadTriggerHandle(HIGHQ_TABLE, highq_tick, highq_whichchild))
    set highq_nroffunctions[highq_tick] = highq_nroffunctions[highq_tick] - 1
else
    set highq_nroffunctions[highq_tick] = 0
endif
return LoadInteger(HIGHQ_TABLE, highq_tick, i)
endfunction


public function Register takes real time, trigger func, integer data returns nothing
local integer ticks
local integer i
if time < HIGHQ_MAXROOF then //This If/then/else - function is solely used for debugging. If you are CERTAIN that ALL functions using this is below or at the 2sec-limit, then you could remove this if and other stuff marked as For Debugging
    set ticks = R2I(time / HIGHQ_TIMEOUT) + highq_tick
    if ticks > 1600 then
        set ticks = ticks - HIGHQ_MAXTICKS
    endif
    set i = highq_nroffunctions[ticks]
    call SaveTriggerHandle(HIGHQ_TABLE, ticks, i, func)
    call SaveInteger(HIGHQ_TABLE, ticks, i, data)
    set highq_nroffunctions[ticks] = i + 1
else //For debugging
    debug call BJDebugMsg("A function passed the 2sec limit, please check through the code") //For debugging
endif //For debugging
endfunction
endlibrary

The good thing here is that it seems fast and skips all loop-stuff, for highest possible speed. Second thing, that is both good and bad, is that it can't call ordinary functions, it needs to use already-made triggers with the function attached as a condition. And one last con: when calling GetData, you need to use the trigger that the executed function is attached to.
It seems fast to me, but there are probably more here who are better on this area of expertise


I realised that it is possible to just use one timer for everything, with the use of integers to recall the function after a second or 2 until it is time to expire, so this is the timer I think I will be going for (:

Any feedback on this would be nice (:

PS: I belive one of the biggest losses on performance coming from the KT2 are all extra trigger code.. So for me I have to create the functions that should be called as already made triggers so that I don't need to declare them more than once at the begining of the map.

(example:

Code (jass) Select

globals
constant trigger EFFECTEXPIRE = CreateTrigger()
endglobals

function EffectExpire takes nothing returns nothing
local SomeStruct data = TickTack_GetData() /* or just use the integer from it directly*/
/*do the stuff*/
endfunction

//===================================
//At the map-init, I will link the function with the trigger:

function InitTrig_Melee_Initialization takes nothing returns nothing
/* the ordinary mapinit-stuff and then:*/
    call TriggerAddCondition( EFFECTEXPIRE,Condition( function EffectExpire))
endfunction


Sure, this will lead to a small list, but in terms of RAM, this should actually be lesser than the peaks if you do this every time you needs to use a function (as one function then could be attached to several, temporary triggers that also are stored in variables = more process required)

This might be a bit overanalytic for many, but I want this map to work for alot of users, and then it needs it's speed ^^'

PS: The first code is not very fast, BUT still faster than KT2 when alot of things is going on, but on the other hand.. TimerUtils seems faster than KT2 when alot of things is going on..
Quote from: rvonsonsnadtz on February 15, 2013, 12:01:45 PM
I have created a efficient way of getting either a integer or a struct (atleast, it should work with a struct since they are just integers)

Code (jass) Select
library TickTack

/*Timefunction "TickTack" by RVonSonSnadtz

This is a simple, yet very effective timerfunction. However, it can't by itself handle things longer than 2seconds, so that is something you should learn to make in the function next. Give a PM at [url=http://www.wc3jass.com]www.wc3jass.com[/url] if you need help with that.

Pros: Very fast, safe and stable, it doesn't change much even if you would throw it ALOT of stuff to do.

Cons: All functions ran with this function needs to be pre-registered as a condition to a trigger. Best is to use a constant trigger for all functions that you know might run in this script. Also, it can't by itself handle stuff longer than 2secs, but on the other hand is that a simple issue to come around (:[/quote]So you need to set all the conditions at map init?? well I think it's not an issue.

[quote]This is version 1.0, hopefully I found this effective enough (: */

globals
    constant timer HIGHQ_TIMER = CreateTimer() //Needs to be started at MapInit using the HIGHQ_TIMEOUT, TRUE to periodic and the function HIGHQ_CALL
    constant real HIGHQ_TIMEOUT = 0.00125 //highq stands for high-freqency, with other words, the short intervall functions
    constant integer HIGHQ_MAXTICKS = 1601 //1601 works for everything <= 2secs, 801 for <=1sec. A higher value lessens the CPU-usage as the long-time functions doesn't need to change their calculations-integer as often, but it increases the RAM-usage because it will store more variables in the hashtable. Nulling the values would be quite ineffective.
    constant real HIGHQ_MAXROOF =  HIGHQ_TIMEOUT * I2R(HIGHQ_MAXTICKS) //Should NEVER be changed unless you know what you are doing. Only using for debugging purpose.
    constant hashtable HIGHQ_TABLE = InitHashtable() //The very heart of this system
    private integer array highq_nroffunctions[HIGHQ_MAXTICKS]
    private integer highq_whichchild = 0
    private integer highq_tick = 0
   

endglobals

function HighQ_Call takes nothing returns nothing //Needs to be used with the timer on startup
if highq_nroffunctions[highq_tick] > 0 then
    call TriggerEvaluate(LoadTriggerHandle(HIGHQ_TABLE, highq_tick, 0))
    set highq_whichchild = 0
endif
if highq_tick >= HIGHQ_MAXTICKS then
    set highq_tick = 0
else
    set highq_tick = highq_tick + 1
endif
endfunction

public function GetData takes nothing returns integer
local integer i = highq_whichchild
local integer data
if i < highq_nroffunctions[highq_tick] then
    set highq_whichchild = i + 1
    call TriggerEvaluate(LoadTriggerHandle(HIGHQ_TABLE, highq_tick, highq_whichchild))
    set highq_nroffunctions[highq_tick] = highq_nroffunctions[highq_tick] - 1
else
    set highq_nroffunctions[highq_tick] = 0
endif
return LoadInteger(HIGHQ_TABLE, highq_tick, i)
endfunction


public function Register takes real time, trigger func, integer data returns nothing
local integer ticks
local integer i
if time < HIGHQ_MAXROOF then //This If/then/else - function is solely used for debugging. If you are CERTAIN that ALL functions using this is below or at the 2sec-limit, then you could remove this if and other stuff marked as For Debugging
    set ticks = R2I(time / HIGHQ_TIMEOUT) + highq_tick
    if ticks > 1600 then
        set ticks = ticks - HIGHQ_MAXTICKS
    endif
    set i = highq_nroffunctions[ticks]
    call SaveTriggerHandle(HIGHQ_TABLE, ticks, i, func)
    call SaveInteger(HIGHQ_TABLE, ticks, i, data)
    set highq_nroffunctions[ticks] = i + 1
else //For debugging
    debug call BJDebugMsg("A function passed the 2sec limit, please check through the code") //For debugging
endif //For debugging
endfunction
endlibrary

The good thing here is that it seems fast and skips all loop-stuff, for highest possible speed. Second thing, that is both good and bad, is that it can't call ordinary functions, it needs to use already-made triggers with the function attached as a condition. And one last con: when calling GetData, you need to use the trigger that the executed function is attached to.
It seems fast to me, but there are probably more here who are better on this area of expertise
I've seen that you use a lot of public. I suggest to use public to genenate function names with suffixes. If you want a function public, just avoid any [lcode=jass]private[/lcode] or [lcode=jass]public[/lcode] keywords, and for private functions, just use [lcode=jass]private[/lcode]. Additionally the usage of hashtables is nice but abusing of them can get slow with huge amount of data. I haven't been to test it precisely, but I think we could do some tests about it. BTW, could you avoid the usage of tables using a trigger array?? I can ensure you arrays are way faster!!!


QuoteI realized that it is possible to just use one timer for everything, with the use of integers to recall the function after a second or 2 until it is time to expire, so this is the timer I think I will be going for (:

Any feedback on this would be nice (:
Definitely anything can be managed by one timer, the only issues is that this apporach is not effective in any kind of maps. For example in altered melee or footmen frenzy maps the huge amount of units and custom abilities can be a serious load and one timer moving hundrends of events at the same time can kill the process (that was my case in PoC). In AoS, RPG or Arenas, the usage of timer utils can be more than enough. So yes, saying this way is faster is true in some circumstances, and that's the reason why the best option is to do the code for your map based on public resources.

QuotePS: I belive one of the biggest losses on performance coming from the KT2 are all extra trigger code.. So for me I have to create the functions that should be called as already made triggers so that I don't need to declare them more than once at the begining of the map.

(example:

Code (jass) Select

globals
constant trigger EFFECTEXPIRE = CreateTrigger()
endglobals

function EffectExpire takes nothing returns nothing
local SomeStruct data = TickTack_GetData() /* or just use the integer from it directly*/
/*do the stuff*/
endfunction

//===================================
//At the map-init, I will link the function with the trigger:

function InitTrig_Melee_Initialization takes nothing returns nothing
/* the ordinary mapinit-stuff and then:*/
    call TriggerAddCondition( EFFECTEXPIRE,Condition( function EffectExpire))
endfunction


Sure, this will lead to a small list, but in terms of RAM, this should actually be lesser than the peaks if you do this every time you needs to use a function (as one function then could be attached to several, temporary triggers that also are stored in variables = more process required)

This might be a bit overanalytic for many, but I want this map to work for alot of users, and then it needs it's speed ^^'

PS: The first code is not very fast, BUT still faster than KT2 when alot of things is going on, but on the other hand.. TimerUtils seems faster than KT2 when alot of things is going on..
As I said before, that depends on the scenario you put the code. the more triggers you need to run at once in your maps, the better is to use one or few of them managing the whole system.
Ok, hmmm, I got some ideas about how to make it with just arrays, but I need to further investigate and figure about it..
Perhaps I could store the integer-data with just 2 integerarrays, instead of the hashtable. Or I could store all integer-data in a string-array, but since the cache-way of strings seems to be a bit "special" I think I should try to succed with the other version.

Edit: Perhaps the smartest way is again to use 2 timers, one high-freqent and one a bit lower (still quite high like 0.0125), and let them go for 400ticks (the high-freqency would go for a half-sec and keep 20funcs per tick, but it still feels for me like my system must be able to handle more, even if it wouldn't run that many functions in the average at every tick, 200units COULD (with a proxamity below 1%) hit their target at the same time.
Manage structs that extends array and it will work very nice for you. [lcode=jass]struct data extends array[/lcode] + Alloc is a very nice way to do the things and very efficient.

I've added to the resources Microtable so attaching structs is a piece of cake.

Hmmm, still many things that are obscured to me, like the "extends array"-thing.
And textmacro? What is that?
Btw, I got one other way to try to, so I will be back with one more try (:

EDIT: I was looking for a native function called "split", which I now saw was a custom-made function, and the struct-allocation seems faster than that...
Quote from: rvonsonsnadtz on February 19, 2013, 09:02:46 AM
Hmmm, still many things that are obscured to me, like the "extends array"-thing.
And textmacro? What is that?
Btw, I got one other way to try to, so I will be back with one more try (:
Entends array is a nice thing. The structs are compiled with a huge amount of data, which ha proven to be inefficient and slow. LEt me find a sample so you can understand.
Ok, thanks! (:
I did some research about the extend array- thing, and found a nice tutorial (by Nestharus at HiveWorksShop), which not only helped me understand what struct-extend array is, but also how to use it and most importantly; why it is so much better. I used the background-code generated by his way of approaching structs to store triggers and the data-integer in array-codes rather than the hashtable (however, the hashtable is still not out of the system, it is a easy way to store- and load the right index)

If you are right about the hashtable-thing, then this SHOULD be alot faster than the previous system (:  (if you would insist that I should remove the hashtable completely, then I will have some more work to do, but in the mean-time, I hope this works! (:  )


EDIT: No, way, I'm gonna remove the table completely, The code will be temporary gone, back soon with update ^^'
EDIT2: Here we go; no hashtables:
Code (jass) Select

library ChainTimer //By RVonSonSnadtz


globals
    constant timer HIGHQ_TIMER = CreateTimer() //Needs to be started at MapInit using the HIGHQ_TIMEOUT, TRUE to periodic and the function HIGHQ_CALL
    constant real HIGHQ_TIMEOUT = 0.00125 //highq stands for high-freqency, with other words, the short intervall functions
    constant integer HIGHQ_MAXTICKS = 1600 //The HighQ works for all functions below 2 sec
    private integer array highq_nroffunctions[HIGHQ_MAXTICKS]
    integer highq_whichchild = 0
    integer highq_tick = 0
    private trigger array highq_func
    private integer array highq_data
    private integer array highq_nextcall
    private integer array highq_whichcall
    integer TickTack_instanceCount= 0
    integer TickTack_recycle= 0
    integer array TickTack_recycleNext
    integer array TickTack_WhichtoCall

endglobals



function HighQ_Call takes nothing returns nothing
if highq_nroffunctions[highq_tick] > 0 then
    call TriggerEvaluate(highq_func[highq_whichcall[highq_tick]])
endif
if highq_tick >= HIGHQ_MAXTICKS then
    set highq_tick = 0
else
    set highq_tick = highq_tick + 1
endif
endfunction

function GetData takes nothing returns integer
local integer i = highq_data[highq_whichcall[highq_tick]]
local integer i2 = highq_nroffunctions[highq_tick] - 1
    set highq_whichcall[highq_tick] = highq_nextcall[highq_whichcall[highq_tick]]
    set TickTack_recycleNext[i] = TickTack_recycle
    set TickTack_recycle = i
    if i2 > -1 then
        set highq_nroffunctions[highq_tick] = i2
        call TriggerEvaluate(highq_func[highq_whichcall[highq_tick]])
    endif
return i
endfunction


function Register takes real time, trigger func, integer data returns nothing
local integer ticks = R2I(time / HIGHQ_TIMEOUT) + highq_tick
local integer index
    if ticks > HIGHQ_MAXTICKS then
        set ticks = ticks - HIGHQ_MAXTICKS
    endif
set highq_nroffunctions[ticks] = highq_nroffunctions[ticks] + 1
    if ( TickTack_recycle == 0 ) then
        set TickTack_instanceCount=TickTack_instanceCount + 1
        set index=TickTack_instanceCount
    else
        set index=TickTack_recycle
        set TickTack_recycle=TickTack_recycleNext[TickTack_recycle]
    endif
    set highq_nextcall[highq_whichcall[ticks]] = highq_whichcall[ticks]
    set highq_whichcall[ticks] = index
    set highq_func[index] = func
    set highq_data[index] = data
endfunction

endlibrary

(Added a testmap incase you want to check it out) I wonder if this really is efficient.. I mean, the doubble-array-codes might be a bit.. overkill? Well, I would like to know what you think Moyack ^^' )
Updated the code ^^'