System Trigger Utils v1.00

emjlr3

Change can be a good thing
Reaction score
395
Background:
People, more recently, have shunned the usage of dynamic triggers, or more accurately, destroying triggers (but one without the other is unrealistic). I like dynamic triggers. Sometimes its just plain easier and more convenient to use them, and I am all about ease of use.

What is the problem with them? - Well, supposedly, destroying a trigger before every thread started by it ends will cause bugs with handles, and possible handle stack corruption. Personally, I have never had such issues; however, perhaps I never did something silly like that.

In any case, this is a little system I have used since hearing about such incidents. Its also similar to the one IceFrog uses in DotA: Allstars to try and avoid these bugs, with much success mind you.​
Usage:
JASS:
call DestroyTriggerEx(trigger to destroy)

When ever you are completely done with your dynamic trigger, or any trigger for that matter. Its beneficial to use Condition functions as opposed to trigger actions with dynamic triggers since they are reused and thus do not leak or need to be cleaned, and because they tend to run faster.​
Theory:
If we wait a decent amount of time after a trigger is last used, the chances that all threads it created are finished running will be much higher then if it is destroyed immediately. Therefore, we wait, and destroy the triggers after a set duration.

The less triggers you create dynamically, the longer you can wait (RecycleTimeLimit) before needing to destroy triggers or experiencing any slowdown, and the higher the chance of success for this system.​
Code:
JASS:
library TriggerUtils initializer Init

//******************************************************************************
//* By: emjlr3     Version 1.00
//* 
//* This librarys functionality is two fold.
//* It allows users to safely use dynamic triggers without leaking, by
//* waiting a duration after which the chance that all threads started through
//* said trigger are no longer active.
//* It also gives users a quick and safe method for attaching data to triggers
//* by using the H2I bug and affording many user error checks.
//*
//* The trigger destruction method removes the need to destroy/disable triggers
//* at the users end.  Users simply call:
//*     function DestroyTriggerEx takes trigger t returns nothing
//*
//* When they wish to destroy any trigger.
//* 
//* The trigger data storage method uses the time tested H2I()-Offset method:
//*     function SetTriggerData takes trigger t, integer i returns nothing
//*     function GetTriggerData takes trigger t returns integer
//*
//* SetTriggerData attachs the integer i to the specified trigger.
//* GetTriggerData retrieves the integer previously stored to a trigger.
//*

globals
    // Duration after a trigger is disabled before it is checked for destruction
    private constant real RecycleTimeLimit = 60.
    // Periodic interval to check for triggers to destroy
    private constant real Timeout = 15.
    // Maximum number of destroyable triggers allowed before the system reports an error
    // This is a fairly inflated size.  If you need more then this you are doing it wrong.
    private constant integer MaxTriggers = 2500
    // Trigger data array size, change this to a larger value if your map has an over abundance of handles
    private constant integer ArraySize = 8191
    // Smallest handle value in your map
    private constant integer Offset = 0x100000
        
    // If in debug mode, this will display the total number of queued triggers every Timeout interval
    private boolean DisplayStatus = true
        
    // -- Don't touch past this point
    private trigger array Trigs[MaxTriggers]
    private integer Count=0
    private integer Time=0
    private integer RealArraySize
    private integer array TriggerData[ArraySize]
    private real array ExpirationTime[MaxTriggers]
    private timer Tim = CreateTimer()
endglobals

private function t2i takes trigger t returns integer
    return t
    return 0
endfunction

private function Recycle takes integer i returns nothing
    if i!=Count then
        set Trigs<i>=Trigs[Count]
        set ExpirationTime<i>=ExpirationTime[Count]
    endif
    set Trigs[Count]=null
    set ExpirationTime[Count]=0.
    set Count=Count-1
endfunction
private function Destroy takes nothing returns nothing
    local integer i=1
    local real r
    
    set Time=Time+1    
    set r = TimerGetElapsed(Tim)+Time*Timeout
    
    loop
        exitwhen i&gt;Count
        if ExpirationTime<i>&lt;=r then
            debug if Trigs<i>==null or IsTriggerEnabled(Trigs<i>) then
                debug call BJDebugMsg(&quot;|c00ff0303Trigger Destruction error: trigger is null or still enabled, destruction is unwise.&quot;)
            debug endif
            call DestroyTrigger(Trigs<i>)
            call Recycle(i)
        else
            set i=i+1
        endif
    endloop
    
    debug if DisplayStatus then
        debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0.,0.,Timeout/1.5,&quot;Total number of triggers in the destroy queue: &quot;+I2S(Count))
    debug endif
