HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Comments on my timing system

10-27-2008, 08:26 AM#1
dead_or_alivex
I made a thread about this here quite a while back, but it went off-topic and I didn't really want to revive it, hence this new one. The system's also undergone some changes since then.

This system basically handles everything timer-related in the map with a single timer, allows any period for functions to be executed, and supports data-passing. It's like TT but has slightly different features and syntax.

Besides general comments, I would greatly appreciate if anyone could help me spot bugs, if any. I recently came across a whole series of unpredictable, random bugs in a certain map I made. I spent days debugging to no avail and finally thought today to switch this system out with TimerUtils - the bugs went away. So, yeah - this system was seemingly what was causing the problems, even though it had worked without hitches in every map, test and scenario I had used it in thus far. I tested all the code again extensively in another map, and everything worked perfectly, like it always had before.

Link to the original TH.net topic.

Collapse JASS:
library WaitSystem initializer Init

//***************************************************************************
//* Wait System v1.3, by Darius34
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* [url]http://www.thehelper.net/forums/showthread.php?p=847782[/url]
//*
//* Essentially a way to rectify having to create and maintain multiple
//* timers, replacing and running everything with a single one. Supports
//* data-passing, and allows fully variable periods as low as 0.01
//* (by default) accurately.
//*
//* Function Usage/Syntax
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* function Wait takes real duration, WS actionfunc, integer datastruct returns nothing
//*
//*   - The main function. When calling it, the name of the user-defined
//*     function actionfunc has to be specified with the "WS." prefix.
//*   - actionfunc also has to take an integer (the data struct) and return
//*     a boolean. Returning true continues the periodic execution of the
//*     function, while returning false halts it.
//*
//* function GetWSExecCount takes nothing returns integer
//*
//*  - This inline-friendly function keeps track of how many times a user-
//*    defined function has been executed by the system. It should only be used
//*    in said user-defined functions.
//*
//* Notes
//* ¯¯¯¯¯
//* - The maxmimum accurate wait time/period you can have with this
//*   system is given by TIMER_PERIOD * ITERATION_LIMIT - 1000 seconds
//*   by default.
//* - The 'wait' used here isn't like conventional wait; it doesn't pause
//*   execution. It runs like a normal timer would.
//*
//* - The period can be increased to the minimum period in the map. It's not
//*   advisable to change it, however, if you're using a large range of very
//*   low periods - a number that can't be divided by evenly (e.g. 0.03)
//*   could cause inaccuracies with timing.
//*   Leave the period as is if you're not sure how to change it.
//* - This system is best used with multiple functions running at high
//*   frequency - it remains optimally efficient that way. A normal timer
//*   stack is more suited to only low-frequency executions.
//*
//* Credits
//* ¯¯¯¯¯¯¯
//* - Vexorian and all the people who made vJass possible.
//* - Cohadar for the concept behind TT, which I adapted some.
//*
//***************************************************************************

function interface WS takes integer DataStruct returns boolean

private keyword waitdata

globals
    // Configuration

    private constant boolean DISPLAY_ERROR_MESSAGES = true // Controls whether error messages are displayed.
    private constant real TIMER_PERIOD = 0.01 // The period of the universal timer. Can be increased to the minimum period in the map.
                                              // See documentation above for details on this.
    private constant integer ITERATION_LIMIT = 100000 // The number at which the counter used by the system resets. Just has to be large
                                                      // enough so that the timer doesn't loop through and execute stuff a few cycles
                                                      // earlier. Increase this if you need a wait longer than 1000 seconds.
    // -- System starts here. --
    
    private timer Timer
    private integer IterationCount = 0 // Increments as the timer executes, resets when > ITERATION_LIMIT.
    private waitdata array WaitData

    private integer InstanceIndex = 0 // Index to be used for the next instance of execution.    

    private integer array Stack // Contains recycled indices.
    private integer StackSize = 0
    private integer ExecCount = 0
endglobals

private struct waitdata
    WS ActionFunction
    integer RunCount
    integer DataStruct
    real Duration = 0
    integer ExecutionCount = 1
