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.
"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:
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
Version 1.2
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 "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
"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;A000039;
endfunction
private function PeriodicActions takes integer DataStruct returns boolean
local spelldata s = spelldata(DataStruct)
call BJDebugMsg(GetUnitName(s.caster) + " " + I2S(GetWSExecCount()))
if GetWSExecCount() > 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:
- 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.
- 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).
- 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).
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.
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 "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.
//*
//* Notes
//* ¯¯¯¯¯
//* - The maxmimum accurate wait time/period you can have with this
//* system is given by TIMER_PERIOD * ITERATION_LIMIT. It's 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 low-frequency executions.
//*
//* - About 0-second timeouts: A value of 0 can be used, even in places where
//* TriggerSleepAction(0.) won't work, such as trigger conditions. However,
//* since the system uses only a single additional timer for this, 0-second
//* waits can't be used back-to-back. This should not be a problem normally,
//* but since it'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'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 > 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 >= ITERATION_LIMIT then
set actioncount = actioncount - ITERATION_LIMIT
endif
debug call BJDebugMsg(" Action Count: " + I2S(actioncount) + " | Iteration Count: " + I2S(IterationCount))
// Recycles a freed index, if any, or allocates a new index.
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))
// 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 > 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
if RunCount[a] == IterationCount then
if ActionFunction[a] == null then
if DISPLAY_ERROR_MESSAGES then
call BJDebugMsg("Wait System - Error: No action function to be executed.\nExecution terminated. (Run Count: " + I2S(RunCount[a]) + ")\n")
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: