Snippet Timer32 Op Limit Safe

Nestharus

o-o
Reaction score
84
JASS:

library T32s /* v2.0.1.0
*************************************************************************************
*
*   Safe version of T32 that helpsavoids op limit. Loop is same speed as T32x when THRESH is
*   8191. Timer that expires 32 times a second.
*
*************************************************************************************
*
*   module T32s
*
*       Interface (declare these in your struct)
*           private static constant integer THRESH
*               -   Determines how much to segment timer. Lower segment means
*               -   less chance to hit op limit but lower speeds. Higher segments
*               -   is higher chance to hit op limit, but higher speeds. T32x segment
*               -   is 8191.
*           private method expire takes nothing returns nothing
*               -   Called whenever timer expires
*
*       method start takes nothing returns thistype
*           -   adds current instance to timer list (start timer)
*           -   returns the instance for easy local thistype this = create().start()
*       method stop takes nothing returns nothing
*           -   removes current instance from timer list (stop timer)
*
************************************************************************************/
    globals
        private timer t = CreateTimer()         //regular timer
        private trigger tr = CreateTrigger()    //expiration trigger
        private triggercondition array rp       //condition to be destroyed
        private integer rc = 0                  //recycler count
        private integer ic = 0                  //instance count
    endglobals
    
    private function Run takes nothing returns nothing
        call TriggerEvaluate(tr)
    endfunction
    
    //recycler function
    private function Rec takes nothing returns nothing
        loop
            set rc = rc - 1
            call TriggerRemoveCondition(tr, rp[rc])
            set ic = ic - 1
            set rp[rc] = null
            exitwhen rc == 0
        endloop
        
        if (ic > 0) then
            //run expiration
            call TriggerEvaluate(tr)
            
            //start the regular timer back up
            call TimerStart(t, .031250000, true, function Run)
        endif
    endfunction

    module T32s
        private static triggercondition array a //periodic code
        private static thistype array n         //next
        private static thistype array p         //previous
        private static thistype e = 0           //current expired
        private static integer c = 0            //current instance count
        private static thistype array w         //next first
        private static thistype array q         //previous first
        private static boolean array f          //first
        private static integer array li         //list id
        private static integer lc = 0           //list count
        private static integer array ni         //node id
        private static integer array an         //absolute next
        private static integer array ap         //absolute previous
        
        //runs mini lists (runs all the code each period eventually)
        //this can be called multiple times based on segment
        //timer list is split into lists that are each on their own
        //trigger condition
        private static method run takes nothing returns boolean
            loop
                call e.expire()
                set e = n[e]
                exitwhen f[e]
            endloop
            return false
        endmethod

        method start takes nothing returns thistype
            local integer i
            //ensure timer isn't already allocated and isn't null
            debug if (n[this] == 0 and this != 0) then
                //if need to add a new segment to list
                if (c-c/THRESH*THRESH == 0) then
                    set a[c] = TriggerAddCondition(tr, function thistype.run)
                    
                    if (ic == 0) then
                        call TimerStart(t, .031250000, true, function Run)
                    endif
                    
                    set ic = ic + 1
                    
                    //add to segment list
                    set w[q[0]] = this
                    set q[this] = q[0]
                    set w[this] = 0
                    set q[0] = this
                    
                    //mark as a first node
                    set f[this] = true
                    
                    set li[lc] = this
                    set lc = lc + 1
                endif
                set ni[this] = lc
                
                //if nothing in the list, just make current head
                if (c == 0) then
                    //update current expired
                    set e = this
                    
                    //add to segmented list
                    set n[this] = this
                    set p[this] = this
                    set n[0] = this
                    set f[this] = true
                    
                    //add to absolute list
                    set an[0] = this
                    set ap[0] = this
                    set an[this] = 0
                    set ap[this] = 0
                //otherwise add to list
                else
                    //add to segmented list
                    set n[p[0]] = this
                    set p[this] = p[0]
                    set n[this] = n[0]
                    set p[n[0]] = this
                    
                    //add to absolute list
                    set an[ap[0]] = this
                    set ap[this] = ap[0]
                    set an[this] = 0
                    set ap[0] = this
                endif
                
                //always update last
                set p[0] = this
                
                set c = c + 1
            debug else
                //if null, display error
                debug if (this == 0) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "T32s ERROR: ATTEMPT TO START NULL TIMER")
                //if already allocated, display error
                debug else
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "T32s ERROR: ATTEMPT TO START TIMER MULTIPLE TIMES")
                debug endif
            debug endif
            
            return this
        endmethod
        
        //stops a timer instance
        method stop takes nothing returns nothing
            local integer i
            local integer i2
            local integer l
            
            //ensure timer is allocated and isn't null
            debug if (n[this] != 0 and this != 0) then
                //update instance list (actual list of timers)
                //exclude 0 to keep loop as fast as possible
                
                set c = c - 1
                
                if (c != 0) then
                    //remove node from segmented list
                    set n[p[this]] = n[this]
                    set p[n[this]] = p[this]
                    
                    //update first/last
                    if (n[0] == this) then
                        set n[0] = n[this]
                    elseif (p[0] == this) then
                        set p[0] = p[this]
                    endif
                    
                    //remove from absolute list
                    set an[ap[this]] = an[this]
                    set ap[an[this]] = ap[this]
                    
                    //update current expired
                    if (e == this) then
                        set e = an[this]
                    endif
                endif
                debug set n[this] = 0
                
                //if need to remove a segment, update the last node
                if (c-c/THRESH*THRESH == 0) then
                    //send segment to recycler for recycling
                    //has to go on a timer since can't remove trigger conditions while the
                    //trigger is running
                    call TimerStart(t, TimerGetRemaining(t), false, function Rec)
                    set rp[rc] = a[c]
                    set a[c] = null
                    set rc = rc + 1
                    
                    if (THRESH > 1) then
                        //get last segment
                        set i = q[0]
                        set f<i> = false    //unmark
                        set li[ni<i>] = 0
                        
                        //go to the previous node
                        set i = q<i>
                        
                        //make 0 point to last node and make
                        //last node point to 0
                        set q[0] = i
                        set w<i> = 0
                        set lc = lc - 1
                    endif
                endif
                
                //if list isn&#039;t empty, update segments
                if (c != 0) then
                    if (f[this]) then
                        set f[this] = false
                        if (THRESH == 1) then
                            //do a simple removal
                            set w[q[this]] = w[this]
                            set q[w[this]] = q[this]
                            set lc = lc - 1
                        else
                            set i = an[this]
                            
                            if (i != 0) then
                                set f<i> = true
                                
                                set li[ni[this]] = i
                                
                                set w[q[this]] = i
                                set q[w[this]] = i
                                set w<i> = w[this]
                                set q<i> = q[this]
                            endif
                        endif
                    endif
                    if (THRESH &gt; 1) then
                        //update all list segments
                        set i2 = ni[this]   //current segment id
                        set l = li[i2]      //current list segment
                        set i = w[l]        //next list segment
                        set l = ni<i>       //next line segment id
                        set this = an<i>    //next node in next list segment
                        loop
                            //while segments left to update
                            exitwhen i == 0
                            
                            set f<i> = false
                            
                            exitwhen this == 0
                            
                            if (this != 0) then
                                //swap current segment head out for
                                //node
                                set w[q<i>] = this
                                set q[w<i>] = this
                                set w[this] = w<i>
                                set q[this] = q<i>
                                set f[this] = true
                                set li[l] = this
                                set ni<i> = i2
                            endif
                            
                            //next
                            set i2 = ni<i>
                            set i = w<i>
                            set l = ni<i>
                            set this = an<i>
                        endloop
                    endif
                endif
            debug else
                debug if (this == 0) then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, &quot;T32s ERROR: ATTEMPT TO STOP NULL TIMER&quot;)
                debug else
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, &quot;T32s ERROR: ATTEMPT TO STOP TIMER MULTIPLE TIMES&quot;)
                debug endif
            debug endif
        endmethod
    endmodule
