System Dynamic Trigger Support

Jesus4Lyf

Good Idea™
Reaction score
397
A trigger index recycling system based on triggers that have conditions attached which only ever return false. In other words, conditions instead of actions. And conditions returning false is mandatory.

ReleaseTrigger does not release the trigger for recycling, it only releases the index to be regenerated for reuse. This means NewTrigger has an infinite spawn.

This uses ExecCount attaching methods with straight O(1) complexity operations exposed.

Functions available are:
JASS:
NewTrigger() --> trigger
ReleaseTrigger(trigger) (Yes, you must null)
SetTriggerData(trigger, data)
GetTriggerData(trigger) --> data

System code:
JASS:
library DTS initializer InitDTS
    globals
        private constant integer MAX_TRIGGERS_AT_ONCE=256
        private constant integer MAX_EXECS_PER_ITER=24
        private constant real RESTORE_PERIOD=0.03125
    endglobals
    
    // The System
    private struct stack // Doesn't require destroy, just must be empty.
        private stack next
        private integer data
        static method create takes nothing returns stack
            return 0
        endmethod
        method operator push= takes integer i returns stack
            local stack new=stack.allocate()
            set new.data=i
            set new.next=this
            return new
        endmethod
        method pop takes nothing returns stack
            call this.destroy()
            return this.next
        endmethod
        method operator val takes nothing returns integer
            return this.data
        endmethod
        method operator empty takes nothing returns boolean
            return this==0
        endmethod
    endstruct
    
    globals
        private trigger array Trig
        private integer NextTrig=MAX_TRIGGERS_AT_ONCE
        private integer array Data
    endglobals
    
    globals
        private constant timer TI=CreateTimer()
        private trigger TI_Current
        private integer TI_ToAdd=0
        private stack IndexesToRestore=0
    endglobals
    private function AddTICurrentExecCount takes integer d returns nothing
        loop
            exitwhen d==0
            set d=d-1
            call TriggerExecute(TI_Current)
        endloop
    endfunction
    private function TI_Process takes nothing returns nothing
        if TI_ToAdd==0 then
            if IndexesToRestore.empty then
                call PauseTimer(TI)
                return
            else
                set TI_ToAdd=IndexesToRestore.val
                set IndexesToRestore=IndexesToRestore.pop()
                set TI_Current=CreateTrigger()
            endif
        endif
        if TI_ToAdd>MAX_EXECS_PER_ITER then
            call AddTICurrentExecCount(MAX_EXECS_PER_ITER)
            set TI_ToAdd=TI_ToAdd-MAX_EXECS_PER_ITER
        else
            call AddTICurrentExecCount(TI_ToAdd)
            set TI_ToAdd=0
            set Trig[NextTrig]=TI_Current
            set NextTrig=NextTrig-1
        endif
    endfunction
    
    function GetTriggerData takes trigger whichTrigger returns integer
        return Data[GetTriggerExecCount(whichTrigger)]
    endfunction
    function SetTriggerData takes trigger whichTrigger, integer data returns nothing
        set Data[GetTriggerExecCount(whichTrigger)]=data
    endfunction
    function NewTrigger takes nothing returns trigger
        set NextTrig=NextTrig+1
        return Trig[NextTrig]
    endfunction
    function ReleaseTrigger takes trigger whichTrigger returns nothing
        if IndexesToRestore.empty then
            set IndexesToRestore.push=GetTriggerExecCount(whichTrigger)
            call DestroyTrigger(whichTrigger)
            call ResumeTimer(TI)
        else
            set IndexesToRestore.push=GetTriggerExecCount(whichTrigger)
            call DestroyTrigger(whichTrigger)
        endif
    endfunction
    
    private function InitTrigExecCount takes trigger t, integer d returns nothing
        if d>64 then
            call InitTrigExecCount.execute(t,d-64)
            set d=64
        endif
        loop
            exitwhen d==0
            set d=d-1
            call TriggerExecute(t)
        endloop
    endfunction
    private function InitDTS takes nothing returns nothing
        call TimerStart(TI,RESTORE_PERIOD,true,function TI_Process)
        loop
            set Trig[NextTrig]=CreateTrigger()
            call InitTrigExecCount.execute(Trig[NextTrig],NextTrig)
            set NextTrig=NextTrig-1
            exitwhen NextTrig==0
        endloop
    endfunction
endlibrary

I intend to provide better documentation after user comments.
 
Reaction score
91
I can't give you a good comment if there is no proper documentation. I can't even get how you use this TriggerExecCount method, still it seems kind of stable.
Does it have any limits? It's good nevertheless, though I don't see how you're recycling the triggers by destroying them.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Questions are as good as comments.

