System Wait System

Darius34

New Member
Reaction score
30
Wait System v1.3

Wait System v1.3

My first actual vJass system. Any suggestions for improvement are appreciated, be they to do with functionality or coding.

Next: I know this system bears some resemblance to Cohadar's TT, but know that I didn't blatantly copy or anything. I started on this, and was mostly done when I chanced upon TT, so I referred to it and adapted things. The basic concepts are similar, but mine has other features and a slightly different focus.

Requirements:
-vJass preprocessor (NewGen pack).
-JASS knowledge. This isn't very useful in GUI.

Implementation:
-Copy the code/trigger into a blank text trigger called Wait System. Voila.

What is it for?
Anything timer-related. It allows you to execute a function after a short delay or periodically with a function call, and supports data-passing. It does all this with only one timer, too.

TT - Differences:

Biggest one is that my system allows you to define the period of the timer. It isn't just limited to high frequencies, so it could be used more widely, unlike TT alone.

JASS:
library WaitSystem initializer Init

//***************************************************************************
//* Wait System v1.3, by Darius34
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* <a href="http://www.thehelper.net/forums/showthread.php?p=847782" class="link link--internal">http://www.thehelper.net/forums/showthread.php?p=847782</a>
//*
//* 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 &quot;WS.&quot; 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 &#039;wait&#039; used here isn&#039;t like conventional wait; it doesn&#039;t pause
//*   execution. It runs like a normal timer would.
//*
//* - The period can be increased to the minimum period in the map. It&#039;s not
//*   advisable to change it, however, if you&#039;re using a large range of very
//*   low periods - a number that can&#039;t be divided by evenly (e.g. 0.03)
//*   could cause inaccuracies with timing.
//*   Leave the period as is if you&#039;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&#039;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 &gt; 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 &lt;= TIMER_PERIOD then
        set actioncount = IterationCount + 1
    else
        set actioncount = R2I(duration/TIMER_PERIOD) + IterationCount
    endif
    if actioncount &gt;= ITERATION_LIMIT then
        set actioncount = actioncount - ITERATION_LIMIT
    endif
    debug call BJDebugMsg(&quot; Action Count: &quot; + I2S(actioncount) + &quot; | Iteration Count: &quot; + I2S(IterationCount))
    
    // Allocates the instance an index, prioritising freed slots.
    if StackSize &gt; 0 then
        set StackSize = StackSize - 1
        set index = Stack[StackSize]
    else
        set index = InstanceIndex
        set InstanceIndex = InstanceIndex + 1
    endif
    debug call BJDebugMsg(&quot;Instance Index: &quot; + 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(&quot;Wait System - Error: No action function specified.&quot;)
        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 &gt; ITERATION_LIMIT then
        set IterationCount = 0
    endif
    
    // Loops through arrays and checks counts, to see if any actions should be executed.
    loop
        exitwhen a &gt; 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

"Wait System" is actually a misnomer, because 'waiting' doesn't pause execution - it runs like a normal timer would. It started out as a wait-like function with no periodic functionality, however (I only decided to put that in later), so the name kind of stuck.

Example of usage:

JASS:
scope RandomPeriodicSpell initializer Init
struct spelldata
    unit caster
endstruct

private function Conditions takes nothing returns boolean
    return GetSpellAbilityId() == &#039;A000&#039;
endfunction

private function PeriodicActions takes integer DataStruct returns boolean
    local spelldata s = spelldata(DataStruct)
    call BJDebugMsg(GetUnitName(s.caster) + &quot; &quot; + I2S(GetWSExecCount()))
    
    if GetWSExecCount() &gt; 10 then
        call s.destroy()
        return false
    endif
    return true
endfunction

private function Actions takes nothing returns nothing
    local spelldata s = spelldata.create()
    set s.caster = GetTriggerUnit()
    call Wait(.01, WS.PeriodicActions, s)
endfunction

private function Init takes nothing returns nothing
    local trigger t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function Conditions))
    call TriggerAddAction(t, function Actions)
    set t = null
endfunction
endscope

