HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

The new timer approach

02-07-2007, 01:20 PM#1
Vexorian
This is an utupy, sending delayed events with a lot of info WITHOUT attaching information to timers, without even creating new timers and without recycling them. (no H2I , no sorcery/abuse of bugs)

There are a lot of improvements to be done to it yet though.
snippet


Collapse JASS:
//===========================================================================
library newtimerthing initializer init

interface timerlistener
    // used to prevent a timer from calling the interface and just destroys it.
    boolean stop=false

    // returning true means repeat cycle...
    method OnExpire takes nothing returns boolean
endinterface



globals
    private timer T=null
    private timer zeroT=null
    private timer gametime=null
    private boolean paused=true

    private timerlistener array events
    private real array expires
    private integer N=0
    private integer st=0
    private timerlistener array zeroevents
    private timerlistener array zeroeventsback
    private integer zeroN=0

endglobals


private function expire takes nothing returns nothing
 local integer i
 local integer c
 local integer readd=0
 local real    time = TimerGetElapsed(gametime)

    if (N==st) then
        call PauseTimer(T)
        set paused=true
        //what?
        return
    endif
    if (events[st].OnExpire()) then
        call events[st].destroy()
    endif
    set c=0
    set st=st+1
    if (N==st) then
        set N=0
        set st=0
        call PauseTimer(T)
        set paused=true
    else
        call TimerStart(T,expires[st]-TimerGetElapsed(gametime),false,function expire)
    endif

endfunction

private function zeroexpire takes nothing returns nothing
 local integer i=0
 local integer n=zeroN
    //maybe too much defensive programming? who would use a 0. timer event in an expiration of a 0. timer event?, really?
    loop
        exitwhen i==n
        set zeroeventsback[i]=zeroevents[i]
        set i=i+1
    endloop
    set i=0
    set zeroN=0
    loop
        exitwhen i==n
        if (zeroeventsback[i].OnExpire()) then
            call zeroeventsback[i].destroy()
        endif
        set i=i+1
    endloop
    if (zeroN!=0) then
        call TimerStart(zeroT,0.,false, function zeroexpire)
    endif
endfunction

function MoveEventsToZero takes nothing returns nothing
 local integer i
 local integer j
    set i=st
    set j=0
    loop
        exitwhen i>=N
        set expires[j]=expires[i]
        set events[j]=events[i]
        set j=j+1
        set i=i+1
    endloop
    set N=j
    set st=0
endfunction

function AddExpirationEvent takes timerlistener tl, real delay returns nothing
 local real wanttime
 local integer i
 local integer j

    if (N==8191) then //Is this even possible?
        if (st!=0) then //move back
            call ExecuteFunc("MoveEventsToZero")
        else //panic
            debug call BJDebugMsg("WARNING: Timer events are full!!")
            return
        endif
    endif

    if (delay==0.) then
        //It is an "instant" timer!
        //these ones will work in a different way...
        if (zeroN==0) then
            call TimerStart(zeroT,0,false, function zeroexpire)
        endif
        set zeroevents[zeroN]=tl
        set zeroN=zeroN+1

    elseif (N==0) then
        //easy one
        call TimerStart(T,delay,false,function expire)
        set N=1
        set expires[0]=TimerGetElapsed(gametime)+delay
        set events[0]=tl
        set paused=false
    else
        //hard one,
        set wanttime=TimerGetElapsed(gametime)+delay
        if ((st!=0) and (wanttime<expires[st])) then
            //it just got easier...
            set expires[st-1]=wanttime
            set events[st-1]=tl
            set st=st-1

        elseif (wanttime>=expires[N-1]) then
            //Another easy case...
            set expires[N]=wanttime
            set events[N]=tl
            set N=N+1
        else
            //forced to do it the hard way -(
            set i=st
            set j=N
            loop
                exitwhen i==N
                if (expires[i]>wanttime) then
                    set j=i
                    exitwhen true
                endif
                set i=i+1
            endloop
            if (j==N) then
                set expires[N]=wanttime
                set events[N]=tl
                set N=N+1
            else
                set i=N
                loop
                    exitwhen i==j
                    set expires[i]=expires[i-1]
                    set events[i]=events[i-1]
                    set i=i-1
                endloop
                set expires[j]=wanttime
                set events[j]=tl
                set N=N+1
            endif

        endif



    endif

