• 🏆 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

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Now that I think of it you could also move the creation of the trigger into the "register" method, because some events never get registered (like only using UnitEvent for reincarnation start/end, for example).

So it would look more like this:

JASS:
        static method create takes nothing returns thistype
            set count = count + 1
            return count
        endmethod
        method register takes boolexpr c returns nothing
            if (null==bc[this]) then
                set bc[this]=c
                set trig[this] = CreateTrigger()
            else
                set bc[this]=Or(bc[this],c)
                call TriggerClearConditions(trig[this])
            endif
            call TriggerAddCondition(trig[this], bc[this])
        endmethod
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Funny, i thought validated submissions had to be leakless.

EDIT : Nevermind i thought there was an unregister method.

About the triggerconditions that doesn't mean it uses the same memory, it could be more or less, but yes my first sentence makes no sense.
 
Level 15
Joined
Jul 6, 2009
Messages
889
JASS:
        method register takes boolexpr c returns nothing
            if (null==bc[this]) then
                set bc[this]=c
                set trig[count] = CreateTrigger()
            else
                set bc[this]=Or(bc[this],c)
                call TriggerClearConditions(trig[this])
            endif
            call TriggerAddCondition(trig[this], bc[this])
        endmethod

The line set trig[count] = CreateTrigger(), is the array value supposed to be count?
 

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,534
Nes, if your standard and data heavy Event libraries have different API's (IE the data heavy one supports GetEventData()), you should name the libraries differently so that systems using the data heavy one can require EventEx (or something), thus not confusing your client with cryptic error messages if they're using the standard Event library.

Cheers,
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
I've been lately working on destructable and item indexer and everyone knows, that events do not fire before map init. I went looking at wc3c/the helper for answers and possible solutions - outcome: each library has it's own preload stuff (if it does cover mentioned issue). UnitIndexer isn't exception.

I guess we should talk about preloading events so we can get best possible way to fix the issue. Personaly, I have had custom public struct preloader to do the job for me. Problem was, each time I wanted to implement Register<...>/TriggerRegister<...> functions, it forced me to write up additional things. Thus, such struct can't be a resource since user still has to manualy change some stuff.
So.. I started thinking about kinda extension for Event to cover that part. The key in my approach was to get into the core of event firing.. the fire method.
JASS:
library PreLoader

    //! textmacro PRELOADER
    private static boolean hasPreloaded=false
    static thistype preloader=0
    static integer array eval
    private boolexpr bexpr

    static method executePreload takes nothing returns nothing
        local trigger t=CreateTrigger()
        local thistype this=1
        set hasPreloaded=true
        loop
            exitwhen integer(this)>w
            set preloader=this
            if eval[this]>0 and this.bexpr!=null then
                call TriggerClearConditions(t)
                call TriggerAddCondition(t,this.bexpr)
                call TriggerEvaluate(t)
            endif
            set this=this+1
        endloop
        call DestroyTimer(GetExpiredTimer())
        call DestroyTrigger(t)
        set t=null
    endmethod
    static method addPreloader takes code c, thistype ev returns nothing
        set ev.bexpr=Condition(c)
    endmethod
    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(),0,false,function thistype.executePreload)
    endmethod
    //! endtextmacro PRELOADER

    module PreLoadData
        static method preloadEvent takes nothing returns boolean
            local integer this=1
            local Event ev=Event.preloader
    endmodule
    module PreLoadSet
            loop
                exitwhen this > Event.eval[ev]
    endmodule
    module PreLoadNull
                call ev.fire()
                set this=this+1
            endloop
    endmodule
    module PreLoadEnd
            return false
        endmethod
    endmodule

endlibrary
and the fire method changes:
JASS:
        method fire takes nothing returns nothing
            set q=0
            set q=this
            call TriggerEvaluate(e[this])
            static if LIBRARY_PreLoader then
                if not thistype.hasPreloaded then
                    set eval[this] = eval[this]+1
                endif
            endif
        endmethod
Now, lets bring the fact that indexers start indexing from 1. Above lib with consideration of such fact allows typical indexer to avoid implementation of custom preload member and instead offers:
JASS:
        implement PreLoadData
            local integer prev = eventIndex
        implement PreLoadSet
            set eventIndex = this
        implement PreLoadNull
            set eventIndex = prev
        implement PreLoadEnd
With addition of (example):
JASS:
    call Event.addPreloader(function thistype.preloadEvent, ItemIndex.INDEX)
at onInit method for given library.

Such method ignores the division on trigger/condition part since it calls .fire() method.
Next, there is basicaly no gain, if you use this for just UnitIndexer for example. However, if you'd go for additional one, like ItemIndexer then it happily cuts the code and makes things look cleaner.

Once again, it's just a suggestion and since conversations can bring nothing but profit I'd like to know ur opinions about that issue.
There are few options available:
a) each lib implements it's own stuff to deal with this
b) create a lib which covers this on it's own (but how could you acces the event data then)?
c) interference with Event itself