There's also a demo map with a simple spell of mine, made with this system. It's a bit old (it still uses dynamic triggers), but it should suffice to show the gist of what this system can do.

Any comments are greatly appreciated.

Mad props to Tukki, Artificial, and Cohadar for feedback.

Changelog

v1.3

- Added some stuff to the documentation and comments.
- Corrected a syntax error that was previously hidden by the debug keyword.
- Restructured the system greatly, in part to facilitate additions to functionality.
- Fixed bugs with 0-second waits:

- Replaced the secondary timer with a minimum wait for the timer period (0.01 seconds by default). The alternative would be a whole timer stack to deal with those waits, and that would defeat the purpose of this system. >.>
- 0-second waits with this system are now more accurate than TriggerSleepAction(0.), but less accurate than using 0-second timers. The inaccuracies are minor, however, and shouldn't be much of a problem as long as users don't floor values for which precision is required.
- The system now works with simultaneous, back-to-back 0-second calls (for events and such).
- Implemented an internal counter to keep track of the number of executions per wait instance, accessible via an inline-friendy function call.

v1.2

- Allowed/fixed 0-second timeouts (previously, nothing would happen). More details can be found in the documentation.
- Added an error message for null action functions. Bugs caused by null functions aren't serious, but can be hard to spot.
- Added a flag to control error message displays.
- Elaborated on documentation somewhat (regarding optimum use of the system with high-frequency executions, and potential inaccuracies when varying the default period).

v1.1

- Implemented function interfaces, and changed syntax slightly as a result. Refer to the documentation for more information.
- There is only a single function now: periodic execution is terminated via the return value of the user-defined function, and the data is directly passed as an argument.

v1.0

- Initial release.
Version 1.2
JASS:
library WaitSystem initializer Init

//***************************************************************************
//* Wait System v1.2, by Darius34
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* <a href="http://www.thehelper.net/forums/showthread.php?p=847782" class="link link--internal">http://www.thehelper.net/forums/showthread.php?p=847782</a>
//*
//* 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 periods as low as 0.01 (by default) accurately.
//*
//* Usage/Syntax
//* ¯¯¯¯¯¯¯¯¯¯¯¯
//* Wait(duration, WS.actionfunc, data)
//*
//*   - actionfunc, the user-defined function, has to be called with
//*     the &quot;WS.&quot; 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.
//*
//* Notes
//* ¯¯¯¯¯
//* - The maxmimum accurate wait time/period you can have with this
//*   system is given by TIMER_PERIOD * ITERATION_LIMIT. It&#039;s 1000
//*   seconds by default.
//* - The &#039;wait&#039; used here isn&#039;t like conventional wait; it doesn&#039;t pause
//*   execution. It runs like a normal timer would.
//*
//* - The period can be increased to the minimum period in the map. It&#039;s not
//*   advisable to change it, however, if you&#039;re using a large range of very
//*   low periods - a number that can&#039;t be divided by evenly (e.g. 0.03)
//*   could cause inaccuracies with timing.
//*   Leave the period as is if you&#039;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 low-frequency executions.
//*
//* - About 0-second timeouts: A value of 0 can be used, even in places where
//*   TriggerSleepAction(0.) won&#039;t work, such as trigger conditions. However,
//*   since the system uses only a single additional timer for this, 0-second
//*   waits can&#039;t be used back-to-back. This should not be a problem normally,
//*   but since it&#039;s a minor flaw, it warrants a mention.
//*
//* Credits
//* ¯¯¯¯¯¯¯
//* - The people who made vJass possible.
//* - Cohadar, for the concept behind TT, which I adapted some.
//*
//***************************************************************************

function interface WS takes integer DataStruct returns boolean