endfunction

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

endfunction


function GetGameTime takes nothing returns real
    return TimerGetElapsed(gametime)
endfunction

endlibrary



Something I must say is that it is a little slower than real timers, yes it is. With 100 active events it may even drop fps by 3.

Example: This is a temporary special effect function made with this approach

Collapse JASS:
struct timedfxtimer extends timerlistener
    effect fx
    method OnExpire takes nothing returns boolean
        call DestroyEffect( .fx)
        return true
     endmethod
endstruct

function AddSpecialEffectTargetWithDuration takes string fxpath, widget tar, string attach, real duration returns nothing
 local timedfxtimer t= timedfxtimer.create()
    set t.fx=AddSpecialEffectTarget(fxpath,tar,attach)
    call AddExpirationEvent(t, duration)
endfunction

globals
    constant string wantedeffect="units\\human\\GryphonRider\\GryphonRider.mdl"
endglobals

function Trig_Random_Timed_Effect_Actions takes nothing returns nothing
    call AddSpecialEffectTargetWithDuration(wantedeffect,GetTriggerUnit(),"overhead",5.0)
endfunction

//===========================================================================
function InitTrig_Random_Timed_Effect takes nothing returns nothing
    set gg_trg_Random_Timed_Effect = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Random_Timed_Effect, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER )
    call TriggerAddAction( gg_trg_Random_Timed_Effect, function Trig_Random_Timed_Effect_Actions )
endfunction


Note: For animation timers (those that usually take between 0.01 and 0.04) It is better to use this approach but without this set of functions, just have a unique timer that calls objects in an array and expires each 0.04? seconds. (Like the way missiles work in the caster system)

This was just for those delayed actions with random(as in any) periods of time.

It allows looping if you don't return true in the expire function (so it doesn't auto destroy the listener) and send the event back.
02-07-2007, 02:59 PM#2
grim001
How fast is this compared to attaching a struct to a timer using a global var system like CSData? If it is not faster I don't see what the advantage of this method is.
02-07-2007, 03:37 PM#3
Vexorian
Speed for a one-time deal in an expiration event that is not animation-related is not really a priority but well:

For adding an events:
It depends of the number of active events and if it is not a last-first event, worst case scenario it requires to go through the whole array and assign stuff when adding an event.

These are the things added for the expiration:
- array access
- Variable value increase.
- TriggerEvaluate (one function call it seems)
- struct destruction (optional (you would still have to destroy the struct if you used CSData, etc?))

CSData requires 2 function calls in total, 1 for H2I and the CSData call itself. It also requires an array access.

Gamecache requires H2I, I2S and gamecache.

Griffen's timerattach requires TimerGetRemaining, R2I and an operation and somehow also a function call unless you do stuff directly.

Pool based ones require an H2I afaik, so it is like CSData with out without a function call.

Notice that this also does not need to recycle/destroy the timer.

In other words: THERE IS NO CONCLUSSION

---
Speed was far from being the objective when making this system.
02-07-2007, 03:49 PM#4
grim001
maybe I don't have much of an imagination, but could you elaborate on the potential uses for this? what is made more convenient by this system compared to attaching a struct to a timer?
02-07-2007, 07:17 PM#5
PipeDream
Using timers is a hassle. It requires getting a timer and releasing a timer, both of which just suck due to JASS bugs. This contains all the event thinking into a couple lines of code which are right next to each other instead of spread out over a couple hundred lines for a nasty spell. So it's easier to use and easier to teach people how to use.
02-08-2007, 02:04 AM#6
grim001
Hmm, I can see how this would clean up the organization of large spells.