• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Snippet] Event

Level 31
Joined
Jul 10, 2007
Messages
6,306
Updated

Also thinking of adding this in for dynamic boolexpr code

Has to be an integer array stack rather than a reg stack (one extra var and extra operations) as multiple boolexpressions on one event may be removed.

I only bring this up because there are certain individuals who state that trigger and boolexpr unregistration from an event are crucial, even though JASS itself doesn't support it : |. The overhead on doing such a things is pretty bleh, but given that the loop would exit immediately if there are no dynamic triggers, I think it's ok to add.
JASS:
        private static triggercondition array removal
        private static integer array removeStack
        private static integer removeStackCount = 0
        private static timer removeTimer = CreateTimer()

        private static method remove takes nothing returns nothing
            loop
                call TriggerRemoveCondition(trig[removeStack[removeStackCount]], removal[removeStack[removeStackCount]])
                set removal[removeStack[removeStackCount]] = null
                set removeStackCount = removeStackCount - 1
                exitwhen removeStackCount == 0
            endloop
        endmethod
        method unregister takes triggercondition tc returns nothing
            set removeStack[removeStackCount] = this
            set removal[removeStackCount] = tc
            set removeStackCount = removeStackCount + 1
            call TimerStart(removeTimer, 0, false, function thistype.remove)
        endmethod


edit
Possible Update

New methods-
JASS:
method registerDynamicTrigger takes trigger t returns integer
method unregisterTrigger takes thistype eventId returns nothing
method unregister takes triggercondition tc returns nothing

Extra overhead with dynamic triggers absent-
JASS:
//Vars
private thistype first
private thistype head
private thistype next
private thistype previous
private static integer dcount = 0
private static integer recycleCount = 0
private static integer array recycle
private trigger dtrig

//Code
set this = first
exitwhen this == 0

edit
The next debate is whether the triggering trigger and triggering event should be retrievable, which would require 2 new local declarations in the firing methods as well 2 new global declarations.

Additions
JASS:
readonly static thistype eventTrigger
readonly static thistype event
readonly trigger trigger