globals

    // Configuration

    private constant boolean DISPLAY_ERROR_MESSAGES = true // Affects whether error messages are displayed.
    
    private constant real TIMER_PERIOD = 0.01 // Yeah, timer period. Can be increased to the minimum period in the map.
    
    private constant integer ITERATION_LIMIT = 100000 // Iteration limit. Just has to be large enough so that the timer doesn&#039;t loop
                                                      // through and execute stuff a few cycles early.
        
    // -- System starts here. --
    
    private timer Timer
    private integer IterationCount = 0 // Increments as the timer executes, resets when &gt; ITERATION_LIMIT.

    private WS array ActionFunction // Wait data.
    private integer array RunCount
    private integer array DataStruct
    private real array Duration

    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 timer SecondaryTimer
    private WS TempActionFunction
    private integer TempDataStruct
endglobals

private function ZeroSecondExecution takes nothing returns nothing
    call PauseTimer(SecondaryTimer)
    call TempActionFunction.execute(TempDataStruct)
endfunction

function Wait takes real duration, WS actionfunc, integer data returns nothing
    local integer actioncount = R2I(duration/TIMER_PERIOD + IterationCount)
    local integer index
    
    if duration == 0. then // Starts a single secondary timer for purposes of 0-second waits.
        set TempActionFunction = actionfunc
        set TempDataStruct = data // Temporary globals.
        call TimerStart(SecondaryTimer, 0., false, function ZeroSecondExecution)
        return
    endif

    // Determines count at which actions will be executed.
    if actioncount &gt;= ITERATION_LIMIT then
        set actioncount = actioncount - ITERATION_LIMIT
    endif
    
    debug call BJDebugMsg(&quot; Action Count: &quot; + I2S(actioncount) + &quot; | Iteration Count: &quot; + I2S(IterationCount))
    
    // Recycles a freed index, if any, or allocates a new index.
    if StackSize &gt; 0 then
        set StackSize = StackSize - 1
        set index = Stack[StackSize]
    else
        set index = InstanceIndex
        set InstanceIndex = InstanceIndex + 1
    endif
    
    debug call BJDebugMsg(&quot;Instance Index: &quot; + I2S(index))
    
    // Stores wait data for a subsequent, periodic wait, if applicable.
    set ActionFunction[index] = actionfunc
    set RunCount[index] = actioncount
    set DataStruct[index] = data
    set Duration[index] = duration
endfunction

private function HandleIterations takes nothing returns nothing
    local integer a = 0
    
    // Increments running count, resets it if necessary.
    set IterationCount = IterationCount + 1
    if IterationCount &gt; ITERATION_LIMIT then
        set IterationCount = 0
    endif
    
    // Loops through arrays and checks counts, to see if any actions should be executed.
    loop
        exitwhen a &gt; InstanceIndex
        if RunCount[a] == IterationCount then
            if ActionFunction[a] == null then
                if DISPLAY_ERROR_MESSAGES then
                    call BJDebugMsg(&quot;Wait System - Error: No action function to be executed.\nExecution terminated. (Run Count: &quot; + I2S(RunCount[a]) + &quot;)\n&quot;)
                endif
            elseif ActionFunction[a].evaluate(DataStruct[a]) then        // Uses the return value of the user-defined function
                call Wait(Duration[a], ActionFunction[a], DataStruct[a]) // to determine if periodic execution continues.
            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)
    set SecondaryTimer = CreateTimer()
endfunction
endlibrary
JASS:
 

Attachments

  • Wait System Demo.w3x
    44.8 KB · Views: 461
Reaction score
341
Looks cool , ive been trying to learn timers and shit for spells , its a little complicated but i almost get it .


Maybe this will help me more.
 

Darius34

New Member
Reaction score
30
Looks cool , ive been trying to learn timers and shit for spells , its a little complicated but i almost get it .
Maybe this will help me more.
Yup, it probably will. Look at the code for reference. The system could probably useful to you as a one-time delay, for executing a function. Most periodic applications need structs, however, for passing data.

How is this > TT or ABCT?
Like I said:

-Biggest one is that my system allows you to define the period of the timer. It isn't just limited to high frequencies, so it can be used more widely, unlike TT alone. It wouldn't require other systems like ABCT to use timers and pass data.
I don't know if this system is strictly > ABCT or TT yet; I haven't done benchmarks. I posted this mainly to get comments, since I had been using this before I knew about TT.
 

Tukki

is Skeleton Pirate.
Reaction score
29
Okey, seems nice :)