endstruct

function GetWSExecCount takes nothing returns integer
    return ExecCount // Inline-friendly!
endfunction

private function AllocateWaitIndex takes real duration, WS actionfunc, integer datastruct, waitdata tw returns nothing
    local integer actioncount
    local integer index
    local waitdata w

    // Determines count at which actions will be executed.
    if duration <= TIMER_PERIOD then
        set actioncount = IterationCount + 1
    else
        set actioncount = R2I(duration/TIMER_PERIOD) + IterationCount
    endif
    if actioncount >= ITERATION_LIMIT then
        set actioncount = actioncount - ITERATION_LIMIT
    endif
    debug call BJDebugMsg(" Action Count: " + I2S(actioncount) + " | Iteration Count: " + I2S(IterationCount))
    
    // Allocates the instance an index, prioritising freed slots.
    if StackSize > 0 then
        set StackSize = StackSize - 1
        set index = Stack[StackSize]
    else
        set index = InstanceIndex
        set InstanceIndex = InstanceIndex + 1
    endif
    debug call BJDebugMsg("Instance Index: " + I2S(index))
    
    // Creates a data struct for a new wait instance or handles it for a subsequent, periodic wait.
    if tw == -1 then
        set w = waitdata.create()
        set w.ActionFunction = actionfunc
        set w.DataStruct = datastruct
        set w.Duration = duration
    else
        set w = tw
        set w.ExecutionCount = w.ExecutionCount + 1
    endif
    set w.RunCount = actioncount
    set WaitData[index] = w
endfunction

function Wait takes real duration, WS actionfunc, integer datastruct returns nothing
    if actionfunc == 0 then
        if DISPLAY_ERROR_MESSAGES then
            call BJDebugMsg("Wait System - Error: No action function specified.")
        endif
    else
        call AllocateWaitIndex(duration, actionfunc, datastruct, -1)
    endif
endfunction

private function HandleIterations takes nothing returns nothing
    local integer a = 0
    local waitdata w
    
    // Increments running count, resets it if necessary.
    set IterationCount = IterationCount + 1
    if IterationCount > ITERATION_LIMIT then
        set IterationCount = 0
    endif
    
    // Loops through arrays and checks counts, to see if any actions should be executed.
    loop
        exitwhen a > InstanceIndex
        set w = WaitData[a]
        if w.RunCount == IterationCount then
            set ExecCount = w.ExecutionCount
            if w.ActionFunction.evaluate(w.DataStruct) then
                call AllocateWaitIndex(w.Duration, w.ActionFunction, w.DataStruct, w)
            else
                call w.destroy()
            endif
            set Stack[StackSize] = a // Index recycling.
            set StackSize = StackSize + 1
        endif
        set a = a + 1
    endloop
endfunction

private function Init takes nothing returns nothing
    set Timer = CreateTimer()
    call TimerStart(Timer, TIMER_PERIOD, true, function HandleIterations)
endfunction
endlibrary
10-27-2008, 09:01 AM#2
Themis
Collapse JASS:
private constant boolean DISPLAY_ERROR_MESSAGES = true
It is only being used 1 time, mainly for debugging purposes. I think its useless. I'd suggest doing something like this:
Collapse JASS:
function Wait takes real duration, WS actionfunc, integer datastruct returns nothing
    if actionfunc > 0 then // or actionfunc != 0 but I guess it wont go lower then 0.
        call AllocateWaitIndex(duration, actionfunc, datastruct, -1)
    debug else
        debug call BJDebugMsg("Wait System - Error: No action function specified.")
    endif
endfunction

This way it doesn't create a if then else and a if then statement.
10-28-2008, 06:52 AM#3
dead_or_alivex
Okay, I'll change that (and remove the unneeded timer global - forgot it).
10-31-2008, 04:46 AM#4
dead_or_alivex
Bump.

Due to all the bugs, I'm actually planning to remake the system, and change the methods used slightly. So, yeah, I would really appreciate feedback.