endfunction

// User functions
function GetTriggerData takes trigger t returns integer
    local integer id = t2i(t)-Offset
    
    debug if t==null then
        debug call BJDebugMsg(&quot;|c00ff0303GetTriggerData error: null triggers cannot have retrievable data.&quot;)
    debug elseif id&lt;0 then
        debug call BJDebugMsg(&quot;|c00ff0303GetTriggerData error: trigger id is too small, reduce Offset value.&quot;)
    debug elseif id&gt;RealArraySize then
        debug call BJDebugMsg(&quot;|c00ff0303GetTriggerData error: trigger id is too large, increase ArraySize value.&quot;)
    debug elseif TriggerData[id]&lt;=0 then
        debug call BJDebugMsg(&quot;|c00ff0303GetTriggerData error: trigger has no attached data.&quot;)
    debug endif
    return TriggerData[id]
endfunction
function SetTriggerData takes trigger t, integer i returns nothing
    local integer id = t2i(t)-Offset
    
    debug if t==null then
        debug call BJDebugMsg(&quot;|c00ff0303SetTriggerData error: null triggers cannot be attached to.&quot;)
    debug elseif i&lt;=0 then
        debug call BJDebugMsg(&quot;|c00ff0303SetTriggerData error: no attachable data specified.&quot;)
    debug elseif id&lt;0 then
        debug call BJDebugMsg(&quot;|c00ff0303SetTriggerData error: trigger id is too small, reduce Offset value.&quot;)
    debug elseif id&gt;RealArraySize then
        debug call BJDebugMsg(&quot;|c00ff0303SetTriggerData error: trigger id is too large, increase ArraySize value.&quot;)
    debug endif
    set TriggerData[id] = i
endfunction
function DestroyTriggerEx takes trigger t returns nothing
    call DisableTrigger(t)
    set Count=Count+1
    set Trigs[Count]=t
    set ExpirationTime[Count]=(TimerGetElapsed(Tim)+Time*Timeout)+RecycleTimeLimit
    debug if Count==MaxTriggers then
        debug call BJDebugMsg(&quot;|c00ff0303DestroyTriggerEx error: maximum number of destroyable triggers reached, increase MaxTriggers value.&quot;)
    debug endif
endfunction

// Initialization
private function Init takes nothing returns nothing
    call TimerStart(Tim,Timeout,true,function Destroy)
    set RealArraySize = ArraySize-1
endfunction

endlibrary</i></i></i></i></i></i>


UPDATES:

Changed this over to a full fledged Trigger Utility system. Changed the ways in which errors were displayed. Added in trigger attachment functionality. Updated the user configurable constants greatly. Added in a much better readme to the script. Removed the need for multiple timers. Added in a trigger queue status update message.
JASS:
function SetTriggerData takes trigger t, integer i returns nothing
function GetTriggerData takes trigger t returns nothing


Check it out!

Thoughts, suggestions, etc....?? Enjoy :shades:​
 

WolfieeifloW

WEHZ Helper
Reaction score
372
Doesnt "exitwhen i==12" only need to be 11 :p ?
Or "bj_MAX_PLAYER_SLOTS".

This seems very useful though.
 

Azlier

Old World Ghost
Reaction score
461
It needs to be 12. Loops that start with a high number and go down by subtraction are easier to understand. Computers count down faster than they count up, anyway.
 

Jesus4Lyf

Good Idea™
Reaction score
397
i=i+1 should be after exitwhen, and exitwhen should be 11.

>This seems very useful though.
No it doesn't. This is a system that allows mappers to use bad practice and get away with it, unless they fail to set a certain constant high enough to match their mistakes. :nuts:

Honestly, can't we all just never use TriggerSleepAction? Use timer systems and trigger conditions... No bug has ever been reported like this in these circumstances.

>Computers count down faster than they count up, anyway.
What a strange thing to say. Won't believe it without proof. :p
 

Igor_Z

You can change this now in User CP.
Reaction score
61
As i don't understand JASS, can u explain every lane what it means? I don't know what this Error() does? Do u get fatal error instantly or something?
By ''dynamic triggers'' u mean those who have the event Every X seconds of game time?
 

Romek

Super Moderator
Reaction score
963
You wouldn't understand what Dynamic Triggers are if you use GUI.