endlibrary
</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>


JASS:

struct Tester extends array
    private static constant integer COUNT = 2500
    private static constant integer THRESH = 1
    
    private method expire takes nothing returns nothing
    endmethod
    
    implement T32s
    
    private static thistype i = COUNT
    private static method run takes nothing returns boolean
        loop
            exitwhen i == 0
            call i.start()
            set i = i - 1
        endloop
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        local integer c = COUNT/500
        local trigger t = CreateTrigger()
        local conditionfunc b = Condition(function thistype.run)
        loop
            exitwhen c == 0
            call TriggerAddCondition(t, b)
            set c = c - 1
        endloop
        if (COUNT-COUNT/500*500 &gt; 0) then
            call TriggerAddCondition(t, b)
        endif
        call TriggerEvaluate(t)
        call TriggerClearConditions(t)
        call DestroyTrigger(t)
        set t = null
        call DestroyCondition(b)
        set b = null
    endmethod
endstruct



stress tests
Instance Count, Thresh: fps

2300, 8191: 59
8000, 8191: 25
8000, 1: 0
2300, 1: 14
2300, 60: 59
8000, 60: 14
 

Nestharus

o-o
Reaction score
84
It merits its own resource. Anyways, I don't want people messing with it, I spent 11 hours on it.
 

