• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • ✅ The POLL for Hive's Texturing Contest #33 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!

[Snippet] Constant Timer Loop 32

I recommend two changes:

1. Have your timer always started (since CTL exists it is a waste to have the dynamic check throughout the game).

2. Add support for instances created by another allocator that don't want to always be running. It would require a slight restructuring of your code to support this. Obviously I don't know how to create that for you because I can't read your code.
 
Level 6
Joined
Jun 20, 2011
Messages
249
1. Have your timer always started (since CTL exists it is a waste to have the dynamic check throughout the game).
If by this you mean never stop CTL yes i support you on this one, makes no sense to stop a timer that would consume almost no speed to open space for other "heavy processes" that don't even happen periodically.
Your map is fast if your period is fast.
2. Add support for instances created by another allocator that don't want to always be running. It would require a slight restructuring of your code to support this. Obviously I don't know how to create that for you because I can't read your code.
This is how T32 works, and it wouldn't be a slight restructuring, it would be an overhaul
 
I have made the updates. Now features shorter modules and a module that supports allocators from other systems.

JASS:
library CTL initializer Ini /* v1.3.0.0
    *************************************************************************************
    *
    *    CTL or Constant Timer Loop provides a loop for constant merged timers at 32 fps.
    *
    *    Similar to T32 but pauses timer when no structs have instances and removes structs
    *    from timer trigger when those structs have no instances. 
    *
    *    This can also create new timers after destroying a previous timer and generates less 
    *    code in the module. It also generates no triggers so long as the module is implemented 
    *    at the top of the struct.
    *
    ************************************************************************************
    *
    *    static method create takes nothing returns thistype
    *        -    CTL
    *        -    Creates new timer
    *
    *    method destroy takes nothing returns nothing
    *        -    CTL
    *        -    Destroys created timer
    *
    *    method insert takes nothing returns nothing
    *        -    CTList
    *        -    Inserts "this" into the list
    *
    *    method remove takes nothing returns nothing
    *        -    CTList
    *        -    Removes "this" from this list
    *  
    *        Module
    *
    *            module CTL
    *                -    Declare locals in here
    *                -    Run ini code
    *            module CTLExpire
    *                -    Run timer code
    *                -
    *                -    thistype this        refers to current expiring timer\
    *            module CTLNull
    *                -    Null locals here
    *            module CTLEnd
    *
    *            module CTList
    *                -    Declare locals in here
    *                -    Run ini code
    *            module CTListExpire
    *                -    Run timer code
    *                -
    *                -    thistype this        refers to current expiring timer\
    *            module CTListNull
    *                -    Null locals here
    *            module CTListEnd
    *
    *            Example of Constant Timer Loop 32
    *                struct MyTimer extends array
    *                    integer myValue
    *                    implement CTL
    *                        local string s="My value is "
    *                    implement CTLExpire
    *                        call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,s+I2S(myValue))
    *                        call destroy()
    *                    implement CTLNull
    *                        set s=null            //pointless, but shows how to use null block
    *                    implement CTLEnd
    *                endstruct
    *
    *                set MyTimer.create().myValue=16 //will display "My value is 16" 32x a second
    *
    *            module CT32
    *                -    A constant running timer. Useful when the timer is pretty much never ever
    *                -    going to stop. Also allows control over loop (just provides an expiring timer).
    *                -    Code goes in between two methods
    *            module CT32End
    *
    *            Example of Constant Timer 32
    *
    *                //Displays
    *                //            1
    *                //            2
    *                //            3
    *                struct MyTimers extends array
    *                    integer myValue
    *                    thistype next
    *                    implement CTL2
    *                        local thistype this=thistype(0).next
    *                        loop
    *                            exitwhen 0==this
    *                            call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,s+I2S(myValue))
    *                            set this=next
    *                        endloop
    *                    implement CTL2End
    *                    private static method onInit takes nothing returns nothing
    *                        set thistype(0).next=1
    *                        set thistype(1).next=2
    *                        set thistype(2).next=3
    *                        set thistype(1).myValue=1
    *                        set thistype(2).myValue=2
    *                        set thistype(3).myValue=3
    *                    endmethod
    *                endstruct
    *
    ************************************************************************************/
    globals
        private integer ic = 0                  //instance count
        private integer array rf                //root first
        private integer array p                     //previous
        private integer array n                     //next
        private integer array th                //timer head
        private integer tt                      //temp timer head
        private integer array ns                //next stack
        private trigger t = CreateTrigger()
        private triggercondition array ct
        private boolexpr array rc
    endglobals
    
    private function E2 takes integer i returns nothing
        local integer a = n[i]
        set ns[0] = 0
        loop
            if 0 == p[i] then
                //If this was the root first.
                set tt = th[i]
                if 0 == a then
                    //The list is empty
                    call TriggerRemoveCondition(t, ct[tt])
                    set ct[tt] = null
                    set rf[tt] = 0
                else
                    //The list requires a new root first.
                    set rf[tt] = a
                    set p[a] = 0
                endif
            else
                //Nothing extraordinary, simply remove the node
                set p[a] = p[i]
                set n[p[i]] = a
            endif
            //Deallocate the node
            set n[i] = a
            set n[0] = i
            set i = ns[i]
            exitwhen 0 == i
            set a = n[i]
        endloop
    endfunction
    private function E takes nothing returns nothing
        if ns[0] != 0 then
            call E2(ns[0])
        endif
        call TriggerEvaluate(t)
    endfunction
    private function Ini takes nothing returns nothing
        call TimerStart(CreateTimer(), .031250000, true, function E)
    endfunction
    
    private function CT takes integer r returns integer
        local integer i = n[0]
        local integer f = rf[r]
        if 0 == i then
            set i = ic + 1
            set ic = i
        else
            set n[0] = n[i]
        endif
        set th[i] = r
        debug set ns[i] = -1
        if 0 == f then
            //Make "i" the root.
            set n[i] = 0
            set p[i] = 0
            set rf[r] = i
            set ct[r] = TriggerAddCondition(t, rc[r])
        else
            //Simply add "i" to the list.
            set n[i] = f
            set p[i] = 0
            set p[f] = i
            set rf[r] = i
        endif
        return i
    endfunction
    private function DT takes integer t returns nothing
        debug if 0 > ns[t] then
            set ns[t] = ns[0]
            set ns[0] = t
        debug else
            debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"TIMER LOOP ERROR: ATTEMPT TO DESTROY NULL TIMER")
        debug endif
    endfunction
    
    private function A takes code c returns integer
        set ic = ic + 1
        set rc[ic] = Filter(c)
        return ic
    endfunction
    
    private keyword r
    private keyword e
    module CTL
        static integer r
        static method create takes nothing returns thistype
            return CT(.r)
        endmethod
        method destroy takes nothing returns nothing
            call DT(this)
        endmethod
        static method e takes nothing returns nothing
            local thistype this = rf[.r]
    endmodule
    module CTLExpire
            implement CTL
            loop
                exitwhen 0 == this
    endmodule
    module CTLNull
                set this = n[this]
            endloop
    endmodule
    module CTLEnd
            implement CTLNull
        endmethod
        private static method onInit takes nothing returns nothing
            set .r = A(function thistype.e)
        endmethod
    endmodule
    
    private keyword d
    private keyword ne
    private keyword pr
    module CTList
        static integer r
        static integer d
        static integer array ne
        static integer array pr
        method remove takes nothing returns nothing
            debug if .pr[this] != 0 or .ne[0] == this then
                set .ne[.pr[this]] = .ne[this]
                set .pr[.ne[this]] = .pr[this]
                set .pr[this] = 0
                if .ne[0] == 0 then
                    call DT(.d)
                endif
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"TIMER LOOP ERROR: ATTEMPT TO REMOVE INVALID INDEX")
            debug endif
        endmethod
        method insert takes nothing returns nothing
            debug if .pr[this] == 0 and .ne[0] != this then
                if .ne[0] == 0 then
                    set .d = CT(.r)
                endif
                set .pr[.ne[0]] = this
                set .ne[this] = .ne[0]
                set .ne[0] = this
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"TIMER LOOP ERROR: ATTEMPT TO DOUBLY-ADD INDEX")
            debug endif
        endmethod
        static method e takes nothing returns nothing
            local thistype this = .ne[0]
    endmodule
    module CTListExpire
            implement CTList
            loop
                exitwhen 0 == this
    endmodule
    module CTListNull
                set this = .ne[this]
            endloop
    endmodule
    module CTListEnd
            implement CTListNull
        endmethod
        private static method onInit takes nothing returns nothing
            set .r = A(function thistype.e)
        endmethod
    endmodule
    
    module CT32
        static method e takes nothing returns boolean
    endmodule
    module CT32End
            return false
        endmethod
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(t, Filter(function thistype.e))
        endmethod
    endmodule
    