About that loop with the player messages, why not just use GetLocalPlayer? :p

Also, what exactly does this do? I don't see it in the post.
 

emjlr3

Change can be a good thing
Reaction score
395
Doesnt "exitwhen i==12" only need to be 11 :p ?
Or "bj_MAX_PLAYER_SLOTS".

This seems very useful though.

I start with i = 0, looped through messages from i=0 to i=11, set i=12 and exited - what Jesus suggests is better though, updated

i=i+1 should be after exitwhen, and exitwhen should be 11.

>This seems very useful though.
No it doesn't. This is a system that allows mappers to use bad practice and get away with it, unless they fail to set a certain constant high enough to match their mistakes. :nuts:

Honestly, can't we all just never use TriggerSleepAction? Use timer systems and trigger conditions... No bug has ever been reported like this in these circumstances.

>Computers count down faster than they count up, anyway.
What a strange thing to say. Won't believe it without proof. :p

static triggers are just plain hard to use for some applications, and I am all about ease of use - if ppl want to use triggersleepaction, then so be it - I use PolledWait2 sometimes when I dont need accuracy and dont care to pass arguments to another timer callback function - does that make me evil, I dont think so

You wouldn't understand what Dynamic Triggers are if you use GUI.

About that loop with the player messages, why not just use GetLocalPlayer? :p

Also, what exactly does this do? I don't see it in the post.

does that actually work?????

I believe Usage and Theory cover what this does and why rather thoroughly

**could someone test something for me - I used two timers because I assumed one timer with a short periodic duration would not summate its elapsed time, is this the case, or could I have used one timer?
 

Builder Bob

Live free or don't
Reaction score
249
TimerGetElapsed() returns how much time has elapsed since last TimerStart() call, or in the case of a periodic timer, last time it expired.

TimerGetElapsed() will never return anything higher than it's timeout.

Yeah, you need two timers.
 

Darius34

New Member
Reaction score
30
static triggers are just plain hard to use for some applications
Such as?

