HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

The EventTimer library

03-24-2007, 05:00 AM#1
Vexorian
Collapse JASS:
library EventTimer initializer init

    interface TimerEventListener
        boolean stop=false
        //If set to true it prevents the interface from running it, instead it cancels the event
        //It is never a good idea to just destroy the listener while it is active


        // If this method returns true, the system will destroy this object after executing it.
        method onExpire takes nothing returns boolean
    endinterface

    globals
        private timer T=null
        private timer gametime=null

        private TimerEventListener array events
        private real array expires
        private integer N=0
    endglobals


    private function init takes nothing returns nothing
        set T=CreateTimer()
        set gametime=CreateTimer()
        call TimerStart(gametime,1000000.0,false,null)
    endfunction

    private function expire takes nothing returns nothing
     local integer i
     local integer cleft
     local integer cright
        if (N==0) then
            debug call BJDebugMsg("Warning: Event Lib expires with no event")
            return
        endif

        // [0] has expired...
        if ((events[0].stop) or (events[0].onExpire()) ) then
            call events[0].destroy()
        endif

        // Reheap the stuff...
        set N=N-1
        set i=0
        if (N==0) then
            //nothing to do here.
            return
        endif

        loop
            set cright=(i+1)*2
            set cleft=cright-1
            exitwhen (cleft>=N)

            if (cright<N) then
                if ( expires[cleft] <= expires[cright]) then
                    if (expires[cleft] <=expires[N] ) then
                       set expires[i]=expires[cleft]
                       set events[i]=events[cleft]
                       set i=cleft
                    else
                        exitwhen true
                    endif
                else
                    if (expires[cright] <=expires[N] ) then
                       set expires[i]=expires[cright]
                       set events[i]=events[cright]
                       set i=cright
                    else
                        exitwhen true
                    endif
                endif
            elseif ( expires[cleft] <= expires[N] ) then
                set expires[i]=expires[cleft]
                set events[i]=events[cleft]
                set i=cleft
            else
                exitwhen true
            endif
        endloop
        set events[i]=events[N]
        set expires[i]=expires[N]


        call TimerStart(T, expires[0]-TimerGetElapsed(gametime),false, function expire)
    endfunction

    function AddTimeEvent takes TimerEventListener lt, real delay returns nothing
     local real t=delay+TimerGetElapsed(gametime)
     local integer i=N
     local integer p
        set N=N+1

        loop
            exitwhen i==0
            set p= (i-1)/2
            exitwhen (t >= expires[p])

            set expires[i]=expires[p]
            set events[i]=events[p]
            set i=p
        endloop
        set events[i]=lt
        set expires[i]=t

        call TimerStart(T, expires[0]-TimerGetElapsed(gametime),false, function expire)
    endfunction




endlibrary

Names are temporary If you can come up with better names I might change them.

I may have made a post about this system before. This is a new way to have timed events based in the pseudo OOP and has a couple of advantages over the old (CreateTimer + attach) usual one:
  • Only 2 timers created once and then no timer destruction: This removes any possibility of those odd bugs that come from messing with timers (like setting them to null?)
  • No need at all for H2I, nothing is based on pointers to handles, so it is safer that way as well.
  • No attaching at all, it is all about inheritance and the same struct gets the job of storing instance data and dealing with the event.

This new edition now also uses a heap as a priority queue so it is much faster than the old linear one. Logarithmic complexity to add events and when events expire. It is fast enough.

The disadvantage is that interfaces require an extra funciton call + TriggerEvaluate when an event expires, so it is in theory slower than alternatives. Besides of the process of preparing the next event in the queue.

But notice that dealing with DestroyTimer() / ReleaseTimer() , using H2I() and extra function calls to assist in that process (even TimerGetRemaining) could arguably be slower than that.

So it is probably slower than native timers, but the advantages are worth it and it is very good for those processes that are not too frequent. Declaring an struct might also be over-verbose sometimes...


Usage example:
Collapse JASS:
library TimedSpecialEffects requires EventTimer

    private struct expire extends TimerEventListener
        effect fx
        method onExpire takes nothing returns boolean
            call DestroyEffect( .fx)
            return true
        endmethod
    endstruct


    function AddSpecialEffectTimed takes string modelName, real x, real y, real duration returns nothing
     local expire e=expire.create()
        set e.fx=AddSpecialEffect(modelName,x,y)
        call AddTimeEvent(e, duration)
    endfunction

    function AddSpecialEffectTargetTimed takes string modelName, widget targetWidget, string attachPointName, real duration returns nothing
     local expire e=expire.create()
        set e.fx=AddSpecialEffectTarget(modelName,targetWidget,attachPointName)
        call AddTimeEvent(e, duration)
    endfunction

endlibrary


This is the alternative for random duration timers, for periodic timers (the animation ones typically 0.04 seconds) it is much better to keep an array of the objects to process and do so periodically, like what I did in this tutorial