endlibrary
 
Last edited:
I am not comfortable with create + insert. It is either one or the other, and there are two types of modules that support each of them. Also, one of the big reasons I actually wrote this was for the pausing timers and the removal of the trigger evaluations. There are some cases where a timer is rarely running, like an effect for a spell that runs 32x a second.


2. Add support for instances created by another allocator that don't want to always be running. It would require a slight restructuring of your code to support this. Obviously I don't know how to create that for you because I can't read your code.

This is already supported

Have your timer always started (since CTL exists it is a waste to have the dynamic check throughout the game).

I can keep the timer always started, but that's really pretty silly when the overhead is this small...

JASS:
        if (0==tc) then
            call PauseTimer(m)
        else
            call TriggerEvaluate(t)
        endif


I think that the resource is perfect as is.
 
Nestharus, you don't realize.

For the counter, it's not just the if-check, it is also keeping the "tc" variable plussed or minused as well.

You forgot to make "set ns = -1" debug-only which I changed. I also improved efficiency on some points by caching array values into scaler variables.

This does not support other allocators while also being able to pause the timer when it's empty, I don't even know what you're thinking to think that it does.

I also shortened the modules which is important. Keeping the timer always running makes the CT32 module very short as well, and I centralized the initializer on the CTL module because it is just duplicate code.