Admittedly the switch from using dynamic triggers to using static triggers takes some getting used to (especially if you've used the former for a good period of time, personal experience), but once you do change over, using static triggers for everything actually seems the more intuitive option - to me, at least. It's also better programming practice. My point is: why worry about all these safety precautions when static triggers work as well as or better than dynamic triggers?

Also, creating and destroying dynamic triggers does cause some extra overhead (which might or might not be significant, it depends).

Not that your system is conceptually flawed or anything like that; it's a good way to avoid the DestroyTrigger() bugs in most applications. I just feel that the merits of using static triggers do not make its usage necessary.
 

Romek

Super Moderator
Reaction score
963
> does that actually work?????
No problems. :p
JASS:
call DisplayTimedTextToPlayer(GetLocalPlayer(),0.,0.,120.,&quot;|c00ff0303The internal trigger safety system has failed!|r&quot;)


> I believe Usage and Theory cover what this does and why rather thoroughly
They certainly explain a lot, in a lot of detail.
But I don't actually see what this does, other than destroy a trigger. :p
 

emjlr3

Change can be a good thing
Reaction score
395
it waits X seconds before doing so, with some checks in place for errors
 

Jesus4Lyf

Good Idea™
Reaction score
397
So this isn't a surefire method of fixing a bug that should be avoided in the first place?

>does that make me evil, I dont think so
You use PolledWait --> I consider you nasty. :D

Actually, I was thinking of writing a precompiler that converts wait in a loop to KT2, and wait outside a loop into TU. But I probably won't end up doing it. :p

Edit: Why not just write this system using polled wait? :p
(Actually, it's because there's much bigger problems with it than inaccuracy. I think issuing orders in WC3 is O(n) over the number of waits happening in the background, last I checked... Or something like that.)
 

Troll-Brain

You can change this now in User CP.
Reaction score
85
Actually, I was thinking of writing a precompiler that converts wait in a loop to KT2, and wait outside a loop into TU. But I probably won't end up doing it.
I planned to make something like that, but maybe it's too much for starting to learn a programmed language :p
 

emjlr3

Change can be a good thing
Reaction score
395
Such as?

Admittedly the switch from using dynamic triggers to using static triggers takes some getting used to (especially if you've used the former for a good period of time, personal experience), but once you do change over, using static triggers for everything actually seems the more intuitive option - to me, at least. It's also better programming practice. My point is: why worry about all these safety precautions when static triggers work as well as or better than dynamic triggers?

Also, creating and destroying dynamic triggers does cause some extra overhead (which might or might not be significant, it depends).

Not that your system is conceptually flawed or anything like that; it's a good way to avoid the DestroyTrigger() bugs in most applications. I just feel that the merits of using static triggers do not make its usage necessary.

Imagine if you wanted to make a spell that when casted grabbed all units in the area and marked them, for the next few seconds, when they take damage, they will takes bonus damage on top of that, but you want the caster to deal all the bonus damage – now imagine if any number of players could have any numbers of these units who could cast this spell – gets tough…

I am not saying its impossible, its just not nearly as easy or straightforward as creating a dynamic trigger on cast, and checking for the effected units taking damage - all needed data can then be attach to that trigger

> does that actually work?????
No problems. :p
JASS:
call DisplayTimedTextToPlayer(GetLocalPlayer(),0.,0.,120.,&quot;|c00ff0303The internal trigger safety system has failed!|r&quot;)


> I believe Usage and Theory cover what this does and why rather thoroughly
They certainly explain a lot, in a lot of detail.
But I don't actually see what this does, other than destroy a trigger. :p

will I have to try and incorporate that

So this isn't a surefire method of fixing a bug that should be avoided in the first place?

>does that make me evil, I dont think so
You use PolledWait --> I consider you nasty. :D

Actually, I was thinking of writing a precompiler that converts wait in a loop to KT2, and wait outside a loop into TU. But I probably won't end up doing it. :p

Edit: Why not just write this system using polled wait? :p
(Actually, it's because there's much bigger problems with it than inaccuracy. I think issuing orders in WC3 is O(n) over the number of waits happening in the background, last I checked... Or something like that.)

its not 100%, no, how astute of you
 

emjlr3

Change can be a good thing
Reaction score
395
massive updates, check out the first thread
 

Jesus4Lyf

Good Idea™
Reaction score
397
I'm tempted not to comment because I fear my comment will be too negative. But here's my honest feedback...

Although this system never worked in all circumstances, this system now also provides an unstable attaching method (in a situation where you could've implemented ExecCount attaching perfectly safely and efficiently no less) with a maximum number of triggers for a single running of a map, which are not recycled! (Obviously recycling is probably not useful due to events.) Furthermore, the usage section doesn't even document the set/get data functions.

If a map using this system lasts longer than the mapper expected, core functions of the map would suddenly fail for not one but TWO reasons - the offset may not be set high enough, and the maximum number of triggers may not be set high enough. What if a map has a high turn-around on triggers?

Worst of all, you haven't documented ANY of these downsides! A moderator, posting an unstable system, naming it after a stable and well founded and used system (TimerUtils) is really quite misleading. Calling it TriggerUtils is a misnomer.

And why name this after TimerUtils anyway? This doesn't support NewTrigger/ReleaseTrigger recycling, which the name suggests it might.

Your set/get data functions don't inline. What's the point of using H2I in a way that's both slow AND unstable?

JASS:
set Time=Time+1    
set r = TimerGetElapsed(Tim)+Time*Timeout

Time should represent the total time passed in the game. This is actually inefficient anyway, you should just use TimerUtils or something.

At very least, I would like to see a name change.

>"It allows users to safely use dynamic triggers"
<_<

This system will work when you implement ExecCount attaching, name DestroyTriggerEx ReleaseTrigger instead, make it still destroy the trigger after time, but also in the mean time generate a new trigger with the same ExecCount number, and make the get/set data functions inline. Until then I would not use this system.

Edit: Oh yeah, but that would mean users can't use actions, so I suppose this system simply can't work. <_<
 

Azlier

Old World Ghost
Reaction score
461
While I agree with other points, Jesus4Lyf, execcount cannot work and still be able to release triggers back into their stacks. You have to destroy and recreate dynamic triggers, because events cannot be removed.
 

Jesus4Lyf

Good Idea™
Reaction score
397
This system will work when you implement ExecCount attaching, name DestroyTriggerEx ReleaseTrigger instead, make it still destroy the trigger after time, but also in the mean time generate a new trigger with the same ExecCount number
Will work fine. Might even make it myself some time.
 

Viikuna

No Marlo no game.
Reaction score
265
Ahem. If you recycle your units you can have unit specific triggers that are not dynamic. Just have one trigger per event per unit and then add some Conditions dynamicly.

Unit recycling really solves a shitloads of problems.
 
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