[Snippet] RegisterPlayerUnitEvent

This system was made to replace that cumbersome GTrigger by Jesus4Lyf.
Special thanks to Bribe, azlier and BBQ :)

JASS:
/**************************************************************
*
*   RegisterPlayerUnitEvent
*   v5.1.0.1
*   By Magtheridon96
*
*   I would like to give a special thanks to Bribe, azlier
*   and BBQ for improving this library. For modularity, it only 
*   supports player unit events.
*
*   Functions passed to RegisterPlayerUnitEvent must either
*   return a boolean (false) or nothing. (Which is a Pro)
*
*   Warning:
*   --------
*
*       - Don't use TriggerSleepAction inside registered code.
*       - Don't destroy a trigger unless you really know what you're doing.
*
*   API:
*   ----
*
*       - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
*           - Registers code that will execute when an event fires.
*       - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
*           - Registers code that will execute when an event fires for a certain player.
*       - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
*           - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
    globals
        private trigger array t
    endglobals
    
    function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
        local integer i = GetHandleId(p)
        local integer k = 15
        if t[i] == null then
            set t[i] = CreateTrigger()
            loop
                call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
                exitwhen k == 0
                set k = k - 1
            endloop
        endif
        call TriggerAddCondition(t[i], Filter(c))
    endfunction
    
    function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
        local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
        if t[i] == null then
            set t[i] = CreateTrigger()
            call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
        endif
        call TriggerAddCondition(t[i], Filter(c))
    endfunction
    
    function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
        return t[GetHandleId(p)]
    endfunction
endlibrary

Here's a vanilla Jass version:

JASS:
//**************************************************************
//*
//*   RegisterPlayerUnitEvent (Vanilla Jass)
//*   v5.1.0.1
//*   By Magtheridon96
//*
//*   I would like to give a special thanks to Bribe, azlier
//*   and BBQ for improving this library. For modularity, it only 
//*   supports player unit events.
//*
//*   Functions passed to RegisterPlayerUnitEvent must either
//*   return a boolean (false) or nothing. (Which is a Pro)
//*
//*   Implementation:
//*   ---------------
//*
//*       - Copy all this script into a new trigger called "RegisterPlayerUnitEvent Jass"
//*       - Create a trigger array variable called RPUE.
//*       - Done.
//*
//*   Warning:
//*   --------
//*
//*       - Don't use TriggerSleepAction inside registered code.
//*       - Don't destroy a trigger unless you really know what you're doing.
//*
//*   API:
//*   ----
//*
//*       - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
//*           - Registers code that will execute when an event fires.
//*       - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
//*           - Registers code that will execute when an event fires for a certain player.
//*       - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
//*           - Returns the trigger corresponding to ALL functions of a playerunitevent.
//*
//**************************************************************
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
    local integer i = GetHandleId(p)
    local integer k = 15
    if udg_RPUE[i] == null then
        set udg_RPUE[i] = CreateTrigger()
        loop
            call TriggerRegisterPlayerUnitEvent(udg_RPUE[i], Player(k), p, null)
            exitwhen k == 0
            set k = k - 1
        endloop
    endif
    call TriggerAddCondition(udg_RPUE[i], Filter(c))
endfunction

function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
    local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
    if udg_RPUE[i] == null then
        set udg_RPUE[i] = CreateTrigger()
        call TriggerRegisterPlayerUnitEvent(udg_RPUE[i], pl, p, null)
    endif
    call TriggerAddCondition(udg_RPUE[i], Filter(c))
endfunction

function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
    return udg_RPUE[GetHandleId(p)]
endfunction
    
function InitTrig_RegisterPlayerUnitEvent_Jass takes nothing returns nothing
endfunction

Feel free to comment..
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,453
I haven't read your code, just your functions descriptions.
Could you add an "EVERY" constant ? (a "random" negative value seems fine)

I mean for orders sometimes we don't need to catch a specific order, just any order.
Sure we could still create a trigger and add it this event, like the old good way.

It would also be useful for the other events, but orders are the most obvious one.
 
Level 17
Joined
Apr 27, 2008
Messages
2,453
Well i'm sure it is useful for order events, but it could also be for other events, that's why i'm suggesting a constant integer.

JASS:
constant integer CommonEvent_ANY = -42

JASS:
...

call registerBeginCast(CommonEvent_ANY,<boolexpr>) // any ability
call registerItemUse(CommonEvent_ANY,<boolexpr>) // any item

Ofc the constant name could (must ?) be improved.
Alternatively you could also create several constants, one for each event "type" (order, item, ...) but i personnaly don't like this idea.
 
Level 17
Joined
Apr 27, 2008
Messages
2,453
;_;
If you list them, it would be much easier for me >:p

But it would be less funny for us :/

Come to think of it Troll-Brain, it would be more efficient if I keep the system the way it is and just add an "AnyOrder" event.
That way, The entire API would inline :D

Like i said order events are only one example ...
And seriously who care about register event inlines, it's just better to work on the API usage. (plz refrain your speed-freak)
 
Level 38
Joined
Sep 26, 2009
Messages
8,463
This line is awkward:

call UD.remove(i) // To avoid bugs after the handle id is recycled.

The unit could die multiple times :/

I think for death events you should let users just use Nestharus' Unit
Event.

This event-player-leave stuff is also strange to include in this library.
>> call TriggerRegisterPlayerEvent(c,p,EVENT_PLAYER_LEAVE)

I will show you how to make this a lot shorter code (works for all
playerunitevents):

JASS:
function RegisterPlayerUnitEvent takes boolexpr condition, playerunitevent pu returns nothing
    globals
        private trigger array trigs
    endglobals
    local integer id = GetHandleId(pu)
    if trigs[id] == null then
        set trigs[id] = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(trigs[id], pu)
    endif
    call TriggerAddCondition(trigs[id], condition)
endfunction
 
Last edited:
Level 21
Joined
Nov 14, 2008
Messages
3,256
I am the public of the JASS section.

Oo loooots of tables, I'm happy that it's not Vex's table. Anyway I'm very thankful for this as I hate the stupid GT (have modified it myself just because of that matter). Only thing that drives me mad but will probably not affect anyone else is the exitwhen i>15 but I can make that constant myself and filter out nonplaying players aswell (so what I mean is that you should keep it). Brilliant.

And the effect event should be in, just static if it out if the player already used Bribe's. (Or maybe merge with Bribe's? Sorry Bribe :D)
 
It's actually 12 am, so maybe I will go to bed soon since school starts in 2 weeks >:p
I'm trying to get this to be very efficient and start only 1 thread per event.
I'm using an instance of Table as an "EventMap".
Each player will have an instance of the Event struct for each playerunitevent.
There are only 2 player events I'm going to include here, so I'm doing something different for them :p
I'm also going to include a "0.00 second elapsed game time event".

edit
Updated.
 
Last edited:
Level 38
Joined
Sep 26, 2009
Messages
8,463
Mag, you did "registerPlayerUnitEvent" wrong, the boolexpr is pretty
useless because you get get data like GetSpellAbilityId() and the like.
It only recognizes a filter unit.

You also don't need a Table, because player unit events only go up to
270 or something on the handle IDs, so you can use arrays. And you
don't need a Table for the player id because that's going to be 0-15
which is also fine of course. Trust me, the way I set it up in my first
post is the shortest and sweetest way to do it ;)
 
Level 38
Joined
Sep 26, 2009
Messages
8,463
You still don't need Table for that.

You also are only adding the condition the first time
you register the player unit event. It needs to register
even if it's the 2nd, 3rd, 4th, etc.

Also, you would use way fewer handles if you didn't
wrap the registerPlayerUnitEvent from within the
registerAnyPlayerUnitEvent, because you're creating
a trigger, event and a condition per player now, instead
of just an event per player.

Just do registerAnyPlayerUnitEvent like I showed you,
and don't inline TriggerRegisterAnyUnitEventBJ it is
wasteful to re-type the same thing because it makes
your map file size bigger.
 
Level 17
Joined
Apr 27, 2008
Messages
2,453
This registers an event that will fire when
* the game starts. (0.00 Elapsed Game Time)
* Only use this function inside module initializers.

I'm aware about the vJass initializers order but i don't get why you say that, since it's a timer callback.
Indeed that doesn't matter in which initializer it's called, coz it will fire after all other initializers regardless in which one you used it.
 
Level 9
Joined
May 27, 2009
Messages
494
if only damageEvent's extension is stable :/ i'll probably use it, but anyways managed to replace those libraries required by damage (AIDS and Event) to the new ones

but please add a non-struct api to this, so i can put it up with tesh

Like
call CE_Register blah blah blah ()

well i may add some of the apis myself but i like others to benefit from it (LOL)
 
Level 38
Joined
Sep 26, 2009
Messages
8,463
Detecting a player-leave event is pretty irrelevant from TriggerRegisterAnyUnitEventBJ.

I recommend having two resources because I don't want to couple this pointless "player leaving" with just simple playerunitevent registry.

If this resource was just the function I showed you (for which it is also the most modular anyway) then it should be approved without second thought!

From such a thing I could make a good SpellEvent library, similar to the one on wc3c I guess but obviously without the overhead of extra features and overhead that are mostly useless, and then deprecate SpellEffectEvent.
 
Level 38
Joined
Sep 26, 2009
Messages
8,463
You don't have to add the struct just because you want the code to seem longer - sometimes less is more.

Personally I would do it like this:

JASS:
library RegisterPlayerUnitEvent
    function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
        local integer i = GetHandleId(p)
        local boolexpr b = Filter(c)
        globals
            private trigger array t
        endglobals
        if null == t[i] then
            set t[i] = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t[i], p)
        endif
        call TriggerAddCondition(t[i], b)
        set b = null
    endfunction
endlibrary

The reason for using the code argument is because I don't like having to
type "Filter()" for every function wrapper. It will still throw a syntax error
if the code forgets to return a boolean, so it's still the same effectiveness.
People often forget to wrap the boolexpr in a Filter() call as well, and you
shouldn't punish people just because they are forgetful.

Another argument for using "code", the more times this function is called,
the less the map KB size will be because there will be fewer cases where
you find the text "Filter()".

The resource name should also be changed to that of RegisterPlayerUnitEvent.

And yes you can inline a globals block into a function block. It's really weird
but hey.
 
Level 6
Joined
Jun 20, 2011
Messages
249
First of all great reasource, it really takes a lot of useless lines out of the code.

Slightly off-topic: at what circumstances is it better to store boolexpr in a local var? I've seen people (nestharus) first storing them inside vars, using them, and then nulling them, does this cause a leak? is it only safe not to do in on initialization calls?
 
Level 17
Joined
Apr 27, 2008
Messages
2,453
Usually, you null every local handles, even if you will never destroy them it's still better, just because of a lame jass bug (note that function arguments are not concerned by the bug).
http://www.thehelper.net/forums/sho...n-the-native-CreateUnit?p=1373796#post1373796
http://www.thehelper.net/forums/sho...n-the-native-CreateUnit?p=1373985#post1373985

Now Filter(),Condition() work quite like strings, they are created only one time and then re-used.
You can destroy them (not like strings) but then it means that all functions which used them before won't work anymore, such as a boolexpr of a TriggerRegister.
We have also the special case of And(), Or() which both create a new boolexpr each time it's used (can be used for advanced stuff).

I suppose Nestharus use a variable for a speedfreak purpose and/or to avoid to write each time Filter(function ...).
Though, Condition() is implicit in vJass in some cases ( can't remember which ones exactly but trigger conditions doesn't need an explicit Filter/Condition() )

TriggerAddCondition(trig,function MyFunction) in vJass will be converted to TriggerAddCondition(trig,Condition(function MyFunction))

And no you don't need to use them only on initialization calls, and you should never destroy a boolexpr, unless you are sure it's not currently used somewhere else.
 
Level 8
Joined
Oct 3, 2008
Messages
367
You know, I think there's a way to make this even faster. If I remember from the KT2 benchmarks and various studies, a trigger with lots of conditions executes quite a lot slower than a trigger with one condition that's actually a big boolexpr made by combining other boolexprs with And.

If I recall, KT2 used the trick to get some extra speed. It'll be slower on initialization but should make execution time faster, which is always good, no? This modification should be enough:

JASS:
library RegisterPlayerUnitEvent // Special Thanks to Bribe
    globals
        private trigger array t
        private boolexpr array b
    endglobals
    function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
        local integer i = GetHandleId(p)
        if null == t[i] then
            set t[i] = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t[i],p)
        endif
        if null == b[i] then
            set b[i] = Filter(c)
        else
            call TriggerClearConditions(t[i])
            set b[i] = And(b[i],Filter(c))
        endif
        call TriggerAddCondition(t[i],b[i])
    endfunction
endlibrary
 
Level 38
Joined
Sep 26, 2009
Messages
8,463
You are right. It just needs to be an "Or" because dummy conditions return false.

JASS:
library RegisterPlayerUnitEvent // Special Thanks to Bribe
    globals
        private trigger array t
        private boolexpr array b
    endglobals
    function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
        local integer i = GetHandleId(p)
        if null == t[i] then
            set t[i] = CreateTrigger()
            call TriggerRegisterAnyUnitEventBJ(t[i], p)
            set b[i] = Filter(c)
        else
            call TriggerClearConditions(t[i])
            set b[i] = Or(b[i], Filter(c))
        endif
        call TriggerAddCondition(t[i], b[i])
    endfunction
endlibrary
 
Well, thank you azlier :)
And you too Bribe ^^

I once had a similar idea (using Or to combine boolexprs), but I was attempting to use it in the wrong field (Events)

edit
Oh and I'm going to consider inlining TriggerRegisterAnyUnitEventBJ since the point of NOT-inlining
it is to save file-size, but anyone using this wouldn't have to use that BJ in the first place!
 
Level 38
Joined
Sep 26, 2009
Messages
8,463
I'm not going to complain if it's inlined for this resource, it would look more official if it was inlined anyway.

Maybe something like this?:

JASS:
library RegisterPlayerUnitEvent // Special Thanks to Bribe
    globals
        private trigger array t
        private boolexpr array b
    endglobals
    function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
        local integer i = GetHandleId(p)
        local integer j = 16
        if null == t[i] then
            set t[i] = CreateTrigger()
            loop
                set i = i - 1
                call TriggerRegisterPlayerUnitEvent(t[i], Player(j), p, null)
                exitwhen 0 == i
            endloop
            set b[i] = Filter(c)
        else
            call TriggerClearConditions(t[i])
            set b[i] = Or(b[i], Filter(c))
        endif
        call TriggerAddCondition(t[i], b[i])
    endfunction
endlibrary
 
Top