Edit: Additional variant of API from previous lib:
JASS:
    //! textmacro PRELOADER takes DATA
    static method preloadEvent takes nothing returns boolean
        local integer this=next[0]
        local Event ev=Event.preloader
        local integer save = $DATA$
        loop
            exitwhen 0==this
            set $DATA$ = this
            call ev.fire()
            set this=next[this]
        endloop
        set $DATA$ = save
        return false
    endmethod
    //! endtextmacro
Uses the linked list from library directly. Now, you can just type: //! runtextmacro PRELOADER("o") in $DATA$ place type global event index name.
That variant requires no changes in fire method.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Ok, I'm redoing all of my event libs

Event
DynamicEvent (not out yet)
PriorityEvent
DynamicPriorityEvent


I've discovered a few interesting things

#1: a way to remove trigger conditions without crashing the trigger and without timers or anything special, just a plain TriggerRemoveCondition

#2: a way to evaluate triggers that's even faster than a plain list of TriggerAddConditoin

Check it out here

JASS:
struct PyramidBuilder extends array
    private static boolexpr array o
    static method register takes trigger T, integer i, boolexpr base returns nothing
        local integer m = i
        call populate(i, base)
        loop
            set m = condense(m)
            exitwhen m == 0
        endloop
        call TriggerAddCondition(T, o[0])
    endmethod
    private static method populate takes integer i, boolexpr base returns nothing
        loop
            exitwhen 0 == i
            set i = i - 1
            set o[i] = base
        endloop
    endmethod
    private static method condense takes integer i returns integer
        local integer m = 0
        local integer n = i/2
        set i = i - 1
        loop
            exitwhen m >= i
            set o[m] = Or(o[m], o[i])
            set m = m + 1
            set i = i - 1
        endloop
        if (m == i) then
            return m
        endif
        return n
    endmethod
endstruct

Less handles are generated and it evaluates more quickly.

edit
A look at Event, underway. Not working atm, but I'll hopefully finish it over the weekend =)

JASS:
/*
static method create takes nothing returns thistype
method fire takes nothing returns nothing
method register takes boolexpr func returns thistype
method unregister takes nothing returns nothing
method destroy takes nothing returns nothing
*/

library Event /* v3.0.0.0
*************************************************************************************
*
*   * uses *
*
*       * Alloc              *          hiveworkshop.com/forums/jass-resources-412/snippet-alloc-alternative-221493/
*
************************************************************************************
*
*   struct Event extends array
*
*       readonly boolexpr functionList
*
*       static method create takes nothing returns thistype
*       method register takes boolexpr c returns nothing
*       method fire takes nothing returns nothing
*
************************************************************************************/
    private keyword FunctionList
    private struct EventContainer extends array
        implement LinkedListNode
        
        boolexpr main
        boolexpr sub
        
        method destroy takes nothing returns nothing
            local thistype node = first
        
            loop
                exitwhen node == 0
                call DestroyBoolExpr(node.sub)
                set node.sub = null
                set node = node.next
            endloop
            
            set main = null
            
            call clear()
        endmethod
    endstruct
    private struct EventContainerBuilder extends array
        private static boolexpr array builder
        private static integer count = 0
        private static EventContainer owner
        
        private static method condense takes nothing returns nothing
            local integer m = 0
            local integer i = count - 1
            local integer n = i/2
            loop
                exitwhen m >= i
                set builder[m] = Or(builder[m], builder[i])
                set builder[i] = null
                set owner.add().sub = builder[m]
                set m = m + 1
                set i = i - 1
            endloop
            if (m == i) then
                set count = m
            else
                set count = n
            endif
        endmethod
        
        static method build takes EventContainer owner returns nothing
            local FunctionList node = FunctionList(owner).first
            set thistype.owner = owner
            
            set count = 0
            loop
                exitwhen node == 0
                set builder[count] = node.container
                set count = count + 1
                set node = node.next
            endloop
            
            loop
                exitwhen count == 0
                call condense()
            endloop
            
            set owner.main = builder[0]
            set builder[0] = null
        endmethod
    endstruct

    private struct FunctionList extends array
        implement LinkedListNode
    
        readonly boolexpr container
        
        method register takes boolexpr func returns nothing
            local thistype node = first
            
            loop
                exitwhen node.container == null
                set func = Or(node.container, func)
                set node.container = null
                set node = node.next
            endloop
            
            if (node == 0) then
                set node = add()
            endif
            
            set node.container = func
        endmethod
        
        method build takes nothing returns nothing
            call EventContainerBuilder.build(this)
        endmethod
        
        method destroy takes nothing returns nothing
            local thistype node = first
            
            if (node == 0) then
                return
            endif
            
            /*
            *   The first node always contains a plain function
            *   Don't destroy it
            */
            set node = node.next
        
            loop
                exitwhen node == 0
                if (node.container != null) then
                    call DestroyBoolExpr(node.container)
                    set node.container = null
                endif
                set node = node.next
            endloop
            
            call clear()
        endmethod
    endstruct
    
    private struct Allocator extends array
        implement Alloc
    endstruct
    struct Event extends array
        private trigger container
        private triggercondition containerCondition
        private boolean update
    
        static method create takes nothing returns thistype
            local thistype this = Allocator.allocate()
            
            set container = CreateTrigger()
            
            return this
        endmethod
        
        method fire takes nothing returns nothing
            if (update) then
                if (containerCondition != null) then
                    call TriggerRemoveCondition(container, containerCondition)
                endif
                call EventContainer(this).destroy()
                call FunctionList(this).build()
                if (EventContainer(this).main != null) then
                    set containerCondition = TriggerAddCondition(container, EventContainer(this).main)
                else
                    set containerCondition = null
                endif
                
                set update = false
            endif
        
            call TriggerEvaluate(container)
        endmethod
        
        method register takes boolexpr func returns thistype
            set update = true
            call FunctionList(this).register(func)
            return 0
        endmethod
        
        method unregister takes nothing returns nothing
            set update = true
        endmethod
        
        method destroy takes nothing returns nothing
            set update = false
            
            if (containerCondition != null) then
                call TriggerRemoveCondition(container, containerCondition)
                set containerCondition = null
            endif
            
            call EventContainer(this).destroy()
            call FunctionList(this).destroy()
            
            call DestroyTrigger(container)
            set container = null
            
            call Allocator(this).deallocate()
        endmethod
    endstruct