The reason I updated this is because I don't want to implement CT32 for what I'm doing, because then I have to have the timer always be running, and I don't want to implement CTL for this because I'd have to abuse the create/destroy methods which is why I wrote the CTList modules so that every user who wants to do what I did doesn't have to have some hacky fix.

The CTList module is comparable to T32x but it's obviously faster and cleaner.

I also think that the CTLEnd module can implement CTLNull in case the user doesn't need it, because remember vJass ignores surplus implements of the same module. I just haven't tested it to know if it will implement the user's CTLNull if the CTLEnd module implements it, but it is something I will check because it reduces verbosity (awesome).
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
Well, I honestly don't get why this overwrites "create" and "destroy" methods, and not "allocate"/"deallocate". Like 15 of my systems are based on the public version of those methods, I think I don't have to tell how painful it would be to edit the whole map. Well, if you add a "CTLP" (Constant Timer Loop Public") or "CTLX" module that's based on allocate and deallocate, and not on create and destroy, I might change a few systems; even tho' the API looks horrible and the code is like a plate of spaghetti, the extra speed I can gain (if I can... I'm not sure) might be useful.
 
Use of create and destroy can be changed by modifying your own map to include a modified version of your resources or a modified version of this resource to fix the problem.

The API looks ugly at first but you get used to it and it actually is more readable once you are used to it. Though the updates I proposed will make the resource more user-friendly by cutting the number of required implementations in half.
 
Level 6
Joined
Oct 23, 2011
Messages
182
You can use 2D fake arrays:

twoDarray[index1 * sizeSlot2 + index2]

You'd have max indices of [8191/(sizeSlot2 + 1)] and [sizeSlot2 - 1]

I don't understand you. If I do something like this

unit[this * 100 + index]

I wouldn't be able to use it once I have like 80 instances of CTL running

I usually use 2d arrays with using struct as first index but in this case
I was wondering if I should make an extra integer variable and use it as index, and if it's worth doing so because I would have to recycle those indexes and that would make an array inside an array

(or use CT32 i guess)
 
I don't understand you. If I do something like this

unit[this * 100 + index]

I wouldn't be able to use it once I have like 80 instances of CTL running

Yes, that's true. (Unfortunately :/)
You could use a Table for the fake 2D array instead.

A Table would be fine unless you need to lookup a lot of data from it inside some loop that runs ~23000 times :p
 
Ok Bribe, I have reviewed your possible additions.

Your list idea, while seemingly good, just adds extra unnecessary features. The reason for this is because the user could just as easily use CTL w/ a linked list.


I do, however, like your A function and the way you made the Null module optional.


Also, I think it would be smart to add things to CTL to make it possible to shut off, like an enabled property : ).
 
I think if you add the "enabled" property, then the linked list option I propose would be useless ;)

I like that approach a lot better.

Keep in mind the CTL module is also optional with my approach, because CTLExpire implements it, just in case the user doesn't need any locals. If there are no locals needed, then the user just has to implement CTLExpire and CTLEnd.
 
Introduced new methods for CT32. Gave timer groups their own instancing to keep timer counts down (they shouldn't have shared the same instancer anyways). Added recycler for timer groups. Added a new timer group struct : ). I also absolutely minimized the sizes of the modules. Furthermore, for the CTL stuff, only CTLExpire and CTLEnd are not optional : ).
 
Top