- Joined
- Nov 30, 2007
- Messages
- 11
1.01 coming soon (fixed a bug where TTS used up all available struct slots over time)
The Goals of this system:
-To allow a single timer with a simple interface to easily handle multiple units and functions at the same time.
-Avoid using gamecache and handle variables, as they can become very slow in large systems.
-To allow the user to customize the units he likes, the data he passes, and the functions he likes without interfering with the system.
Usage/Whatsitfor:
- You need
* a unit that wants to do something repeatedly on a timer (move, take an order, cast a spell).
* A function that takes a unit as it's only argument.
- It's for running multiple units with different functions on the same timer, for efficiency of timer calls.
Benchmarking/Effeciency
- Aside from the occasional remove unit call, the main portion of the codes (the part that loops) is 4 lines long, with four lines at the start of the function that do not lool.
- How to Use
* Run the textmacro:
...Running the textmacro creates an instance of the timer system which takes the types of struct that you specify.
The ID Key is just to make each instance unique so no duplicate errors occur. See the demo map for the places where the ID Key comes into play.
* Create a global timer struct and initialize it somewhere:
* Create your unit and your function.
* Make sure that your function matches the interface syntax:
* Add the Unit and the Function to the timer array, using the function interface.
...A function interface is just a method for getting a pointer to a function. It uses the name of the function interface that you declared in the text macro, the "." syntax, and then the name of the function that you have declared somewhere in your code. Thus, "InterfaceName.functionname"
...The unit and the function are added to parallel arrays. When the timer triggers, the arrays are looped through and each unit is passed to the function in the corresponding array that you set with the TimerAddUnit(...) function.
* Set the cooldown on the timer.
* Activate the timer
...So now, you have one unit on the timer, and every .025 seconds yourfunction(unitname) will be called. If you add another function, then at the same time that yourfunction(unitname) is called then yourotherfunction(otherunitname) will be called, and so on and so on.
...Works pretty much just like blizzard's timer system, but with one timer for multiple units. If you want to pass additional data to the function, just attach a struct to the unit and then get that struct inside the function.
A couple of other notes:
The unit you pass needs to have a struct attached to it of the type you specified in the textmacro with a member named ".timercounter", and another one that's named "max_iterations", both integers. You can use this to set the max iterations for the timer, and if it is zero or not attached, nothing will happen. I may change this to add another local array later if speedtests permit. See example:
Future versions will include:
Optimization
JESP Standard
Trigger: The complete, unabridged timer system, v1.0 (copy this into your map)
Maps Included:
TTS v1.0 - includes overcommenting and a wealth of flying frogs for testing purposes.
Orbits v.00 - A map I made in about 30 mins using the timer system, which is much more organized, with better commenting, and a great starter for anyone looking to see a design process in action.
The Goals of this system:
-To allow a single timer with a simple interface to easily handle multiple units and functions at the same time.
-Avoid using gamecache and handle variables, as they can become very slow in large systems.
-To allow the user to customize the units he likes, the data he passes, and the functions he likes without interfering with the system.
Usage/Whatsitfor:
- You need
* a unit that wants to do something repeatedly on a timer (move, take an order, cast a spell).
* A function that takes a unit as it's only argument.
- It's for running multiple units with different functions on the same timer, for efficiency of timer calls.
Benchmarking/Effeciency
- Aside from the occasional remove unit call, the main portion of the codes (the part that loops) is 4 lines long, with four lines at the start of the function that do not lool.
- How to Use
* Run the textmacro:
JASS:
// ***Syntax: textmacro TTS takes STRUCT_TYPE, INTERFACE_HANDLE_NAME, INTERFACE_UNIT_NAME, ID_KEY ***
//! runtextmacro TTS("Techno_GunUnitAttach", "INTERFACE", "interface_unit", "")
...Running the textmacro creates an instance of the timer system which takes the types of struct that you specify.
The ID Key is just to make each instance unique so no duplicate errors occur. See the demo map for the places where the ID Key comes into play.
* Create a global timer struct and initialize it somewhere:
JASS:
globals
Techno_TimerStruct GLOBAL_TTS
endglobals
set GLOBAL_TTS = DEMOTechno_TimerStruct.create()
* Make sure that your function matches the interface syntax:
Code:
function YOUR_FUNCTION_NAME_HERE takes unit INTERFACE_UNIT_NAME returns nothing
* Add the Unit and the Function to the timer array, using the function interface.
JASS:
call GLOBAL_TTS.TimerAddUnit(unit, InterfaceName.functionname)
...A function interface is just a method for getting a pointer to a function. It uses the name of the function interface that you declared in the text macro, the "." syntax, and then the name of the function that you have declared somewhere in your code. Thus, "InterfaceName.functionname"
...The unit and the function are added to parallel arrays. When the timer triggers, the arrays are looped through and each unit is passed to the function in the corresponding array that you set with the TimerAddUnit(...) function.
* Set the cooldown on the timer.
JASS:
set GLOBAL_TTS.cooldown = .025
* Activate the timer
JASS:
if not GLOBAL_TTS.TimerActive then
call GLOBAL_TTS.Timer_TimerStart()
endif
...So now, you have one unit on the timer, and every .025 seconds yourfunction(unitname) will be called. If you add another function, then at the same time that yourfunction(unitname) is called then yourotherfunction(otherunitname) will be called, and so on and so on.
...Works pretty much just like blizzard's timer system, but with one timer for multiple units. If you want to pass additional data to the function, just attach a struct to the unit and then get that struct inside the function.
A couple of other notes:
The unit you pass needs to have a struct attached to it of the type you specified in the textmacro with a member named ".timercounter", and another one that's named "max_iterations", both integers. You can use this to set the max iterations for the timer, and if it is zero or not attached, nothing will happen. I may change this to add another local array later if speedtests permit. See example:
JASS:
struct Hadouken
integer timercounter = 0
integer max_iterations = 100
//include other data you want to pass with the unit here
endstruct
//later...
local Hadouken blueHadouken = Hadouken.create()
call SetUnitUserData(yourunit, blueHadouken)
call GLOBAL_TTS.TimerAddUnit(unit, InterfaceName.functionname)
Future versions will include:
Optimization
JESP Standard
Trigger: The complete, unabridged timer system, v1.0 (copy this into your map)
JASS:
// ------------------ VERSIONS AND CHANGELOG ----------------------------------
// Version: 1.0
// Changelog:
// 1.0 Official first release! seems to be fairly good as of right now
// NEW - Converted everything to a text macro for more customization!
// Textmacroing needs a little work.
// .04 beta - remove a few useless variables from Tiner_Action_Functions
// .03 beta - fully commented with test map
// .02 beta - many errors fixed, some comments added, unreleased
// .01 beta - initial public release, poorly coded and commented, some errors
//
// ------------------- IMPROVEMENTS FORTHCOMING -------------------------------
// Need to figure out exactly how to null some vars and clean up some leaks
// ^^ or even if there are leaks lol?
// Need to figure out how to null function interface pointers.
// Optimization!!
// More optimizations!
// Reducing Cohadar Attack Index below 30%
//
// ------------------- GET ON WITH IT ALREADY ---------------------------------
// These functions and structs are the core of the Techno Timer System Library
//
// DOCUMENTATION
// I'm generally bad at documentation so please, if you would like anything explained,
// please, PM me @Technomancer on [url]www.thehelper.net[/url], and I'll add it to the documentation
//
// ***************************************************************************************
// The general idea is to run your map according to a global timer, instead of creating
// and destroying any number of individual timers, because as a rule we try to cut down
// on trigger events and function calls during the execution of a map, and CreateTimer(),
// DestroyTimer(), and espeically TimerStart(...) and the associated triggers will lag your
// shit up, that's a gaurentee. Several maps already incorporate global event timers into
// themselves, this is just a premade package that will allow you to easily do so in your
// map.
//
// ***************************************************************************************
// This trigger set uses vJass. The only other effective system for doing this sort of
// would be to use gamecache based handle var systems, which in rare cases are buggy,
// but can also slow the game excessively after a large amount of data is transferred.
//
// ***************************************************************************************
//
// This is how this pacakage will work with new timer operated units:
// Step 1) Create a Unit, and a non-private, non-method Function that follows the syntax
// of function YOUR_NAME HERE takes unit interface_unit returns nothing
// Keep it exactly the same, except for the name.
// Step 2) Create a timer structure (Techno_TimerStruct)
// Step 3) Assign a struct of type Techno_GunUnitAttach to the unit's custom value.
// *No custom libraries are required for this, just use
// call SetUnitUserData( UNIT , STRUCT_NAME )
// Step 4) Assign the unit and an associated function to the timer
// the timer can now automatically pick out the unit's necessary
// custom values from the struct attached to the unit, and
// will automatically send the unit with those values to the
// function which you selected for it, at the interval you select
//
// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
// EXAMPLE
// call YOUR_TIMER_STRUCT_NAME.Timer_Add_Unit( YOUR_UNIT_NAME, UNIT'S_FUNCTION_NAME )
// Obviously, replace the stuff in all caps with your names
// '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
// Step 5) Activate the timer if necessary. The timer will automatically pass the unit
// to the required function.
// Step 6) Any additional data you wish to pass can be passed in the structure you
// assigned in the Unit's custom value. This allows total modularity, because
// any function that can access the unit can access all of the values that are
// involved in the function
//
// I would love some help textmacroing this up to be more modular, so that the unit
// can take differently named structs, and the function interface can take different
// arguements if the user so desires.
//TTS: Techno Timer System
library TTS uses ABC
//Declare globals and stuff
globals
constant integer MAX_UNITS = 64 // max array size
endglobals
// *****************************************************************************
// ** First and foremost, we create a textmacro to allow you to customize **
// ** the types of variables you'd like to use with the struct. **
// *****************************************************************************
// Syntax: STRUCT_TYPE is the type of struct that you want to attach to
// your unit
// INTERFACE_HANDLE_NAME is a variable type ID for function pointers.
// INTERFACE_UNIT_NAME is important to remember, because whenever you
// have a function that you want to use with the timer, you'll have
// to use this syntax:
// function f takes unit INTERFACE_UNIT_NAME returns nothing
// ID Key is just a unique ID to prevent duplicate system entries.
// Don't start ID_KEY with a number.
//! textmacro TTS takes STRUCT_TYPE, INTERFACE_HANDLE_NAME, INTERFACE_UNIT_NAME, ID_KEY
// ** ALL function interface OBJECTS OF TYPE "INTERFACE" MUST USE THIS SYNTAX **
// ** COPY AND PASTE: DO NOT CHANGE THE WORDS, OR ADD ANYTHING **
// ** REMOVE THE WORD "interface" **
// ** you are welcome to edit it should you see fit. **
// ** You can pass whatever you need through the unit using the struct **
// ** attached to it **
function interface $INTERFACE_HANDLE_NAME$ takes unit $INTERFACE_UNIT_NAME$ returns nothing
// ** This is NOT A FUNCTION
// ** This is NOT A FUNCTION
// ** This is NOT A FUNCTION , It is a function interface
// ** This is NOT A FUNCTION used for getting pointers to functions
// ** This is NOT A FUNCTION
//This function is called whenever the timer hits 0.
//It is generic and (UNTESTED) should work with any number of created timers.
//This function does not do anything but loop, get the required functions and
//their associated units, and then pass the units to the functions, while the
//functions execute.
//ID Key is used to distinguish this function from other textmacro declared functions
function $ID_KEY$Timer_Action_Functions takes nothing returns nothing
local integer i = 0
local $STRUCT_TYPE$ gAttach = $STRUCT_TYPE$.create()
local $ID_KEY$Techno_TimerStruct thisTimer
set thisTimer = GetTimerStructC(GetExpiredTimer())
loop
exitwhen i >= thisTimer.Num_Units
//Get Function Attached GunStruct
set gAttach = GetUnitUserData(thisTimer.Unit_Array[i])
set gAttach.timercounter = gAttach.timercounter + 1
//*debug*
//call BJDebugMsg("timercounter is set to: " + I2S(gAttach.timercounter))
if (gAttach.timercounter >= gAttach.max_iterations) then
//*debug*
//call BJDebugMsg(" gAttach.timercounter = " + I2S(gAttach.timercounter) + " | gAttach.max_iterations = " + I2S(gAttach.max_iterations) )
call SetUnitUserData(thisTimer.Unit_Array[i], 0) //Removes the custom value from the unit
// ^^^ is this line necessary? ^^^
call RemoveUnit(thisTimer.Unit_Array[i]) // Kill the unit when the timer expires, you can fiddle this if you want.
call thisTimer.Timer_Remove_Unit_Enum(i) // Remove the unit from the timer arrays.
// ^^Should be inlined for effeciency, will do it later after I know everything works.
//gattach is reused so do not destroy it!!!
//Notice that in thise side of the if statement the counter is not increased!
//This is to prevent units being skipped when they replace a removed unit
//in the Timer_Remove_Unit_Enum function
else // Specific unit timer is not expired
//Call the function associated with the unit
call thisTimer.Unit_Funcs_Array[i].execute(thisTimer.Unit_Array[i])
set i = i + 1
endif
endloop
endfunction
struct $ID_KEY$Techno_TimerStruct
timer T_Timer = CreateTimer()
//In future versions, both arrays may be changed to linked lists of units.
unit array Unit_Array[MAX_UNITS]
$INTERFACE_HANDLE_NAME$ array Unit_Funcs_Array[MAX_UNITS]
real cooldown = .01
integer Num_Units = 0
boolean TimerActive = false
method TimerAddUnit takes unit u, $INTERFACE_HANDLE_NAME$ f returns nothing
//First we check to make sure we have enough space
if this.Num_Units < MAX_UNITS then
//Then we set the unit equal to the appropriate array,
//add the appropriate function to the corresponding array
//and increase the counter for the number of units by 1
set this.Unit_Array[this.Num_Units] = u
set this.Unit_Funcs_Array[this.Num_Units] = f
set this.Num_Units = this.Num_Units + 1
else
//if too many units:
call BJDebugMsg("Error: Unit Cap Reached on Timer | From TTS")
endif
set f = 0
set u = null
endmethod
//EASILY INLINABLE: DO NOT CALL THIS FUNCTION
method Timer_Get_Last_Unit takes nothing returns unit
return this.Unit_Array[this.Num_Units - 1]
endmethod
//Removes the last unit from the list. Inlinable but prettier
// if not inlined.
method Timer_Remove_Last_Unit takes nothing returns nothing
set this.Unit_Array[this.Num_Units - 1] = null
set this.Num_Units = this.Num_Units - 1
//Next is to prevent idiots like myself from accidently removing
//the last timer unit or if idiots fiddle with Num_Units directly.
if this.Num_Units < 0 then
set this.Num_Units = 0
call BJDebugMsg("Inappropriate remove unit call | From TTS")
endif
endmethod
// This function needs to be tested for buggers and leaks
method Timer_Remove_Unit_Enum takes integer unum returns nothing
if this.Num_Units == unum + 1 then
//CODE HERE IS INLINED FROM Timer_Remove_Last_Unit
//Just removes the last unit from the array
//*debug* call BJDebugMsg("First Remove method | num_units = " + I2S(this.Num_Units) + " | enum = " + I2S(unum) )
set this.Unit_Array[this.Num_Units - 1] = null
set this.Num_Units = this.Num_Units - 1
else
// Credit to Cohadar of WC3Campaigns.net, I stole this idea from ABC:
set this.Unit_Array[unum] = this.Unit_Array[this.Num_Units - 1]
set this.Unit_Array[this.Num_Units - 1] = null
set this.Unit_Funcs_Array[unum] = this.Unit_Funcs_Array[this.Num_Units - 1]
set this.Unit_Funcs_Array[this.Num_Units] = 0
// * what that does is just takes the last unit in the array, and *
// * overwrites the unit we want to remove with that unit, *
// * then erases the last unit, which leaves us with a 1 shorter *
// * array without the unit we want to remove in a nice crisp *
// * effecient manner. *
set this.Num_Units = this.Num_Units - 1
endif
endmethod
//Timer Start Function:
method Timer_TimerStart takes nothing returns nothing
if this.TimerActive == false then
set this.TimerActive = true
//to prevent duplicate activation which causes errors
call SetTimerStructC(this.T_Timer, this)
//so the timer can access it's own info
call TimerStart(this.T_Timer, this.cooldown, true, function $ID_KEY$Timer_Action_Functions)
//start the timer
else
call BJDebugMsg("Double start attempt detected: Timer already active.")
endif
endmethod
endstruct
//! endtextmacro
// *********************************************************************************************
// *** Actual Function is created with ! runtextmacro
// *** Syntax for textmacro:
// *** ! textmacro TTS takes STRUCT_TYPE, INTERFACE_HANDLE_NAME, INTERFACE_UNIT_NAME, ID_KEY
// *********************************************************************************************
endlibrary
Maps Included:
TTS v1.0 - includes overcommenting and a wealth of flying frogs for testing purposes.
Orbits v.00 - A map I made in about 30 mins using the timer system, which is much more organized, with better commenting, and a great starter for anyone looking to see a design process in action.
Attachments
Last edited: