• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Techno's Timer System

Level 2
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:
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()
* Create your unit and your function.
* 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

  • TTS v1.0.w3x
    54 KB · Views: 114
  • Orbits v.00.w3x
    70 KB · Views: 131
Last edited:
Level 11
Joined
Aug 25, 2006
Messages
971
Tell me, how is this any better then simply passing around a struct that has everything you need. You could attach the struct to a timer with handles. Or by using this pretty fast method.
JASS:
// TimerUserData by DiscipleOfLife (works with timeouts ranging from 0 to 0.1):
function TimerStartWithUserData takes timer t, real timeout, integer data, boolean periodic, code handlerFunc returns nothing 
    call TimerStart(t, timeout+data/67108871., periodic, handlerFunc) 
endfunction
function GetTimerUserData takes timer t, real timeout returns integer
return R2I((TimerGetTimeout(t)-timeout)*67108871.+0.5)
endfunction
If you pass the unit/other data in a struct (which is passed as an integer) to a timer using this. Then I don't see why you'd need your system. Could you please better explain what your system does?
 
Level 20
Joined
Apr 22, 2007
Messages
1,960
It's simple, really:
HINDYhat said:
Okay, so you're just extending ABC, but instead of attaching a timer, you're attaching a timer from which you can get a struct which contains units and their corresponding function pointers?

I don't think you could use TimerUserData in this case, since you'd have to know the timer's timeout to retrieve the attached struct.
 
Level 2
Joined
Nov 30, 2007
Messages
11
Tell me, how is this any better then simply passing around a struct that has everything you need. You could attach the struct to a timer with handles. Or by using this pretty fast method.
JASS:
// TimerUserData by DiscipleOfLife (works with timeouts ranging from 0 to 0.1):
function TimerStartWithUserData takes timer t, real timeout, integer data, boolean periodic, code handlerFunc returns nothing 
    call TimerStart(t, timeout+data/67108871., periodic, handlerFunc) 
endfunction
function GetTimerUserData takes timer t, real timeout returns integer
return R2I((TimerGetTimeout(t)-timeout)*67108871.+0.5)
endfunction
If you pass the unit/other data in a struct (which is passed as an integer) to a timer using this. Then I don't see why you'd need your system. Could you please better explain what your system does?

Multiple units on the same timer, running different functions for each unit.

Even if you could run multiple units easily on one timer using your system, you would have to get the function you want to call from the unit user data every time which would be dirt slow. And if you were paying attention, we are passing around a struct that has everything we need, that's what ABC is there for. Pretty soon I'll be removing the requirement for ABC altogether because I kinda figured out how to redo the interfaces to use methods, special TY to Vexorian.

Also if the data is attached to the unit you'd have to get it outside of the function anyway and it doesn't save you any calls.

Textmacro'd version with MUCH BETTER COMMENTING is out so you can use your own struct types now.
 
Level 11
Joined
Aug 25, 2006
Messages
971
For further note its not 'my' system. It was made by 'DiscipleOfLife.' (I don't want to take credit for something thats not my work)

I still think that this could be done faster for individual cases than for every case.

I'll keep up with the project and see if how it goes....
 
Level 11
Joined
Aug 25, 2006
Messages
971
Not exactly what I meant.
Heres an example: If you want to sort 100 different shaped different colored beads you'd do it. You wouldn't spend 10 hours making a system to do it for you when you could do it in 5 hours yourself. Thats what I mean.

I'll look further into your testmap after I get back into school.
 
Level 2
Joined
Nov 30, 2007
Messages
11
Understandable, but if 100 people want to sort 100 beads each, a system that cuts bead sorting time by an hour is a great investment.

Also, if you want to sort beads, and have your friend do jumping jacks, this can handle that as well.
 
Last edited:
Level 11
Joined
Aug 25, 2006
Messages
971
Ok, now I see more where your coming from. I don't have an immediate use for this program, however I'm sure at one time or another I'll need something like this, and I'll know where to go.
 
Level 2
Joined
Nov 30, 2007
Messages
11
In further coding on some other maps, I've come to realize that for the most part, wd40bomber is right and that 90% of coders don't need to use this system, it's mostly just useful for optimizing large systems and adding units created on the fly to a timer.

For instance, if you want X tauren to jump, just code a forloop into the function that the timer calls that makes X tauren that jump.

If you want to have a tauren that jumps, with units that get knockbacked from the tauren when it lands, and then some chickens flying in circles, use TTS.
 
Level 2
Joined
Nov 30, 2007
Messages
11
This seems to me like a remake of TimeLib, with evil textmacros... (They shouldn't really be used so liberally as people do...)

You're right, but in this case, they are useful because they allow you to change variable types easily while keeping the code short and effecient.

I'm new to Jass, and I've seen a few other coders bust this out (Vexorian has AnimationTimers now which pretty much does the same thing and is a lil cooler with very pretty code), but this is pretty much my first project and runs pretty slick, and uses interfaces which is cool too if you want to learn how to use them.
 
Top