tooltiperror

Super Moderator
Reaction score
231
At least give it its own name, or make it an entirely different system.

Isn't the point of T32x that you get no safety for speed, anyways?
 

Nestharus

o-o
Reaction score
84
For the add and remove, not the loop itself.

The thing added here is op limit resets to avoid op limit (just like description says). I could explain all of the logic to you and why it works and why it matches T32x for speed at 8191 even though it has so much other crap in it, but I'm too tired to explain it all. The demo code runs at 60 fps on my laptop and 100 is decently safe for op limit on most things.

While T32x can support 8191 instances, it really can't because it'd more than likely hit the op limit way before that time. If you want mass instances, you'll end up having to go to this system.

My suggestion is check to see if your thing maxed out on T32x hits op limit. If it doesn't, stay on T32x. If it does, move to this. What I mean by maxed out is start up the max instances for your spell or w/e and see if the last instance ends up running all the way to the end (maybe 12 heroes of same type or 40 creeps or w/e).

If you have something that might have 1000 instances or something, then chances are you'll have to move to T32s.

Also, there is one line in here that still needs to be fixed (not a big deal, doesn't really do anything, it's the period for the TimerStart on the recycler), but I'm way too tired to fix it atm (the comp with the code on it is upstairs). I know the fix for it so you don't need to mention it ;p.
 

emjlr3

Change can be a good thing
Reaction score
395
I think this should be an addition to T32 if you are going to name it as such

it functions different enough to be its own resource otherwise, IMO

with that said - is there really a need for this?? has anyone ever reached the op-limit in a real map using T32? there is a lot of extra overhead here that isn't needed otherwise
 

Nestharus

o-o
Reaction score
84
with that said - is there really a need for this?? has anyone ever reached the op-limit in a real map using T32? there is a lot of extra overhead here that isn't needed otherwise

If the op limit is reached, this script can be here for people to use. There have only been a few times (few being 1, lol) when I have personally reached the op limit for these sorts of timers :\. It's rare, but this script can be here when it does happen ^)^.

Also, this could actually be merged into the T32 lib so that only one .031250000 timer is running rather than 2, but eh... I don't want it messed with so I don't think that's going to happen ^_^.

edit
updated
 

Romek

Super Moderator
Reaction score
963
A rare (read: never except for ridiculous testing purposes) occasion that 'breaks' a system doesn't warrant it's own resource.
This just ruins the whole essence of T32.

Also, does the regular struct member syntax scare you? :p
 

Nestharus

o-o
Reaction score
84
I can only think of one situation where this might be usable atm, but whose to say there aren't more. One shouldn't judge a script as being useless by the nature of that script, but rather by how that script was executed. If the exact same features can be done with less code and less overhead, then the script is useless. If the script is 100% broken with no chance of fixes, then the script is useless. This doesn't fall into either criteria. As you said, the script does have a super rare occasion where its use. In fact, when you get into those extreme circumstances, the map becomes unplayable from lag anyways =P. Perhaps then, due to that, this may be useless =). I'd have to ponder it (possibly with everyone else here) for a bit. Actually, no, even under those limits without lag, this can handle what T32 can't, so then there is a use, but anywho.

The script is complex enough that your average joe couldn't code it in 5 minutes. It's complex enough that professional programmers couldn't code it in 5 minutes =P, lol.

This just ruins the whole essence of T32.

It still does the same thing... it's the same operations if you have 0 segments.


The script has a definite, albeit very rare use =P. If nobody uses it, w/e, but it's smart to keep it there for in case anybody does need it or even wants to see how it's done.

Arguing the uselessness of a script is like arguing whether a movie was good or bad. For the most part, it's based on the viewpoints of a given user.

Anyways, sadly I didn't code this for a specific use... I coded it in response to emjr3 coding a safe version of T32 =P.\