endlibrary

With the new design, I can make all of the libs dynamic and they'll be faster :D. This means that these libs will be faster than triggers with spammed TriggerAddCondition, plus they'll allow you to do TriggerRemoveCondition. Win win? Yes ;D.
 
Last edited:
Level 16
Joined
Aug 7, 2009
Messages
1,403
Out of curiosity, will the new Event version simply be changed to use Triggers instead of triggers? I haven't really worked on my map lately, but I've been considering updating it with this, because why not. Everything is based on Event after all, so I pretty much only need to change it.

I'll do it nevertheless even if the new version of Event does not come out before the release, but I'd like to know if you have further plans with it (well, it's not like it was needed or possible to further optimize it, and I don't need more functionality, so yea).
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I'm planning to move everything over to Trigger/PriorityTrigger. Event is kind of deprecated, but I'll keep it up so as not to break everything else. Behind the scenes, it will use Trigger, and I'll provide access to the Trigger so that users can use all of the cool new features of Trigger ;). PriorityEvent will be the same deal.


Nothing in the API is going to break.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
JASS:
        private static trigger eventContainer
        method fire takes nothing returns nothing
            call TriggerEvaluate(eventContainer)
        endmethod

Explain to me how this doesn't break in the case where you have multiple events. How is the game engine going to know which event you're firing when you use one trigger for the entire Event library?

EDIT: WHAT THE HECK where did the @JASS@ highlighting go???
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
JASS:
        private static trigger eventContainer
        method fire takes nothing returns nothing
            call TriggerEvaluate(eventContainer)
        endmethod

Explain to me how this doesn't break in the case where you have multiple events. How is the game engine going to know which event you're firing when you use one trigger for the entire Event library?

Yup, it actualy breaks the entire system. You just need to remove the "static" keyword and it works fine. I wanted to report it when I moved from Advent to Event, but totally forgot about it.

I'm kinda curious what the new version will look like now that we have Trigger
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Even with the fix:

JASS:
        method register takes boolexpr func returns nothing
            if (functionList == null) then
                set functionList = func
            else
                set functionList = Or(functionList, func)
            endif
            call TriggerAddCondition(eventContainer, func)
        endmethod

If you always do the "Or" function with the previous conditions, when you add a second registry to the event, the first registry will fire twice, because you have redundancy now. The current version of Event is still broken.

This is why you should always test your resources and encourage others to test your resources, especially even if you're Nestharus.
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
Even with the fix:

JASS:
        method register takes boolexpr func returns nothing
            if (functionList == null) then
                set functionList = func
            else
                set functionList = Or(functionList, func)
            endif
            call TriggerAddCondition(eventContainer, func)
        endmethod

If you always do the "Or" function with the previous conditions, when you add a second registry to the event, the first registry will fire twice, because you have redundancy now. The current version of Event is still broken.

This is why you should always test your resources and encourage others to test your resources, especially even if you're Nestharus.

Well, it's only for the list (that I got rid of anyways) - it still works fine as only the passed boolexpr gets registered to the trigger. But ehm yea, I don't get how no one else reported these before. I didn't, because I'm not really active on the forums, especially not in the JASS section, but still, there're quite a few Nes fanboys around, I don't get how they didn't report it.
 
Top