JASS:
library Event
    globals
        private real eventv = 0
    endglobals
    struct Event extends array
        private static integer count = 0
        private static trigger array trig
        private static triggercondition array removal
        private static integer array removeStack
        private static integer removeStackCount = 0
        private static timer removeTimer = CreateTimer()
        static integer data = 0
        readonly trigger trigger
        private thistype head
        private thistype first
        private thistype next
        private thistype previous
        readonly static thistype eventTrigger = 0
        readonly static thistype event = 0
        private static integer dcount = 0
        private static thistype recycle = 0
        static method create takes nothing returns thistype
            set count = count + 1
            set trig[count] = CreateTrigger()
            return count
        endmethod
        method registerTrigger takes trigger t returns nothing
            call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "eventv", EQUAL, this)
        endmethod
        method registerDynamicTrigger takes trigger t returns thistype
            local thistype new
            if (recycle == 0) then
                set dcount = dcount + 1
                set new = dcount
            else
                set new = recycle
                set recycle = recycle.next
            endif
            if (first == 0) then
                set first = new
                set new.previous = new
                set new.next = 0
            else
                set new.previous = first.previous
                set new.next = 0
                set new.previous.next = new
                set first.previous = new
            endif
            set new.head = this
            set new.trigger = t
            return new
        endmethod
        method unregisterTrigger takes nothing returns nothing
            if (head.first == this) then
                set head.first = next
            else
                set previous.next = next
            endif
            if (next == 0) then
                set head.first.previous = previous
            else
                set next.previous = previous
            endif
            set next = recycle
            set recycle = this
            set trigger = null
        endmethod
        method register takes boolexpr c returns triggercondition
            return TriggerAddCondition(trig[this], c)
        endmethod
        private static method remove takes nothing returns nothing
            loop
                call TriggerRemoveCondition(trig[removeStack[removeStackCount]], removal[removeStack[removeStackCount]])
                set removal[removeStack[removeStackCount]] = null
                set removeStackCount = removeStackCount - 1
                exitwhen removeStackCount == 0
            endloop
        endmethod
        method unregister takes triggercondition tc returns nothing
            set removeStack[removeStackCount] = this
            set removal[removeStackCount] = tc
            set removeStackCount = removeStackCount + 1
            call TimerStart(removeTimer, 0, false, function thistype.remove)
        endmethod
        method fire takes nothing returns nothing
            local integer prevTrigger = eventTrigger
            local integer prevEvent = event
            set event = this
            set eventv = 0
            set eventv = this
            call TriggerEvaluate(trig[this])
            set this = first
            loop
                exitwhen this == 0
                if (IsTriggerEnabled(trigger)) then
                    set eventTrigger = this
                    if (TriggerEvaluate(trigger)) then
                        call TriggerExecute(trigger)
                    endif
                else
                    call EnableTrigger(trigger)
                    if (IsTriggerEnabled(trigger)) then
                        call DisableTrigger(trigger)
                    else
                        if (head.first == this) then
                            set head.first = next
                        else
                            set previous.next = next
                        endif
                        if (next == 0) then
                            set head.first.previous = previous
                        else
                            set next.previous = previous
                        endif
                        set next = recycle
                        set recycle = this
                        set trigger = null
                    endif
                endif
                set this = next
            endloop
            set eventTrigger = prevTrigger
            set event = prevEvent
        endmethod
        method fireData takes integer data returns nothing
            local integer prev = Event.data
            local integer prevTrigger = eventTrigger
            local integer prevEvent = event
            set event = this
            set Event.data = data
            set eventv = 0
            set eventv = this
            call TriggerEvaluate(trig[this])
            set this = first
            loop
                exitwhen this == 0
                if (IsTriggerEnabled(trigger)) then
                    set eventTrigger = this
                    if (TriggerEvaluate(trigger)) then
                        call TriggerExecute(trigger)
                    endif
                else
                    call EnableTrigger(trigger)
                    if (IsTriggerEnabled(trigger)) then
                        call DisableTrigger(trigger)
                    else
                        if (head.first == this) then
                            set head.first = next
                        else
                            set previous.next = next
                        endif
                        if (next == 0) then
                            set head.first.previous = previous
                        else
                            set next.previous = previous
                        endif
                        set next = recycle
                        set recycle = this
                        set trigger = null
                    endif
                endif
                set this = next
            endloop
            set Event.data = prev
            set eventTrigger = prevTrigger
            set event = prevEvent
        endmethod
    endstruct
    function GetEventData takes nothing returns integer
        return Event.data
    endfunction
    function CreateEvent takes nothing returns integer
        return Event.create()
    endfunction
    function TriggerRegisterDynamicEvent takes trigger t, Event ev returns integer
        return ev.registerDynamicTrigger(t)
    endfunction
    function TriggerUnregisterEvent takes Event eventId returns nothing
        call eventId.unregisterTrigger()
    endfunction
    function TriggerRegisterEvent takes trigger t, Event ev returns nothing
        call ev.registerTrigger(t)
    endfunction
    function RegisterEvent takes boolexpr c, Event ev returns triggercondition
        return ev.register(c)
    endfunction
    function UnregisterEvent takes triggercondition tc, Event ev returns nothing
        call ev.unregister(tc)
    endfunction
    function FireEvent takes Event ev returns nothing
        call ev.fire()
    endfunction
    function FireEventData takes Event ev, integer data returns nothing
        call ev.fireData(data)
    endfunction
endlibrary


Thus, over these three versions, there is negligible difference in overhead. The dynamic triggers should run at around the same speed on these as the Event lib by j4l.



What is accomplished then? Register and unregistering boolean expressions, fast triggers that can't be unregistered except by destruction, slow triggers that can be unregistered by destruction or by unregistering.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
EventData is useless. TriggeringEvent is useless. Keep it short like you had it yesterday and don't include this meaty shit that no one ever used from Event in the first place.