I also bring up the point of QueueQueue... or BigInt... originally, people were saying BigInt was 100% useless because people didn't understand when anyone would ever need something bigger than an integer. Obviously, there was a use for it, but people didn't see it at the time =P.

For QueueQueue, a lot of people didn't even know what it was, let alone what it could possibly be used for, but I proved that it was useful in a situation.

Perhaps it is the same deal here >.>.

This is why I stand by the statement that a script is only useless if that script is literally unusable in every possible situation. If there is even one situation, hypothetical or not, where the script is a good choice, then the script isn't useless =P. You stated that there were situations yourself, so the script can't be useless then ^)^.

Look at AIDS... Sankaku thinks AIDS is useless because he can't see what it can be used for, but that doesn't mean that it has no uses =P.

Anyways, I'm just arguing against the uselessness argument at this point and saying that that's not a good argument against a script =P. Whenever I argue the uselessness argument, I code the same thing with either faster speeds or way less code + same speeds or I show how the script can be broken with no possible work around... I was recently working on an AttackIndexer and discovered that it was useless since I couldn't figure out a good way around null units (permanently broken).


But perhaps this is my own way of thinking, measuring a script's uselessness on concrete evidence rather than the lack of abstract ideas.
 

Jesus4Lyf

Good Idea™
Reaction score
397
JASS:
debug if (n[this] == 0 and this != 0) then

NEVER write code that can break purely by changing from debug mode to release mode. The way you have written this, it will function differently when you take off debug mode. Debug mode should add warnings when things fail, but always function completely the same way! Edit: My bad, did not see the "else" at the end because it's such a long chunk of code. :p

If I'm not mistaken, this is kind of nice because it adds the loop multiple times to the global trigger, and indicates the points at which it should stop each time. The problem is threefold.

1. Your data structure isn't clever enough to have pure O(1) complexity add/removal. I'm not blind! Edit: Actually, because of the short variables, I can't tell what the complexity is O(n) over. Instances or segments.
JASS:
if (THRESH &gt; 1) then
                        //update all list segments
                        set i2 = ni[this]   //current segment id
                        set l = li[i2]      //current list segment
                        set i = w[l]        //next list segment
                        set l = ni<i>       //next line segment id
                        set this = an<i>    //next node in next list segment
                        loop
                            //while segments left to update
                            exitwhen i == 0
                            
                            set f<i> = false
                            
                            exitwhen this == 0
                            
                            if (this != 0) then
                                //swap current segment head out for
                                //node
                                set w[q<i>] = this
                                set q[w<i>] = this
                                set w[this] = w<i>
                                set q[this] = q<i>
                                set f[this] = true
                                set li[l] = this
                                set ni<i> = i2
                            endif
                            
                            //next
                            set i2 = ni<i>
                            set i = w<i>
                            set l = ni<i>
                            set this = an<i>
                        endloop
                    endif</i></i></i></i></i></i></i></i></i></i></i></i>


2. Timer32 not just has the fastest loop speed, but the fastest add/remove isntance speed I am aware of. This is important just when you add/remove a lot of instances. Which is probably the case if you have a lot of instances. Which is when you're implying you would use this resource... so this is nowhere near as efficient as T32. You wouldn't use this, yourself. Well, if I wrote this I wouldn't use it! Much better off writing the same struct twice, implementing vanilla T32x twice, and forking based on how many instances are running of each.

3. You said it best in your code when you wrote [LJASS]f[this][/LJASS]. :p

Anyway, I can see why he called it this and all, etc, but really, vanilla T32x is much better to use in every case where this isn't absolutely vital, because of the add and remove speed which is sweet. :)

Of course, unfortunately, on that grounds, nobody would ever use this..? It's kind of novel.. lol
 

Nestharus

o-o
Reaction score
84
If I'm not mistaken, this is kind of nice because it adds the loop multiple times to the global trigger, and indicates the points at which it should stop each time. The problem is threefold.

That is correct =).

Also

The O(n) search isn't quite what you think it is...

Let's say you have this
[1,2][3,4][5,6][7]

So 2 things per segment. If you removed the 5, it'd turn into this
[1,2][3,4][6][7]

The O(n) search starts from where you removed and goes to the end to make it so that each segment has as much as possible in it. It does this by shifting every segment start node past the segment of the thing you removed down by 1 (shift into last position of previous segment).
->[1,2][3,4][6,7]

