Wc3C.net

Wc3C.net (http://www.wc3c.net/forums.php)
-   Scripts (http://www.wc3c.net/forumdisplay.php?f=737)
-   -   Chromatic TimerUtils 2.0 [for 1.24b+] (http://www.wc3c.net/showthread.php?t=101322)

Vexorian 07-06-2008 09:40 PM

Chromatic TimerUtils 2.0 [for 1.24b+]
 
(Requires jasshelper 0.9.Z.something, better just get something like 0.A.2.6 or later)

Timer recycling and attaching.
--
It began as Anitarf complaining about CSSafety's name considering it really has not much to do with the caster system anymore.

Ended up merging CSSafety and CSData, considerations:
  • Group recycling is good for legacy, but for actual usage using a single global group is better.
  • Attaching private values is great for timers and triggers. But we don't need dynamic triggers / the mantra is that dynamic triggers are evil. So we just need quick private attaching for timers? Well, maybe we could use private attaching for ... units or items but they already got user data, there are also dialog buttons but speed is not important there.
  • This system conflicts with CSSafety. If you got CSSafety in your map, you must delete it (And possibly include the compatibility fake CSSafety included in this post) before using TimerUtils. Within time, we'll wipe the old CSSafety, for example I'll use TimerUtils and the currently-fake CSSafety in the next caster system version.
  • This includes double-free protection, many implementations of New/ReleaseTimer don't take this into account, when a timer double-free happens in your map it becomes very hard to find, believe me, I know.
  • Two versions were provided so you can pick the one that suits your taste the better. But for example, if you make a spell with TimerUtils, it won't matter which flavor a person has in her map, it will work.
  • Now three flavors are in the same script and you can tweak the constants to use the one you want!

The flavors:
"Blue" TimerUtils DEFAULT
(When USE_HASH_TABLE is set to true)
Uses a hashtable object from wc3 patch 1.24b+ or later. These things are very fast though not as fast as just an array lookup. So it is the 'slowest'...

... But it is the safest! There are no limits to the number of timers you can have.
"Orange" TimerUtils (aka "old red")
(When USE_HASH_TABLE is set to false, and USE_FLEXIBLE_OFFSET is set to true)
Preloads QUANTITY timers at map init, then can use a subtraction based on the handle id of the first timer. The limit is something from QUANTITY to 8190 depending on the map.

Array lookup + GetHandleId + subtraction + variable lookup. (VERY fast)

"Red" TimerUtils
(When USE_HASH_TABLE is set to false, and USE_FLEXIBLE_OFFSET is set to false)
Tries to preload QUANTITY timers at map init, then can subtract a FIXED OFFSET. The limit is therefore smaller than orange TimerUtils. There is also a possibility it won't be able to preload enough timers, so you will have to MANUALLY TWEAK OFFSET until there is a correct value for your map.

Array lookup + GetHandleId + subtraction. (Theoretical bound for flexible-time timer attaching)

Collapse Chromatic TimerUtils (patch 1.23b or later):
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//*  To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//*  To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass)   More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//*  * Attaching
//*  * Recycling (with double-free protection)
//*
//* set t=NewTimer()      : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x)   : Get a timer (alternative to CreateTimer), call
//*                            Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t)       : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2)     : Attach value 2 to timer
//* GetTimerData(t)       : Get the timer's value.
//*                         You can assume a timer's value is 0
//*                         after NewTimer.
//*
//* Multi-flavor:
//*    Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************

//================================================================
    globals
        //How to tweak timer utils:
        // USE_HASH_TABLE = true  (new blue)
        //  * SAFEST
        //  * SLOWEST (though hash tables are kind of fast)
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true  (orange)
        //  * kinda safe (except there is a limit in the number of timers)
        //  * ALMOST FAST
        //
        // USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
        //  * THE FASTEST (though is only  faster than the previous method
        //                  after using the optimizer on the map)
        //  * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
        //                     work)
        //
        private constant boolean USE_HASH_TABLE      = true
        private constant boolean USE_FLEXIBLE_OFFSET = false

        private constant integer OFFSET     = 0x100000
        private          integer VOFFSET    = OFFSET
              
        //Timers to preload at map init:
        private constant integer QUANTITY   = 256
        
        //Changing this  to something big will allow you to keep recycling
        // timers even when there are already AN INCREDIBLE AMOUNT of timers in
        // the stack. But it will make things far slower so that's probably a bad idea...
        private constant integer ARRAY_SIZE = 8190

    endglobals

    //==================================================================================================
    globals
        private integer array data[ARRAY_SIZE]
        private hashtable     ht
    endglobals
    
    

    //It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
    function SetTimerData takes timer t, integer value returns nothing
        static if(USE_HASH_TABLE) then
            // new blue
            call SaveInteger(ht,0,GetHandleId(t), value)
            
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-VOFFSET]=value
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            set data[GetHandleId(t)-OFFSET]=value
        endif        
    endfunction

    function GetTimerData takes timer t returns integer
        static if(USE_HASH_TABLE) then
            // new blue
            return LoadInteger(ht,0,GetHandleId(t) )
            
        elseif (USE_FLEXIBLE_OFFSET) then
            // orange
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-VOFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-VOFFSET]
        else
            // new red
            static if (DEBUG_MODE) then
                if(GetHandleId(t)-OFFSET<0) then
                    call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
                endif
            endif
            return data[GetHandleId(t)-OFFSET]
        endif        
    endfunction

    //==========================================================================================
    globals
        private timer array tT[ARRAY_SIZE]
        private integer tN = 0
        private constant integer HELD=0x28829022
        //use a totally random number here, the more improbable someone uses it, the better.
        
        private boolean       didinit = false
    endglobals
    private keyword init

    //==========================================================================================
    // I needed to decide between duplicating code ignoring the "Once and only once" rule
    // and using the ugly textmacros. I guess textmacros won.
    //
    //! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
    // On second thought, no.
    //! endtextmacro

    function NewTimerEx takes integer value returns timer
        if (tN==0) then
            if (not didinit) then 
                //This extra if shouldn't represent a major performance drawback
                //because QUANTITY rule is not supposed to be broken every day. 
                call init.evaluate()
                set tN = tN - 1
            else
                //If this happens then the QUANTITY rule has already been broken, try to fix the
                // issue, else fail.
                debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
                set tT[0]=CreateTimer()
                static if( not USE_HASH_TABLE) then
                    debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
                    static if( USE_FLEXIBLE_OFFSET) then
                        if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    else
                        if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
                            //all right, couldn't fix it
                            call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
                            return null
                        endif
                    endif
                endif
            endif
        else
            set tN=tN-1
        endif
        call SetTimerData(tT[tN],value)
     return tT[tN]
    endfunction
    
    function NewTimer takes nothing returns timer
        return NewTimerEx(0)
    endfunction


    //==========================================================================================
    function ReleaseTimer takes timer t returns nothing
        if(t==null) then
            debug call BJDebugMsg("Warning: attempt to release a null timer")
            return
        endif
        if (tN==ARRAY_SIZE) then
            debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")

            //stack is full, the map already has much more troubles than the chance of bug
            call DestroyTimer(t)
        else
            call PauseTimer(t)
            if(GetTimerData(t)==HELD) then
                debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
                return
            endif
            call SetTimerData(t,HELD)
            set tT[tN]=t
            set tN=tN+1
        endif    
    endfunction

    private function init takes nothing returns nothing
     local integer i=0
     local integer o=-1
     local boolean oops = false
        if ( didinit ) then
            return
        else
            set didinit = true
        endif
     
        static if( USE_HASH_TABLE ) then
            set ht = InitHashtable()
            loop
                exitwhen(i==QUANTITY)
                set tT[i]=CreateTimer()
                call SetTimerData(tT[i], HELD)
                set i=i+1
            endloop
            set tN = QUANTITY
        else
            loop
                set i=0
                loop
                    exitwhen (i==QUANTITY)
                    set tT[i] = CreateTimer()
                    if(i==0) then
                        set VOFFSET = GetHandleId(tT[i])
                        static if(USE_FLEXIBLE_OFFSET) then
                            set o=VOFFSET
                        else
                            set o=OFFSET
                        endif
                    endif
                    if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
                        exitwhen true
                    endif
                    if (GetHandleId(tT[i])-o>=0)  then
                        set i=i+1
                    endif
                endloop
                set tN = i
                exitwhen(tN == QUANTITY)
                set oops = true
                exitwhen not USE_FLEXIBLE_OFFSET
                debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")               
            endloop
            
            if(oops) then
                static if ( USE_FLEXIBLE_OFFSET) then
                    debug call BJDebugMsg("The problem has been fixed.")
                    //If this message doesn't appear then there is so much
                    //handle id fragmentation that it was impossible to preload
                    //so many timers and the thread crashed! Therefore this
                    //debug message is useful.
                elseif(DEBUG_MODE) then
                    call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
                    call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
                    call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
                    call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
                endif
            endif
        endif

    endfunction

endlibrary

Expand CSSafety proxy:

Other TimerUtils
Purple - Almost as fast as red but less safe to 'leaks' or using a lot of timers than blue.

GroupUtils (needed by the CSSafety proxy
http://www.wc3c.net/showthread.php?t=104464 .

Switch33 07-06-2008 11:13 PM

Quote:

Group recycling is good for legacy, but for actual usage using a single global group is better.
I wonder how one would go about coding for a system that makes so you only use one global group. I guess you would have to use a system like PUI(that uses custom unit data) and attach numbers to the members of each group but i'm unsure. That's the only relatively good way to distinguish members of different groups in one unit group i think.

Glad the thing finally dropped "CS" from it as Anitarf was right it was definitely misleading.

Here-b-Trollz 07-07-2008 02:04 AM

You don't normally need groups for more than Enums, in which case you just use a global group and a tricky boolexpr. The only times you actually need a group are for say, saving all the units affected by a spell or something similar.

Feroc1ty 07-07-2008 02:48 AM

Is this system better than TT? Meh, since i'm having issues with TT i'll give this a shot.

EDIT: What do you mean by lower values = fast but unsafe, should "safety" be of my concern?

PandaMine 07-07-2008 03:04 AM

I think all these timer systems are confusing the hell out of people

Vexorian 07-07-2008 03:51 AM

Quote:

Originally Posted by PandaMine
I think all these timer systems are confusing the hell out of people

Then don't make them.


Quote:

s this system better than TT? Meh, since i'm having issues with TT i'll give this a shot.



Why must everything be better? I am just trying to get something more consistent than CSSafety here.

Quote:

EDIT: What do you mean by lower values = fast but unsafe, should "safety" be of my concern?
If you use 8000, it may not work with timers that have high ids, but it will be very fast, you'll need to ensure that no timer would get a handle id higher than 8191+0x100000, I would suggest you to keep the default values if you have doubts.

Quote:

I wonder how one would go about coding for a system that makes so you only use one global group. I guess you would have to use a system like PUI(that uses custom unit data) and attach numbers to the members of each group but i'm unsure. That's the only relatively good way to distinguish members of different groups in one unit group i think.
I really, really think that dynamic group usage is not necessary. That's what I meant by one unit group, in fact, you may have 3 or 6 unit groups, the other day I made a whole unit attachment system using 10 groups, what we want to avoid is the need for DestroyGroup, besides of it being possible, I think it makes code simpler, make sure to post counter example cases.

Switch33 07-07-2008 04:16 AM

Yea, that sounds better. So basically i would use something like 10 unit groups or so and recycle them(like re-empty them at the end of triggers and get a free one when i needed one).

Also it would be slower if u searched through all units with data on them in one single group than searching several groups i think.

This is what i got so far... am i on the right track?
Collapse JASS:
library GroupRecycle
globals
unit group G[1] = CreateGroup()
unit group G[2] = CreateGroup()
unit group G[3] = CreateGroup()
unit group G[4] = CreateGroup()
unit group G[5] = CreateGroup()
unit group G[6] = CreateGroup()
unit group G[7] = CreateGroup()
unit group G[8] = CreateGroup()
unit group G[9] = CreateGroup()
unit group G[10] = CreateGroup()
endglobals
    
function GetGroup takes nothing returns integer
//this gives you a free group integer
     loop
        exitwhen i > 10          //Max # of groups
            if CountUnitsInGroup(G[i]) == 0 then                 
                return i               //Returns first empty group it finds
            endif
           set i = i + 1
     endloop
       return 11 //error if it ever gets this far
endfunction
function GroupRelease takes integer groupid returns nothing
//this clears your free group integer
     call GroupClear(G[groupid])
endfunction
endlibrary
//Simple example of a trigger:
function m takes nothing returns booleanexpr
return true //standard filter function
endfunction
function forgroupfunc takes nothing returns nothing
//do something with the units here
endfunction
function usegroup takes nothing returns nothing
local integer i =  GetGroup()
local booleanxpr = Condition(m)
set G[i] = GroupEnumUnitsInRange(G[i],x,y,radius, condition)
call ForGroup(G[i],function forgroupfunc)
//wait.. (spell ends)
call GroupRelease(i)
endfunction
Quote:

I think all these timer systems are confusing the hell out of people

QFT, yea... They all seem to all have "personal benefits" for each of them too if it wasn't enough to annoy the heck out of you trying to choose which ones to use. I think using like TT and HSAS works good.
(Since i think HSAS is faster than ABC, but not faster than TT. Also TT only uses 1 timer.
Another reason why is i don't like going back to gamecache really either.)

cohadar 07-07-2008 04:41 AM

You probably want to remove this Vex: CS_H2I

Vexorian 07-07-2008 04:50 AM

bleh, since CSData begins with a C it was getting added before TimerUtils in my sandbox so that syntax error wasn't catchable . lame.

PandaMine 07-07-2008 05:28 AM

Quote:

Originally Posted by Vexorian
Then don't make them.


Im not making them, what I am saying is having something like 10 timer systems floating around just confusers new JASS programmers

There should be just one general system that allows you to implement different methods with timers using macro's or something like

Vexorian 07-07-2008 04:10 PM

Quote:

There should be just one general system that allows you to implement different methods with timers using macro's or something like
And the objective was not to confuse people...

Rising_Dusk 07-07-2008 04:50 PM

Quote:

Originally Posted by PandaMine
There should be just one general system that allows you to implement different methods with timers using macro's or something like

I don't see the need for anything more elaborate than cache storing of structs or direct array accessing, really. Anything else that has to do with attaching to timers is a load of bollocks as far as I'm concerned.

cohadar 07-07-2008 05:31 PM

Quote:

Originally Posted by Rising_Dusk
I don't see the need for anything more elaborate than cache storing of structs or direct array accessing, really. Anything else that has to do with attaching to timers is a load of bollocks as far as I'm concerned.


Any and all attaching to timers is bollocks.

Themerion 07-07-2008 08:50 PM

Why don't you just provide 1 global group for Enumerations and the like?

Quote:

Originally Posted by cohadar
Any and all attaching to timers is bollocks.


I disagree. If you want something to be perfectly timed (for matching an animation, for instance), it's much more comfortable attaching to a timer than using some kind of global loop or something.

PandaMine 07-08-2008 02:14 AM

The problem with all these timer systems is that htey all have their own specific use in certain situations.

One timer system is much more suited in one map/script then another, and thats why we have like 10 of these systems floating around

To me it makes no difference, i make my own timer systems. If there was a very good tutorial fully explaining how attaching/hashing/pointers/handles works then I wouldn't be complaining but what inevitibly ends up happening is people end up using the completely wrong system


All times are GMT. The time now is 07:25 PM.

Powered by vBulletin (Copyright ©2000 - 2019, Jelsoft Enterprises Ltd).
Hosted by www.OICcam.com
IT Support and Services provided by Executive IT Services