Code for Review

Technomancer

New Member
Reaction score
14
Sup kids, it's Technomancer, I'm posting up my new timer system here for review and a little polishing.

The Goals of this system:
-To create a global timer object that can handle/influence any user created unit
-To allow the user to completely customize or custom-build any functions he wants to use with the unit.
-Avoid using gamecache and handle variables, as they can become very slow in large systems.
and the big one...
-To allow a single timer with a simple interface to easily handle multiple units and functions at the same time.

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.

- 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". 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 = 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

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 <a href="http://www.thehelper.net" class="link link--internal">www.thehelper.net</a>, and I&#039;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&#039;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&#039;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&#039;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
//
//        &#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;
//      EXAMPLE
//        call YOUR_TIMER_STRUCT_NAME.Timer_Add_Unit( YOUR_UNIT_NAME, UNIT&#039;S_FUNCTION_NAME )
//        Obviously, replace the stuff in all caps with your names
//        &#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;               
// 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&#039;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&#039;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&#039;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&#039;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 &quot;INTERFACE&quot; MUST USE THIS SYNTAX **
// ** COPY AND PASTE: DO NOT CHANGE THE WORDS, OR ADD ANYTHING                **
// **  REMOVE THE WORD &quot;interface&quot;                                            **
// **        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 &gt;= thisTimer.Num_Units
            //Get Function Attached GunStruct
            set gAttach = GetUnitUserData(thisTimer.Unit_Array<i>)
            set gAttach.timercounter = gAttach.timercounter + 1
                    //*debug*
                    //call BJDebugMsg(&quot;timercounter is set to: &quot; + I2S(gAttach.timercounter))
            if (gAttach.timercounter &gt;= gAttach.max_iterations) then
                    //*debug*
                    //call BJDebugMsg(&quot;    gAttach.timercounter = &quot; + I2S(gAttach.timercounter) + &quot;  |  gAttach.max_iterations = &quot; + 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 &lt; 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(&quot;Error: Unit Cap Reached on Timer | From TTS&quot;)
        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 &lt; 0 then
            set this.Num_Units = 0
            call BJDebugMsg(&quot;Inappropriate remove unit call | From TTS&quot;)
        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(&quot;First Remove method | num_units = &quot; + I2S(this.Num_Units) + &quot; | enum = &quot; + 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&#039;s own info
            call TimerStart(this.T_Timer, this.cooldown, true, function $ID_KEY$Timer_Action_Functions)
                //start the timer
        else
            call BJDebugMsg(&quot;Double start attempt detected: Timer already active.&quot;)
        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</i></i></i></i></i>
 

Pyrogasm

There are some who would use any excuse to ban me.
Reaction score
134
You're just the slightest bit behind :D

MagicTimers by Vexorian

Edit: Using your name as a prefix for a function like that which will be called a lot looks kind-of dumb, in my opinion. You should remove the Techo_ prefix.
 

Cohadar

master of fugue
Reaction score
209
Whaaaaaaaaaat?
What is this monstrosity supposed to do actually?
Is it a unit move system or something?

And how exactly is this going to improve speed of anything?
You are using function interfaces witch are like 10 times slower than any gamecache call.

I got a headache just from reading your comments.

At the end I can only conclude that you tried to make some version of
my Collections system (hilton.gw.oiccam.com/showthread.php?t=96112) but you honestly lack the knowledge how to do it.
And you don't even need ABC for all this stuff.

My advice to you: simply forget about this and use Collections,
you cannot make it any more better/optimized than I already did.
 

Technomancer

New Member
Reaction score
14
1) Vexorian > me.
2) Dummy triggers, wtf? Can't you just downcompile into a function pointer in java?

As far as the comments go, I have to redo alot of it. 1.0 is out at hiveworkshop but I'm going to hold off on posting it here until I get a more legible system going (some of the comments are like 3 versions old and don't even apply to anything)

Also you seem to give out alot of fishes.
 

Vexorian

Why no custom sig?
Reaction score
187
Interfaces are fine. Seriously, if you can run 600 of them each 0.04seconds without causing a fps drop they are "fine" , compare with gamecache calls and they are actually blazing fast...

Edit: An interface call is not 10 times slower than a gamecache call... I guess the only map I ever made would be at 10 fps all the time if that was true.

Can't you just downcompile into a function pointer in java?
wtf are you talking about?
 

Technomancer

New Member
Reaction score
14
I get a headache trying to understand how jass -> java so I'm not going to try to clarify that because I already know I don't know what I'm talking about.

Cohadar - show me how to use a global array to pass a function arguement and I'm on it.

Updates: Prettied up the code a TON from .03b, released v1.0, examples included.

Vex, that method interface was slick in MagicTimers, I might steal that. If I'm not mistaken, extends just extends the memory allocation for a type right?
 

Cohadar

master of fugue
Reaction score
209
Edit: An interface call is not 10 times slower than a gamecache call... I guess the only map I ever made would be at 10 fps all the time if that was true.

My mistake, I mixed things up.
Now I remember you once said that virtual function calls are around 10 times slower than normal function calls, did that improve in the meantime?
 

Vexorian

Why no custom sig?
Reaction score
187
My mistake, I mixed things up.
Now I remember you once said that virtual function calls are around 10 times slower than normal function calls, did that improve in the meantime?

We concluded that the proportion is not constant.

It looks like the more functions are declared in the map's script the faster TriggerEvaluate gets in comparison to a normal function call. (It does not get faster than a function call, but I've seen the time proportion change from 30.0 (when there are almost no functions) to 2.54 (on the caster system map)

Technomancer: vJass must compile to normal Jass, so we can't do any fancy stuff, and war3 is written in C++ apparentally, whatever they used to write war3 they definitely didn't use Java.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top