1)
JASS:
call ExecuteFunc(ActionFunction[a])

You should change this to execute.<someaction> instead, as you can pass arguments to it. Also it's alot faster. And, ExecuteFunc will crash the game if it doesn't find the spcified string-function.

2)
SCOPE_PRIVATE
This is not that user friendly, swap that argument to triggercondition/action.

3)
JASS:
call TerminateInstance()

To me it seems better if you make the function return a boolean, and evaluate instead of execute. And if false/true then continue -- would also remove one argument from the Wait(..) function.

Else it seems fine :thup: good job!
 

Darius34

New Member
Reaction score
30
You should change this to execute.<someaction> instead, as you can pass arguments to it. Also it's alot faster. And, ExecuteFunc will crash the game if it doesn't find the spcified string-function.
But the function names there are dynamic. How would I use .execute()?

2) This is not that user friendly, swap that argument to triggercondition/action.

3) To me it seems better if you make the function return a boolean, and evaluate instead of execute. And if false/true then continue -- would also remove one argument from the Wait(..) function.
That's the method used by TT. Is it the best one available? I started my system using ExecuteFunc(), so it kind of carried through. I could change it.

Thanks for the comments.
 

Artificial

Without Intelligence
Reaction score
326
> But the function names there are dynamic. How would I use .execute()?
You can use function interfaces (making it a bit harder for the user, though).
JASS:
scope lol initializer heh

function interface WaitSystemCallback takes integer data returns nothing

globals
    WaitSystemCallback array funcs
    integer array datas
endglobals

function TheOneThatCallsTheUsersFunction takes nothing returns nothing
    call funcs[1].execute(datas[1])
endfunction

function WhatEverTheUsersCall takes WaitSystemCallback func, integer data returns nothing
    local timer t = CreateTimer()
    set funcs[1] = func
    set datas[1] = data
    call TimerStart(t, 1., false, function TheOneThatCallsTheUsersFunction)
endfunction

function FunctionMadeByUser takes integer myData returns nothing
    call BJDebugMsg(&quot;It&#039;s running!&quot;)
    call BJDebugMsg(&quot;HOORRAY!&quot;)
endfunction

function heh takes nothing returns nothing
    call WhatEverTheUsersCall(WaitSystemCallback.FunctionMadeByUser, 1)
endfunction

endscope
I hope that gives you an idea about how you can do it. :p
 

Darius34

New Member
Reaction score
30
^ Thanks a lot! ^^

I updated the system somewhat. Implemented interfaces for calling functions, cut out the two functions and that one argument.
 

Cohadar

master of fugue
Reaction score
209
JASS:

ActionFunction[a] != null

This should be reported as user error and not just simply ignored

JASS:

            if ActionFunction[a].evaluate(DataStruct[a]) then            // Uses the return value of the user-defined function
                call Wait(Duration[a], ActionFunction[a], DataStruct[a]) // to determine if periodic execution continues.
            endif

Instead of using Wait there maybe make another private function that only takes argument a
That way you can put null action error message in Wait.

JASS:

Supports
//* data passing, and allows any period.

This is not practically correct because if period is greater than 0.1 system gets extremely inneficient.

All in all it is a good solution if you for example need both 0.04 and 0.02 timers in your map.
But I never had that kind of requirement, in my experience on high timer frequencies all can be done with one fixed period. It is on you to decide which one.
TT said:
// List of recommended periods:
// 0.04 = 25 calls per second
// 0.03125 = 32 calls per second
// 0.025 = 40 calls per second
// 0.02 = 50 calls per second

On low frequencies ABCT is the master.
 

Darius34

New Member
Reaction score
30
Looks like TimeLib but a linear search has replaced the heap...
Umm. Is that a good thing, or a bad thing? :p I don't really know.

This should be reported as user error and not just simply ignored
Got it. I'll change it in the next update.

Instead of using Wait there maybe make another private function that only takes argument a
That way you can put null action error message in Wait.
Mm, do you mean that I should put the index-allocating bit in another function, then use it in Wait(), and whenever Wait() is called again? If possible, could you provide an example? I don't really understand what you mean.

This is not practically correct because if period is greater than 0.1 system gets extremely inneficient.
Well, it's technically correct. :) Anyhow, yeah, I get it. I'll add a notice on.

A question, though: I get how the system would get inefficient when running a single function on a period of, say, 0.5 seconds; many more executions and checks than needed would be made with the high-frequency timer.

If, however, I were running several functions at high frequency, and another one at a period of 0.5 seconds, would the inefficiency problem still apply? After all, the runs would be done anyway for those high-frequency functions, and the low-frequency ones could be just 'slotted' in. Is that what constitutes efficiency?

All in all it is a good solution if you for example need both 0.04 and 0.02 timers in your map.
Yup, that was my original aim, when I came up with the idea for the system.

Thanks lots for the constructive feedback. :)
 