As of now your draft has even more text than event. Just the sheer lack of text by the source code was impressive, by achieving the same functionality everyone was already using, without the overhead of stupid ideas that have no practical use.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Keep it simple.

If they want to unregister the trigger, just destroy the trigger.

There is no reason to ever disable an event response for UnitIndex or UnitDeindex. Those are alway critical components.

Keep it simple or don't bother. People on MSN can use Jesus4Lyf's stuff if they want snail speed and plenty of generated text from the JassHelper abomination, but please follow the trend of your other libraries and keep this as tight as possible, and...

Keep it simple.

Also, get rid of that eventreg data shit that has no purpose. No reason to do an eventreg for OnUnitIndex/Deindex or anything of that nature.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->Also, get rid of that eventreg data shit that has no purpose. No reason to do an eventreg for OnUnitIndex/Deindex or anything of that nature.

Actually, that has saved me quite a few variables, heh... UnitIndexer and UnitEvent use integers in the background to store the current event unit id. I didn't originally do it because very few libs just use a single integer or w/e, most do handles or various integers or something like that.

I suppose I can get rid of it though and update UnitIndexer and UnitEvent to be sorta the way they are ;o.

It has also saved me a few functions ; ).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->returning the prefix of the struct

It doesn't ; P. Well, actually .name + SCOPE_PRIVATE might work. I don't use these features very often, so I gotta recheck ;D.

->Perhaps switch the names of registerTrigger and register. That way, if one lib uses the Event at TH, it supports this one as well.

Possibly ; ), but Event at TH also includes EventReg data, so trying to conform to that is silly as there will always be thingies missing ;P.

Also, the RegisterEvent should really be BoolExprRegisterEvent and .register should really be boolExprRegister, but those names are soooo annoying, ;D.

Ehhh, I can change it, but that'll make people currently using this annoyed as they'll have to change everything around =P.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Actually, TriggerRegisterEvent and BoolExprRegisterEvent are the proper naming conventions. RegisterEvent was only done because BoolExpr is annoying to type and looks ugly ;P.

In the method, it would probably be triggerRegister and boolExprRegister. I did registerTrigger cuz it looked spazzier, and I love being spazzy ;P.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
JASS:
        method fire takes nothing returns nothing
            call TriggerEvaluate(trig[this])
            set eventv = 0
            set eventv = this
        endmethod

The TriggerEvaluate should go before the hardcoded event, since you don't need to retrieve the event id anyway. It would simplify issues with recursion if you want a simple boolexpr to run before everything else.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
JASS:
        method fire takes nothing returns nothing
            call TriggerEvaluate(trig[this])
            set eventv = 0
            set eventv = this
        endmethod

The TriggerEvaluate should go before the hardcoded event, since you don't need to retrieve the event id anyway. It would simplify issues with recursion if you want a simple boolexpr to run before everything else.

Huh? It doesn't matter what order they go in... there is no event id to retrieve...

not changing as order does not matter >.>.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Posted up the 2 other versions of event.

Event Data Version has stuff like FireEventData so that data can be attached to events.

Event Dynamic Version has it all ; O.

These 2 other versions should only be used by mappers. Everyone else please stick with the light version. When he who shall not be named moved his map from j4l's Event to this one, he wanted a way to easily fire events with data as he didn't want to learn how to do recursive data ;P, thus the event data version. When he who shall not be named #2 was checking this out, he wanted a way to register/unregister triggers in events without destroying the triggers like j4l's did, thus the dynamic version.

Data version doesn't slow the thing down at all and is pretty much the same speed as doing it from scratch if you just need to attach an integer.

Dynamic version does slow it down a little bit. If the dynamic triggers are used, it slows it down a lot, but it is still much faster than j4l's Event because of how the loop exitwhen statement is done.