>Does it have any limits?
JASS:
private constant integer MAX_TRIGGERS_AT_ONCE=256

And that caps at 8191. But using such a high number would drastically increase load time of the map. Load time increase goes up quadratically with the max triggers at once, but makes no visible difference thereafter. I wouldn't recommend setting this over about 512. This is simply the number of NewTrigger/ReleaseTrigger triggers you can have in play at once.

>I can't even get how you use this TriggerExecCount method
Since in false-condition dynamic triggers the actions are never run, it means the number of times the actions HAS been run is constant. You can increment this number by one by running the actions manually using TriggerExecute(). Therefore, give each trigger a unique ExecCount, and use that as their "handle" index. Except it's stable because it is independant of everything else. So basically instead of H2I I use the native GetTriggerExecCount. It's like native, stable H2I! (When applied like this - as it only works on triggers where the actions will not be run.)

>seems kind of stable
More stable (and efficient, I expect) than H2I-0x100000. As long as conditions return false.

>though I don't see how you're recycling the triggers by destroying them
As said, only the index is recycled. What happens is the index is taken off the trigger first, then the trigger is destroyed. The index is added to a list of indexes to be reused, then a timer runs in the background (only when there is something to process) which processes this list, reconstructing the ExecCount index on a new trigger (over time so there is no lag spike or O(n) complexity on destruction) which then gets returned to the pool once it is rebuilt.

This system is designed to return roughly the lowest ExecCount possible, to run as fast as possible. There's a couple of optimisations I can do yet, but they're very minor... Just a couple of array read/writes.
 

Sevion

The DIY Ninja
Reaction score
413
Why do you use 0.03125? Why not 0.03? :p I never understood these things XD

And why two global blocks? Just for readability? Or what? :p

Being that I'm less and less active in actually coding, I'm not really sure what this would be used for :(

Yeah, I'm stupid like that. -rep me plox

(DON'T! I'LL CALL REP SUPPORT!)

Yeah. I stole that from Russel Peters. :D
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Why do you use 0.03125? Why not 0.03?
Something vague that Cohadar once said about the way real numbers are represented in memory. I don't have any reason not to use it, so I may as well.

>And why two global blocks? Just for readability? Or what?
Yep. One is associated with the trigger pool, one is associated with the trigger index reconstruction. Mostly for my readability. Makes it easier to locate certain variables.

>I'm not really sure what this would be used for
Attaching data to dynamic triggers (safely AND efficiently). :)
I suppose I should really mention that when I get to doccing.
 

Romek

Super Moderator
Reaction score
964
0.03125 is 32 frames per second. :)

I don't think I'll understand why you'd do all this inefficient, slow stuff. Just to get rid of H2I. (Which isn't a problem)
 

Jesus4Lyf

Good Idea™
Reaction score
397
>do all this inefficient, slow stuff
This is blindingly fast, what are you talking about?
I haven't benchmarked yet, but I expect it's faster than H2I. AND it's more stable.

H2I for dynamic triggers is fail because there is no guarantee that the handle range will stay put. Just try such a system on the H2I test map.
 

Romek

Super Moderator
Reaction score
964
The H2I Test Map isn't anything special, really.
You just create an abnormally large amount of handles.

Something like that wouldn't happen in a real game.

> I haven't benchmarked yet, but I expect it's faster than H2I
Constantly executing a trigger until you get to a certain number is faster than a single function call, and subtraction? :rolleyes:
 

Jesus4Lyf

Good Idea™
Reaction score
397
>Something like that wouldn't happen in a real game.
Stop saying that. It can happen all the time.

>Constantly executing a trigger until you get to a certain number is faster than a single function call, and subtraction?
JASS:
function GetTriggerData takes trigger whichTrigger returns integer
    return Data[GetTriggerExecCount(whichTrigger)]
endfunction
function SetTriggerData takes trigger whichTrigger, integer data returns nothing
    set Data[GetTriggerExecCount(whichTrigger)]=data
endfunction

Blindingly fast. The whole point of this system is that the effect of mass execution is never felt. It's done on initialisation, or slowly over time in the background thereafter.
 

Romek

Super Moderator
Reaction score
964
> Stop saying that. It can happen all the time.
I'll correct myself:
"Something like that won't happen in any real game, except J4Ls map". :)

It seems I also misunderstood the whole TriggerExecCount thing.
 

Azlier

Old World Ghost
Reaction score
461
Shall I port R&R to this so it works decently dynamically?
 

Jesus4Lyf

Good Idea™
Reaction score
397
Actually, systems shouldn't use this. Unless they will actually release the trigger they take. I might add a snippet for systems to use instead. For example, R&R doesn't release triggers until rects are destroyed...

R&R is very slightly more efficient anyway because it attaches the struct directly.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top