Cohadar

master of fugue
Reaction score
209
If, however, I were running several functions at high frequency, and another one at a period of 0.5 seconds, would the inefficiency problem still apply? After all, the runs would be done anyway for those high-frequency functions, and the low-frequency ones could be just 'slotted' in. Is that what constitutes efficiency?

Then it would be efficient but it would not be accurate, for example if you used 0.5 timer it would look something like this:

ooooXooooXooooXooooXooooXooooXooooX

Each character represents a point in time of 0.1 scale and X represents when timer executes.

But if someone casts a spell that needs a 0.5 timer in time that is not dividable by 0.5

ooooXooooXooooXooooXooooXooooXooooX

You cast the spell at 1.2 (blue) and you want to execute something after 0.5 sec, but instead you code executing on 1.7 it executes prematurely at 1.5

It is impossible to make one timer system that will be both efficient and accurate on all frequencies (althou people have been trying that for years), that is why I made two systems.

Heisenberg said:
Locating a particle in a small region of space makes the momentum of the particle uncertain; and conversely, measuring the momentum of a particle precisely makes the position uncertain.
 

Darius34

New Member
Reaction score
30
Hmm, but wouldn't that be an issue with the reference point from which the 0.5-second delay was taken, and not with the system itself? For example, if a spell were cast at t=1.2s, the starting reference point would be taken there, and the periodic running would then occur at t=1.7s, t=2.2s, and so on.

I understand how your example is applicable with indivisible numbers, but would this problem still occur with a period like 0.01? Every number to two decimal places would be accurately divisible by it, and return a whole number of runs for the timer - a period like 19.34 would be given 1934 0.01-second runs before being executed, etc. Not changing the default period (and not allowing users to change it) could probably solve this.

Instead of using Wait there maybe make another private function that only takes argument a
That way you can put null action error message in Wait.
Also (sorry to bug you :p), but could you elaborate on what you meant by this?
 

Darius34

New Member
Reaction score
30
Updated.

v1.2

- Allowed/fixed 0-second timeouts (previously, nothing would happen). More details can be found in the documentation.
- Added an error message for null action functions. Bugs caused by null functions aren't serious, but can be hard to spot.
- Added a flag to control error message displays.
- Elaborated on documentation somewhat (regarding optimum use of the system with high-frequency executions, and potential inaccuracies when varying the default period).
 

Darius34

New Member
Reaction score
30
Updated.

v1.3

- Added some stuff to the documentation and comments.
- Corrected a syntax error that was previously hidden by the debug keyword.
- Restructured the system greatly, in part to facilitate additions to functionality.
- Fixed bugs with 0-second waits:

- Replaced the secondary timer with a minimum wait for the timer period (0.01 seconds by default). The alternative would be a whole timer stack to deal with those waits, and that would defeat the purpose of this system. >.>
- 0-second waits with this system are now more accurate than TriggerSleepAction(0.), but less accurate than using 0-second timers. The inaccuracies are minor, however, and shouldn't be much of a problem as long as users don't floor values for which precision is required.
- The system now works with simultaneous, back-to-back 0-second calls (for events and such).
- Implemented an internal counter to keep track of the number of executions per wait instance, accessible via an inline-friendy function call.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top