No real way around this :\.

And also, because this runs on segments, the O(n) shifting thing isn't that bad... you might only have 2-5 segments ; p

I 100% agree that the T32 add/remove stuff is a lot faster than this one (notice I didn't mention it) because this one has to update the segments =P.

vanilla T32x is much better to use in every case where this isn't absolutely vital

I 100% agree, and not just because of the add/remove, but because of all the extra generated code this has. It's a monster of a module, like my TimerQueue thing =P.

But in situations where the segments are 100% required, then you've no choice but to use this... what needs to be done is research into whether the game will start to lag or not when T32 can no long handle the max number of segments... simply get the limit T32 can handle and then plug a larger number of instances into this with minimal segments and see if it lags. If it doesn't lag, then it has a measurable use =P.

To prove something is useful, there need only be 1 situation where the thing can be used. To prove it is useless, you have to prove that it wouldn't be smart to use in any situation =).

The outline of the test I just provided gives 1 situation where it can be used, thus proving its usefulness =P.
 

Jesus4Lyf

Good Idea™
Reaction score
397
To be honest, I worry more than in every instance this becomes useful, lag will ensue. Think about it, if you write a particle system (one of the most basic things to do, right?) and you're worried about op limit, it means you have thousands of instances... well, WC3 doesn't naturally handle that well regardless what you do. Let alone the obvious - these will be added and removed, continuously. In a test scenario, you have the luxury of initiating all instances before you start benchmarking. Outside of a test scenario, the lag just from creating 8000 instances will probably be unbearable, especially using this. I would honestly prefer, in my own code, that the op limit cause some instances not to tick after several thousand, than the map be totally unplayable due to lag. =S

Food for thought..

Anyway, as usual, up to you to show that it's worthwhile. Write a small, simple particle system (for visuals only) or something with just gravity and bouncing, see if it's ok... I mean, gosh, if you're hitting op limit 32 times a second, your maps unplayable, right?!
 

Nestharus

o-o
Reaction score
84
jesus4lyf, that's the thingie I brought up =P, lol. If it doesn't lag and it does more instances than T32x can do, then it's useful. If not, then it's useless ^_^.

simply get the limit T32 can handle and then plug a larger number of instances into this with minimal segments and see if it lags. If it doesn't lag, then it has a measurable use =P.

The sad part is I think the limit on my machine was 1700 simple particles before it starts to lag, and 600 more complex ones. I'm not sure if T32 could handle 1700 instances with code in them, but we'll see =P. And I pulled these numbers out from a year ago, I'm very sure of the ~600, but not so sure of the 1700 (can't really remember the simple projectiles magic lag number).

Anywho... I guess I'll test it out at some point >.>.
 

Jesus4Lyf

Good Idea™
Reaction score
397
I recall my old machine lagging with my Rain stress-test at 400. Some people hit 1200, though, or so. I've heard that op limit is something in the 120,000 region but I know that it varies depending on what the lines contain. But using that as a ballpark (a completely unreliable one at that), that would allow for ~100 ops on a high end machine per particle, which I didn't use. Simply put, it gives me sufficient concern that hitting op limit 32 times per second is not feasible for a playable map. Up to you to show otherwise! Please include adding/removing instances while the system is running. If you need an example, here's the original Rain stress-test. Make something simple like that, just creating a new rain drop x times a second, and giving them a timed life inside T32 which expires after x seconds or something. You'll figure it out! It's a good test because it has the add/remove stuff firing while it runs, bit more practical than displaying a line of test or something dumb like that.
 

Nestharus

o-o
Reaction score
84
Was actually planning on a simple projectile test where the projectile removes itself when it hits bounds and a new projectile gets created on removal =P. Simple, but to the point ^)^. Sending the projectiles out at different intervals and in different directions ensures that the pc doesn't lag from the models (if I decide to look).

Anyways, yea... my thoughts on the usability thing exactly j4l =P.

I'm just working on other stuff at the moment for a map, but I will get to this eventually ^)^, kk.
 

tooltiperror

Super Moderator
Reaction score
231
Nestharus, I will ask Jesus4Lyf if he'd be open to this.

Basically, since this doesn't merit its own thread, and is just a piggyback of T32, it's not fair for it to be considered a new resource.

I think the best solution would be to post this in the T32 thread, and prove a link to it in the Original Post.

What are your thoughts?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top