If I catch the data or dynamic versions being used outside of things specific to maps, I shall grab my hammer and smite you all because I'm sure that anyone using a resource doesn't want to be forced into using anything heavier than Event Light.

The overhead on recreating a trigger is smaller than the overhead on evaluating a dynamic trigger in Event Dynamic >: O, so I shall smite you all for your laziness.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->Because the event id does not need to be retrieved, the boolexpr should go first.

There is no event id to retrieve??? : |

private real eventv = 0

Also... it doesn't matter what order they go in as it's the exact same speed either way.

Request Denied ; P.

Future replies regarding this issue will be ignored so as not to spam the thread ^_^.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
I'm using Event instead of function interfaces for my new systems, using FireEventData.

You could do the same in UnitIndexer/UnitEvent/any of your other work that relies on specific data for an event. It would make the API more intuitive, you just have to learn the API for Event and suddenly you know how to use it with everything else.

I'll soon be implementing this technique for my existing libraries which use Event.

Also, I don't think that the "data" variable should be public/settable (just readonly/gettable). It should only be done through fireData.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Needed it to be settable for a specific thing in UnitIndexer back when I used it.

The problem with the data thing is that it only stores an integer. If you want multiple values, then you have to end up allocating a struct.

Think of DamageEvent, which has many event values ;P.

This is why I say only the lightest possible version of Event should be used. The fireData and Dynamic were only really put up for map makers, not for resources.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
UnitIndexer obviously could use it as it's only firing an integer. IsUnitMoving can use it because it's only firing an integer. DamageEvent could use it to store the triggering unit (the one receiving the damage) and use its existing variables for the other factors.

The thing I have in mind is standardization. You have an "Event" library, it's extremely modular, and I see "GetEventData" has the potential to become the same standard as "GetTriggerUnit". Making it readonly would only further serve to further it as a standard.

The main advantages is that it's recursion-proof and prevents spamming different "get" functions for every library. Having that recursion-proof data is a great advantage for anyone who doesn't know how recursion works to make viable use of it in order to construct their own events.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
UnitIndexer can't use fireData (I mean it can, but it shouldn't) because of the mass spam of early index events before the game starts (all for the same indexed unit).


Listen... every type of event has different types of event data. Look at the wc3 API.

Now, it would be nice if we could like latch on to GetTriggerUnit every time we had a triggering unit in an event or w/e ;P, but we can't ;|.

Originally, I had my stuff running on the GetEventData, but there was a reason I stopped that practice =). Different fields, different types of data... it might not even always be an integer ;p.

The data version of event shouldn't be the standard at all >.>. It's only useful if you only have 1 piece of data to store and it happens to be an integer. I put it up there more to show how recursion was done. I even wrote a tutorial on recursion ;P.

I have a firm belief that the only version that should be used for resources (use w/e you want in your maps) is the lightest version possible =).
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
>> Listen... every type of event has different types of event data. Look at the wc3 API.

Yes, but GetTriggerUnit() is a pattern.

>> Different fields, different types of data... it might not even always be an integer ;p.

For UnitIndexer, it will be. And for the systems I'm working on that take advantage of it, it will be. I'm not going to bother with duplicating all this recursion code when I have EventData, as I am not a fan of generated text (one of the biggest reason why I despise function interfaces). Saving on RAM and file size are important factors for me.

>> The data version of event shouldn't be the standard at all >.>. It's only useful if you only have 1 piece of data to store and it happens to be an integer.

That's why there's different "get" functions for different wc3 events, but GetTriggerUnit() is still a standard that points to the primary area of focus. GetEventData() points to the indexed unit id, the moving unit id, the expired missile id, etc.

>> I have a firm belief that the only version that should be used

This statement looks really puffed up, you sound more like Jesus4Lyf or Vexorian, people who refuse to look at new ideas.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
You should really release a version that is not "broken" by Vexorian's map optimizer.

Also, I don't see a point in that 'data' version. If you need to pass any kind of data along with an event, looping through a stack of function pointers would always be a better and a neater solution. Well, I don't see a point in the 'dynamic' version either (and seems like you don't recommend its use as well), but there are people who like dynamic stuff.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
People asked for Data and Dynamic versions. I only recommend the lightest version =).

And what about the first sentence of my post?

I know that you feel uncomfortable with everything that is Wc3C, but if that's the case, then I strongly suggest you stop using JassHelper and wait for Bribe's LuckyParser. You should be aware that the optimizer is one of the greatest modding tools that has been used by pretty much any (remotely) successful map - so making a snippet incompatible with it is not a cool thing, especially when that can easily be changed.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
ehm, that's the way to do custom events for triggers. Not much to be done.
So you will change the inner-workings of your snippet. Don't worry, you won't lose any efficiency.

Rather than asking me to change my snippet, get vexorian's optimizer fixed.
You know very well that Vexorian is inactive, and will probably not return. WC3 modding is doomed to die anyway.

And don't get me wrong, I'm not asking you, rather, I'm advising you. I don't even use any of your snippets.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
So you will change the inner-workings of your snippet. You won't lose any efficiency.

Yes I will, which is why that's never going to happen.

Only other way to do it would be the Dynamic version... hence again why that'll never happen.


Script's staying as is =). If people don't want optimizer to break, then optimizer needs to be fixed. Coders shouldn't have to do worse designs to be compatible with a broken optimizer.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
OK, take this situation:

1. Two triggers register two different events.
2. The first event fires, then the trigger that is run fires the event
that the second trigger is registered to.
3. Due to a wc3 bug, the second trigger will run once, but then
once the thread expires it will run a second time.

I can reproduce this if you want irrefutable proof that it breaks,
but the overall thing is that using the variable event for a modular
task like this is not bug-free. I recommend not even including
TriggerRegisterVariableEvent in the light/data versions and leaving
the heavy version (the one modeled from Jesus4Lyf) as the only
option.

Also, TriggerRegisterVariableEvent does not break with the optimizer
if you use just one string, but it does break when you use it with
concatenation. The solution is to obviously not use concatenation so
avoid private variables.

Yeah, it's time to bring back reasonable-length variable names.
ExecuteFunc works as well as long as you don't use concatenation,
which is not a necessity anyway.

The only thing it honestly breaks is the UnitAlive native which of
course can be replaced by IsUnitType UNIT_TYPE_DEAD and a
GetUnitTypeId!=0. This obviously has a performance hit but it is
editable with an MPQ editor if you really care about it.

So I'm not going to have resource readability completely obliterated
just to save one single native call which can be fixed with a simple
MPQ edit - and how often do you optimize a map? Pretty much just
for when you are doing benchmarks or doing releases, which is not
justifiable to force the authors and users of resources to put up with
a bunch of names that are obviously nonsensical. "q" as the name
of the event variable? It doesn't even make sense.

Go ahead and fix your other resources, you'll get more users who
want to put them in their map that way as it stands.

This is good news for the community and for us because we have
an optimizer that works a lot better than we thought. I successfully
optimized and played a map with GUI Unit Indexer which uses both
ExecuteFunc and TriggerRegisterVariableEvent - there were no
issues because I wasn't concatenating strings.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
For AI natives the only things I've seen you use are GetUnitGoldCost,
GetUnitWoodCost and UnitAlive.

And honestly, an MPQ editor to just copy and paste those in on the
compile war3map.j file is worth it for your case. Don't destroy every
public resource with obliterated variable names just because of this
one little thing. Please. Let's take this as an opportunity to bring the
quality level of scripts back to normal, readable levels, and for your
extreme tests that you perform you'll get better performance from
the optimizer so you don't run into situations where your UnitIndexer
variable names are prefixed like "UnitIndexer___q" instead of letting
the optimizer shorten it to something like "uQ".

You're actually making it worse for yourself when you look at it from
that angle.
 
Top