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

[vJASS] [Library/Package] Pseudo-Var Event

Greetings, HIVERS.

This is a library that goes beyond the conventional functionality of TriggerRegisterVariableEvent by mimicking its' interface and providing a safe unregistration of instances. It can also fire all triggers related to the event if possible. This is directed to vJASSers, casual and extreme alike.

If you need to use the Hashtable version, just set USE_HASH to true. If you want safety in recursion depth, you can set Event.SAFETY_ENABLED to true.

By default, the fire and run method in the struct Event indirectly cheat the op limit.


A clear method operator that will clear the designated trigger group related to the real instance of the event instance. (Re-implemented in Wurst)


vJASS

Wurst


JASS:
library PseudoVarEvent requires /*

    */  AllocationAndLinks, /*
    * - For the allocation and creation of the necessary linked lists
    *
    */  optional Table /*
    * - Bribe, https://www.hiveworkshop.com/threads/snippet-new-table.188084/
    * - Allows USE_HASH to take effect.
    *
    */  optional UtilityFunctions /*
    * - Allows a break line in the console, but otherwise useless.
    *   Ignore if possible.
    *
    */

    /*
    *  /================/
    *  /Pseudo Var Event/
    *  /v.1.2.0.0       /
    *  /================/
    *
    *       - A library for easily creating new Events and allowing the execution of triggers through those Events.
    *       Much like jesus4lyf's event, but with the ability to act as a priority queue if configured well enough.
    *
    *   ====================================================//
    *   What is Pseudo Var Event?                           //
    *   ====================================================//
    *
    *       - It is a library which allows one to easily create a trigger group that will fire when an Event instance
    *       is fired or ran. Registering the individual triggers won't be that much of a hassle, since it is regulated
    *       via the struct EventTrigs' register method. Unregistering is a breeze as well, which allows triggers in the
    *       trigger group to no longer fire.
    *
    *   ====================================================//
    *   Application Programming Interface                   //
    *   ====================================================//
    *
    *       struct Event
    *
    *       important variables:
    *           readonly static Event array current
    *               -   The array that holds the necessary Event.
    *           readonly static real array real
    *               -   The array that holds the necessary real instance.
    *           readonly static integer instruction
    *               -   The integer that tells the handler trigger (Event.EXEC) which function to call.
    *           readonly static integer count_parallel
    *               -   The integer that tells how many times the Event thread has been called.
    *                   (Either through run() or op_fire(real r)
    *           readonly static integer count
    *               -   The integer that holds the depth of recursion.
    *
    *           readonly static constant trigger EXEC
    *               -   The handler trigger that calls the appropriate methods in the struct EventTrigs
    *                   depending on the instruction sent.
    *
    *       methods:
    *           private static method remap()
    *               - remaps the currently firing Event and its' equivalent real.
    *           method create() returns Event
    *               - constructor method.
    *           method run()
    *               - Fires all triggers related to that Event. Checks if a trigger has been already been fired once.
    *           method op_fire(real r)
    *               - Fires all triggers related to the real instance of the Event.
    *
    *       struct EventTrigs
    *   
    *       important variables:
    *       
    *       methods:
    *           private static method event_get(Event e) returns EventTrigs
    *               - Returns the head of the Event instance. (Acts as a wrapper for EventTrigs(e).head)
    *                 O(1) complexity
    *           private static method real_get(Event e, real r) returns EventTrigs
    *               - Returns the head of the real instance of the Event instance. (Internally calls event_get(e))
    *                 Potential O(n) complexity
    *           private static method trigger_get(Event e, real r, trigger t) returns EventTrigs
    *               - Returns the unique trigger of the real instance of the Event instance. (calls real_get(e, r))
    *                 Potential O(2n) complexity
    *           static method getInstance(Event e, real r, trigger t) returns EventTrigs
    *               - Wrapper for trigger_get(e, r, t). Also adds backwards compatibility.
    *           private method unlink()
    *               - Removes the links of a certain EventTrigs instance.
    *           method unregister()
    *               - Destructor of an EventTrigs instance. (Internally calls unlink)
    *           private method link(Event e, real r, trigger t)
    *               - Adds the links for a certain trigger instance.
    *           static method register(Event e, real r, trigger t)
    *               - Constructor of an EventTrigs instance. (If instance exists, acts as a wrapper for
    *                 trigger_get(e, r, t))
    *           method op_eventid(Event e)
    *               - Remaps the instance to another Event.
    *           method op_event(real r)
    *               - Remaps the instance to another real instance of the Event.
    *
    *           module generated methods:
    *               static method fire(Event e, real r)
    *                   - Is called when op_(Event(instance).fire = r) is called. (See Event.op_fire for more
    *                     details)
    *               static method all_fire(Event e)
    *                   - Is called when Event.run() is called. (See Event.run for more details)
    *               static method remove(Event e)
    *                   - Is called when Event.destroy() is called. It destroys all EventTrigs instances related to
    *                     the Event instace.
    *
    *           imported modules:
    *               module EventTrigs_all_fire
    *               module EventTrigs_fire
    *               module EventTrigs_remove
    *                   -   These modules are implemented to maximize readability in the main struct.
    *                       Very difficult to read and understand, but more important to not touch!
    *
    *               module Alloc_MyPad or AllocH
    *               module DoubleLink(H)_event
    *               module DoubleLink(H)_real
    *               module DoubleLink(H)_trigger
    *                   - These modules are responsible for making everything in the struct work more
    *                     properly such as the addition of an allocator and deallocator method, of which
    *                     the custom implementation is favored over the former.
    *
    *                     The DoubleLink modules are what essentially enables easy link creation.
    *
    *       globals:
    *           private constant boolean USE_HASH
    *               -   Affects the struct EventTrigs.
    *           private constant boolean ALLOW_DEBUG
    *               -   Will only print messages if already in debug mode.
    *
    *       functions:
    *           LineBreak()
    *               -   Prints an empty line.
    *           constant GetEvent() returns Event
    *               - Returns the current Event (Event.current[count])
    *           constant GetRealEvent() returns real
    *               - Returns the current real instance of the Event (Event.real[count])
    */
 
    globals
        private constant boolean USE_HASH = true
        private constant boolean ALLOW_DEBUG = false
    endglobals
 
    static if not LIBRARY_UtilityFunctions then
        function LineBreak takes nothing returns nothing
            call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, " ")
        endfunction
    endif
 
    struct Event extends array
        implement Alloc_MyPad
   
        //  For static depth
        private static constant boolean SAFETY_ENABLED = false
        private static constant integer FIRE_WARNING = 16
        private static constant integer FIRE_LIMIT = 32
   
        //  For the execution
        readonly static thistype array current
        readonly static real array real
        readonly static integer instruction  = 0
   
        readonly static integer count_parallel = 0
        readonly static integer count = 0
   
        //  For the handler-triggercondition
        readonly static constant trigger EXEC = CreateTrigger()
   
        private static method remap takes nothing returns nothing
            set current[count] = current[0]
            set real[count] = real[0]
       
            set count = count - 1
            if count <= 0 then
                set count = 0
                set count_parallel = 0
            endif
        endmethod
   
        method destroy takes nothing returns nothing
            set count = count + 1
            set instruction = 3
       
            set current[count] = this
       
            call TriggerEvaluate(EXEC)
       
            call deallocate()
            call remap()
        endmethod
   
        method run takes nothing returns nothing
            set count = count + 1
            set count_parallel = count_parallel + 1
            set instruction = 2
       
            set current[count] = this
       
            static if not thistype.SAFETY_ENABLED then
                call TriggerEvaluate(EXEC)
            else
                if count > thistype.FIRE_LIMIT then
                    debug call BJDebugMsg("Error: Depth exceeded fire limit!")
                else
                    if count > thistype.FIRE_WARNING then
                        debug call BJDebugMsg("Warning: Recursive call of fire limit detected!")
                        debug call BJDebugMsg("Please refrain from calling a run method within the firing of an Event")
                        debug call BJDebugMsg("Depth of recursion: " + I2S(count))
                        debug call BJDebugMsg("========================")
                        debug call BJDebugMsg(" ")
                    endif
                    call TriggerEvaluate(EXEC)
                endif
            endif
            call remap()
        endmethod
   
        method operator fire= takes real r returns nothing
            set count = count + 1
            set count_parallel = count_parallel + 1
            set instruction = 1
       
            set current[count] = this
            set real[count] = r
       
            static if not thistype.SAFETY_ENABLED then
                call TriggerEvaluate(EXEC)
            else
                if count > thistype.FIRE_LIMIT then
                    debug call BJDebugMsg("Error: Depth exceeded fire limit!")
                else
                    if count > thistype.FIRE_WARNING then
                        debug call BJDebugMsg("Warning: Recursive call of fire limit detected!")
                        debug call BJDebugMsg("Please refrain from calling a fire method within the firing of an Event")
                        debug call BJDebugMsg("Depth of recursion: " + I2S(count))
                        debug call BJDebugMsg("========================")
                        debug call BJDebugMsg(" ")
                    endif
                    call TriggerEvaluate(EXEC)
                endif
            endif
            call remap()
        endmethod
   
        static method create takes nothing returns thistype
            return allocate()
        endmethod
   
        static method isWarning takes nothing returns boolean

        static if thistype.SAFETY_ENABLED then
            return count < FIRE_WARNING
        else
            return true
        endif
   
        endmethod
    endstruct
 
    constant function GetEvent takes nothing returns Event
        return Event.current[Event.count]
    endfunction
 
    constant function GetRealEvent takes nothing returns real
        return Event.real[Event.count]
    endfunction
 
    function RecursionFlag takes nothing returns boolean
        return Event.isWarning()
    endfunction
 
    function Execute takes code func returns nothing
        call ForForce(bj_FORCE_PLAYER[0], func)
    endfunction
 
    private module EventTrigs_fire

        static if LIBRARY_Table and USE_HASH then
            private static thistype EventTrigs_fire_head = 0
            private static thistype EventTrigs_fire_this = 0
       
            private static integer EventTrigs_fire_i = 0
       
            private static method exec_fire takes nothing returns nothing
                set EventTrigs_fire_i = 0
                loop
                    exitwhen EventTrigs_fire_this == EventTrigs_fire_head or EventTrigs_fire_i >= 80
               
                    if IsTriggerEnabled(EventTrigs_fire_this.trigger) then
                        call ConditionalTriggerExecute(EventTrigs_fire_this.trigger)
                    endif
               
                    set EventTrigs_fire_this = EventTrigs_fire_this.trigger_next
                    set EventTrigs_fire_i = EventTrigs_fire_i + 1
                endloop
                if EventTrigs_fire_i >= 80 then
                    call Execute(function thistype.exec_fire)
                    return
                endif
                set EventTrigs_fire_head = 0
                set EventTrigs_fire_this = 0
                set EventTrigs_fire_i = 0
            endmethod
        endif
   
        static method fire takes Event e, real r returns nothing
            static if LIBRARY_Table and USE_HASH then
                set EventTrigs_fire_head = real_get(e, r)
                set EventTrigs_fire_this = EventTrigs_fire_head.trigger_next
           
                if EventTrigs_fire_head == 0 then
                    set EventTrigs_fire_head = 0
                    set EventTrigs_fire_this = 0
                    return
                endif
           
                if IsTriggerEnabled(EventTrigs_fire_head.trigger) then
                    call ConditionalTriggerExecute(EventTrigs_fire_head.trigger)
                endif
           
                call exec_fire()
            else
                local thistype head = real_get(e, r)
                local thistype this = head.trigger_next
           
                if head == 0 then
                    return
                endif
           
                if IsTriggerEnabled(head.trigger) then
                    call ConditionalTriggerExecute(head.trigger)
                endif
           
                loop
                    exitwhen this == head
               
                    if IsTriggerEnabled(trigger) then
                        call ConditionalTriggerExecute(trigger)
                    endif
               
                    set this = trigger_next
                endloop
            endif
        endmethod
    endmodule

    private module EventTrigs_all_fire
        static if LIBRARY_Table and USE_HASH then
       
            //! runtextmacro op_set("Num05", "EventTrigs_all_fire_trig", "trigger", "null", "")
       
            private static method onInit takes nothing returns nothing
                set Num05 = Table.create()
            endmethod
        else
            private trigger EventTrigs_all_fire_trig   
        endif
 
        private static integer EventTrigs_all_fire_count = 0
   
        private static method EventTrigs_all_fire_check takes trigger t returns boolean
            local thistype this = 1
            loop
                exitwhen integer(this) > EventTrigs_all_fire_count or EventTrigs_all_fire_trig == t
                set this = this + 1
            endloop
            if integer(this) > EventTrigs_all_fire_count then
                set this = 0
            endif
            return this == 0
        endmethod
   
        private static method EventTrigs_all_fire_clear takes nothing returns nothing
            local thistype this = 1
            loop
                exitwhen integer(this) > EventTrigs_all_fire_count
                set EventTrigs_all_fire_trig = thistype(0).EventTrigs_all_fire_trig
                set this = this + 1
            endloop
            set EventTrigs_all_fire_count = 0
        endmethod
   
        static if LIBRARY_Table and USE_HASH then
            private static thistype EventTrigs_all_fire_head = 0
            private static thistype EventTrigs_all_fire_this = 0
       
            private static integer EventTrigs_all_fire_i = 0
       
            private static method exec_all_fire takes nothing returns nothing
                set EventTrigs_all_fire_i = 0
                loop
                    exitwhen EventTrigs_all_fire_this == EventTrigs_all_fire_head or EventTrigs_all_fire_i >= 75
               
                    if IsTriggerEnabled(EventTrigs_all_fire_this.trigger) then
                        if EventTrigs_all_fire_check(EventTrigs_all_fire_this.trigger) then
                            call ConditionalTriggerExecute(EventTrigs_all_fire_this.trigger)
                   
                            set EventTrigs_all_fire_count = EventTrigs_all_fire_count + 1
                            set thistype(EventTrigs_all_fire_count).EventTrigs_all_fire_trig = EventTrigs_all_fire_this.trigger
                        endif
                    endif
               
                    set EventTrigs_all_fire_this = EventTrigs_all_fire_this.event_next
                    set EventTrigs_all_fire_i = EventTrigs_all_fire_i + 1
                endloop
                if EventTrigs_all_fire_i >= 75 then
                    call Execute(function thistype.exec_all_fire)
                    return
                endif
                set EventTrigs_all_fire_head = 0
                set EventTrigs_all_fire_this = 0
                set EventTrigs_all_fire_i = 0
           
                call Execute(function thistype.EventTrigs_all_fire_clear)
            endmethod
        endif
   
        static method all_fire takes Event e returns nothing
            static if LIBRARY_Table and USE_HASH then
                set EventTrigs_all_fire_head = event_get(e)
                set EventTrigs_all_fire_this = EventTrigs_all_fire_head.event_next
           
                if EventTrigs_all_fire_head == 0 then
                    set EventTrigs_all_fire_head = 0
                    set EventTrigs_all_fire_this = 0
                    return
                endif
           
                if IsTriggerEnabled(EventTrigs_all_fire_head.trigger) then
                    if EventTrigs_all_fire_check(EventTrigs_all_fire_head.trigger) then
                        call ConditionalTriggerExecute(EventTrigs_all_fire_head.trigger)
               
                        set EventTrigs_all_fire_count = EventTrigs_all_fire_count + 1
                        set thistype(EventTrigs_all_fire_count).EventTrigs_all_fire_trig = EventTrigs_all_fire_head.trigger
                    endif
                endif
           
                call exec_all_fire()
            else
                local thistype head = event_get(e)
                local thistype this = head.event_next
           
                if head == 0 then
                    return
                endif
           
                if IsTriggerEnabled(head.trigger) then
                    if EventTrigs_all_fire_check(head.trigger) then
                        call ConditionalTriggerExecute(head.trigger)
               
                        set EventTrigs_all_fire_count = EventTrigs_all_fire_count + 1
                        set thistype(EventTrigs_all_fire_count).EventTrigs_all_fire_trig = head.trigger
                    endif
                endif
           
                loop
                    exitwhen this == head
               
                    if IsTriggerEnabled(trigger) then
                        if EventTrigs_all_fire_check(trigger) then
                            call ConditionalTriggerExecute(trigger)
                   
                            set EventTrigs_all_fire_count = EventTrigs_all_fire_count + 1
                            set thistype(EventTrigs_all_fire_count).EventTrigs_all_fire_trig = trigger
                        endif
                    endif
               
                    set this = event_next
                endloop
           
                call Execute(function thistype.EventTrigs_all_fire_clear)
            endif
        endmethod
    endmodule
 
    private module EventTrigs_remove
 
        static if LIBRARY_Table and USE_HASH then
            private static thistype EventTrigs_remove_head = 0
            private static thistype EventTrigs_remove_this = 0
       
            private static integer EventTrigs_remove_i = 0
       
            private static method exec_remove takes nothing returns nothing
                set EventTrigs_remove_i = 0
                loop
                    exitwhen EventTrigs_remove_this == EventTrigs_remove_head or EventTrigs_remove_i >= 80
               
                    call EventTrigs_remove_this.unregister()
               
                    set EventTrigs_remove_this = EventTrigs_remove_this.event_prev
                    set EventTrigs_remove_i = EventTrigs_remove_i + 1
                endloop
                if EventTrigs_remove_i >= 80 then
                    call Execute(function thistype.exec_remove)
                    return
                endif
           
                call EventTrigs_remove_this.unregister()
           
                set EventTrigs_remove_head = 0
                set EventTrigs_remove_this = 0
                set EventTrigs_remove_i = 0
            endmethod
        endif
   
        static method remove takes Event e returns nothing
            static if LIBRARY_Table and USE_HASH then
                set EventTrigs_remove_head = event_get(e)
                set EventTrigs_remove_this = EventTrigs_remove_head.event_prev
           
                if EventTrigs_remove_head == 0 then
                    set EventTrigs_remove_head = 0
                    set EventTrigs_remove_this = 0
                    return
                endif
           
                call exec_remove()
            else
                local thistype head = event_get(e)
                local thistype this = head.event_prev
           
                if head == 0 then
                    return
                endif
           
                loop
                    exitwhen this == head
               
                    call unregister()
               
                    set this = event_prev
                endloop
           
                call unregister()
            endif
        endmethod
    endmodule
 
    static if LIBRARY_Table and USE_HASH then
        //! runtextmacro link_moduleH("event", "private")
        //! runtextmacro link_moduleH("trigger", "private")
        //! runtextmacro link_moduleH("real", "private")
 
        private module EventTrigs_m
            private static method onInit takes nothing returns nothing
                set Num01 = Table.create()
                set Num02 = Table.create()
                set Num03 = Table.create()
           
                set Num04 = Table.create()
            endmethod
        endmodule
   
    else
        //! runtextmacro link_module("event", "private")
        //! runtextmacro link_module("trigger", "private")
        //! runtextmacro link_module("real", "private")
    endif
 
    struct EventTrigs extends array
        static if LIBRARY_Table and USE_HASH then
            implement AllocH
       
            //! runtextmacro op_read("Num01", "event", "integer", "0")
            //! runtextmacro op_read("Num02", "trigger", "trigger", "null")
            //! runtextmacro op_read("Num03", "real", "real", "0.")
       
            //! runtextmacro op_set("Num04", "head", "integer", "0", "private")
       
            implement DoubleLinkH_event
            implement DoubleLinkH_trigger
            implement DoubleLinkH_real
       
            implement EventTrigs_m
        else
            implement Alloc_MyPad
       
            readonly integer event
            readonly trigger trigger
            readonly real real
       
            private integer head
       
            implement DoubleLink_event
            implement DoubleLink_trigger
            implement DoubleLink_real
        endif
   
        private static method event_get takes Event e returns thistype
            static if ALLOW_DEBUG then
                debug call BJDebugMsg("thistype.event_get: returning {" + I2S(thistype(e).head) +  "}")
            endif
            return thistype(e).head
        endmethod
   
        private static method real_get takes Event e, real r returns thistype
            local thistype head = event_get(e)
            local thistype this = head.real_next
       
            if head == 0 or head.real == r then
                static if ALLOW_DEBUG then
                    debug call BJDebugMsg("thistype.real_get: {head} returning {" + I2S(head) +  "}")
                endif
                return head
            endif
       
            loop
                exitwhen this == head or this == 0 or real == r
                set this = real_next
            endloop
       
            if this == head then
                set this = 0
            endif
            static if ALLOW_DEBUG then
                debug call BJDebugMsg("thistype.real_get: {this} returning {" + I2S(this) +  "}")
            endif
            return this
        endmethod
   
        private static method trigger_get takes Event e, real r, trigger t returns thistype
            local thistype head = real_get(e, r)
            local thistype this = head.trigger_next
       
            if head == 0 or head.trigger == t then
                static if ALLOW_DEBUG then
                    debug call BJDebugMsg("thistype.trigger_get: {head} returning {" + I2S(head) +  "}")
                endif
                return head
            endif
       
            loop
                exitwhen this == head or this == 0 or trigger == t
                set this = trigger_next
            endloop
       
            if this == head then
                set this = 0
            endif
            static if ALLOW_DEBUG then
                debug call BJDebugMsg("thistype.trigger_get: {this} returning {" + I2S(this) +  "}")
            endif
            return this
        endmethod
   
        static method getInstance takes Event e, real r, trigger t returns thistype
            return trigger_get(e, r, t)
        endmethod
   
        private method unlink takes nothing returns nothing
            local thistype that = event_get(event)
       
            //  If the instance is the head of the list of the Event instance...
            if this == that then
                //  Check if the next EventTrigs trigger instance exists.
                if trigger_next != this then
                    //  Trigger instance exists.
                    set that = trigger_next
               
                    call that.real_insert(real_next)
                //  Check if the next EventTrigs real instance exists...
                elseif real_next != this then
                    //  It exists
                    set that = real_next
                else
                    set that = 0
                endif
           
                call real_pop()
                set thistype(event).head = that
            else
                //  If the instance is a head of its' real list.
                if real_head != 0 then
                    //  Check if the next EventTrigs trigger instance exists...
                    if trigger_next != this then
                        set that = trigger_next
                   
                        call that.real_insert(real_next)
                    endif
               
                    call real_pop()
                else
                    //  No need to adjust, it's just a trigger instance.
                endif
            endif
       
            call trigger_pop()
            call event_pop()
       
            set event_next = 0
            set event_prev = 0
            set event_head = 0
       
            set trigger_next = 0
            set trigger_prev = 0
            set trigger_head = 0
       
            set real_next = 0
            set real_prev = 0
            set real_head = 0
        endmethod
   
        method unregister takes nothing returns nothing
            if this == 0 then
                debug call BJDebugMsg("thistype.unregister: Trigger to be unregistered is not registered to begin with.")
                return
            endif
       
            call unlink()
       
            set trigger = null
            set real = 0.
            set event = 0
       
            static if ALLOW_DEBUG then
                debug call BJDebugMsg("================")
                debug call LineBreak()
            endif
            call deallocate()
        endmethod
   
        implement EventTrigs_fire
        implement EventTrigs_all_fire
        implement EventTrigs_remove
   
        private method link takes Event e, real r returns nothing
            local thistype array that
       
            static if ALLOW_DEBUG then
                debug call LineBreak()
                debug call BJDebugMsg("================")
                debug call BJDebugMsg("Instance allocated: " + I2S(this))
            endif
       
            set that[1] = event_get(e)
            if that[1] == 0 then
                set that[1] = this
           
                set thistype(e).head = this
           
                static if ALLOW_DEBUG then
                    debug call BJDebugMsg("Head of Event is now mapped to " + I2S(that[1]))
                endif
            debug else
           
                static if ALLOW_DEBUG then
                    debug call BJDebugMsg("Head of Event is " + I2S(that[1]))               
                endif
            endif
       
            static if ALLOW_DEBUG then
                debug call LineBreak()
            endif
       
            set that[2] = real_get(e, r)
            if that[2] == 0 then
                set that[2] = this
           
                call real_insert(that[1])
                set real_head = 1
           
                static if ALLOW_DEBUG then
                    debug call BJDebugMsg("Head of real list in Event is now mapped to " + I2S(that[2]))
                endif
            endif
       
            call trigger_insert(that[2])
            call event_insert(that[1])
       
            static if ALLOW_DEBUG then
                debug call LineBreak()
                debug call BJDebugMsg("================")
            endif
        endmethod
   
        static method register takes Event e, real r, trigger t returns thistype
            local thistype this
            local thistype that
       
            set that = trigger_get(e, r, t)
       
            if that == 0 then
                set this = allocate()
                static if ALLOW_DEBUG then
                    call BJDebugMsg("Registering")
                endif
           
                call link(e, r)
           
                set event = e
                set real = r
                set trigger = t
           
                return this
            endif
            return that
        endmethod
   
        method operator eventid= takes Event new returns nothing
            if event == new then
                return
            endif
       
            call unlink()
            call link(new, real)
       
            set event = new
        endmethod
   
        method operator prio= takes real new returns nothing
            if not (real != new) then
                return
            endif
       
            call unlink()
            call link(event, new)
       
            set real = new
        endmethod
    endstruct

    private function OnHandlerFunc takes nothing returns nothing
        if Event.instruction == 1 then
            call EventTrigs.fire(GetEvent(), GetRealEvent())
        elseif Event.instruction == 2 then
            call EventTrigs.all_fire(GetEvent())
        elseif Event.instruction == 3 then
            call EventTrigs.remove(GetEvent())
        endif
    endfunction
 
    private module EventInit_m
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(Event.EXEC, function OnHandlerFunc)
        endmethod
    endmodule
 
    private struct EventInit_s extends array
        implement EventInit_m
    endstruct
 
endlibrary

library_once EventStruct uses PseudoVarEvent

endlibrary

Wurst:
package Event

import ErrorHandling
import HashMap
import LinkedList

constant HANDLER_TRIG = CreateTrigger()
constant HANDLER_FUNC = TriggerAddCondition(HANDLER_TRIG, Condition(function handler_func))

enum INS
    fire
    clear
    destruct
    nil

/** The handler function which calls the necessary functions. */
function handler_func()
    switch getIns()
        case fire
            EvListener.fireEv(getEvent(), getRealOfEvent())
        case clear
            EvListener.clearEv(getEvent(), getRealOfEvent())
        case destruct
            EvListener.destroyEv(getEvent())
        default

/** A more sensible trigger function that dereferences said trigger from all real instances.
    of a certain Event. */
public function trigger.unbindFromEvent(Event e) returns bool
    return EvListener.unbindTriggerEv(e, this)
  
/** A trigger wrapper function for unbinding from a specific real instance of an Event. */
public function trigger.unbindFromEvent(Event e, real r) returns bool
    return EvListener.getInstance(e, r, this)..deregister() != null

/** A trigger wrapper function for binding */
public function trigger.bindToEvent(Event e, real r) returns EvListener
    return EvListener.register(e, r, this)

/** Returns the currently triggered Event object */
public function getEvent() returns Event
    return Event.curEv[Event.cur]

/** Returns the currently calibrated real. */
public function getRealOfEvent() returns real
    return Event.curReal[Event.cur]

/** Reserved function, internally used in handler_func() */
function getIns() returns INS
    return Event.curIns[Event.cur]

public class Event
    protected static Event array curEv
    protected static real array curReal
    protected static INS array curIns

    protected static int cur

    private static function assign(Event that, real r, INS which)
        cur++

        curEv[cur] = that
        curReal[cur] = r
        curIns[cur] = which

        HANDLER_TRIG.evaluate()

        curIns[cur] = INS.nil
        curReal[cur] = 0.
        curEv[cur] = null

        cur--

    ondestroy
        assign(this, 0., INS.destruct)

    function clear(real r)
        assign(this, r, INS.clear)

    function fire(real r)
        assign(this, r, INS.fire)

public class EvListener
    private static HashMap<Event, EvListener> evHead = new HashMap<Event, EvListener>()

    /** These variables are to be read-only. */
    trigger trig
    Event ev
    real r

    private LinkedList<EvListener> rList
    private LinkedList<EvListener> tList

    ondestroy
        this.trig = null
        this.ev = null
        this.r = 0.

    private construct()

    private construct(Event e, real r, trigger t)
        this.trig = t
        this.r = r
        this.ev = e

    /** Returns the representative instance of the Event object. */
    private static function getHead(Event e) returns EvListener
        return evHead.get(e)

    /** Internal method that gets the head node at a specified real. */
    private static function getReal(EvListener that, real r) returns EvListener
        if that == null or not (that.r != r)
            return that

        EvListener temp = null
        for iter in that.rList
            if not (iter.r != r)
                temp = iter
                break      
        return temp

    /** Internal method that gets the instance with a matching trigger. */
    private static function getTrig(EvListener that, trigger t) returns EvListener
        if that == null or not (that.trig != t)
            return that

        EvListener temp = null
        for iter in that.tList
            if not (iter.trig != t)
                temp = iter
                break      
        return temp
  
    /** This basically serves as a wrapper inner function for getInstance.
        Returns a head instance in the main real list with the specified real value.  */
    private static function getRealHead(Event e, real r) returns EvListener
        return getReal(getHead(e), r)

    /** Gets */
    static function getInstance(Event e, real r, trigger t) returns EvListener
        return getTrig(getRealHead(e, r), t)

    /** Deregisters a trigger and destroys its instance. */
    function deregister()
        EvListener array other

        //  Check if the instance is the head
        other[1] = evHead.get(this.ev)
        if other[1] == this
            //  Instance is the head
            //  We search first at the list of triggers with the same real value.
            if this.tList.size() > 1
                //  Since the first instance is always first...
                this.tList.remove(this)
                this.rList.remove(this)
                this.rList.addAt(other[1].tList.get(1), 1)

                other[1] = this.tList.get(1)
                other[1].tList = this.tList
                other[1].rList = this.tList

                evHead.put(this.ev, other[1])
            else
                //  No more instances remaining in the list remaining.
                destroy this.tList

                //  We search at any available real list and check if it has
                //  more than 1 entry.
                if this.rList.size() > 1
                    //  At least another instance exists.
                    this.rList.remove(this)
                  
                    other[1] = this.rList.get(1)
                    other[1].rList = this.rList

                    evHead.put(this.ev, other[1])
                else
                    //  We destroy the instance.
                    destroy this.rList

            this.rList = null
            this.tList = null
        else
            if this.tList != null
                //  The instance is head.
                if this.tList.size() > 1
                    //  At least another instance exists.
                    this.tList.remove(this)

                    var counter = 0
                    for iter in this.rList
                        counter++
                        if iter == this
                            break
                      
                    other[1].rList.remove(this)
                    other[1].rList.addAt(this.tList.get(1), counter)

                    other[2] = this.tList.get(1)
                    other[2].tList = this.tList

                else
                    //  We destroy the instance.
                    destroy this.tList

                this.tList = null
  
        destroy this

    /** Registers a trigger to a specified Event object at the desired real. */
    static function register(Event e, real r, trigger t) returns EvListener
        if t == null or e == null
            if t == null
                error("EvListener.register: invalid trigger object")
            else if e == null
                error("EvListener.register: invalid Event object")
          
            return null
      
        EvListener array other
        EvListener temp = null

        other[1] = getHead(e)
        if other[1] == null
            other[1] = new EvListener(e, r, t)

            other[1].rList = new LinkedList<EvListener>..add(other[1])
            other[1].tList = new LinkedList<EvListener>..add(other[1])
          
            evHead.put(e, other[1])

            temp = other[1]
            return temp

        other[2] = getReal(other[1], r)
        if other[2] == null
            other[2] = new EvListener(e, r, t)

            other[2].tList = new LinkedList<EvListener>..add(other[2])
            other[1].rList.add(other[2])
          
            temp = other[2]
            return temp

        other[3] = getTrig(other[2], t)
        if other[3] == null
            other[3] = new EvListener(e, r, t)

            other[2].tList.add(other[3])

        //  Just to fool the console...
        if temp == null

        temp = other[3]
        return temp

    /** Unbinds a trigger from an Event object in all its' real lists. */
    static function unbindTriggerEv(Event e, trigger t) returns bool
        let header = getHead(e)
        var counter = 0

        if header != null
            for iter in header.rList
                var iter1 = getInstance(e, iter.r, t)
                if iter1 != null
                    iter1.deregister()
                    counter++

        return counter != 0
              
    protected static function fireEv(Event e, real r)
        let header = getRealHead(e, r)

        if header != null
            for iter in header.tList
                if iter.trig.isEnabled()
                    if iter.trig.evaluate()
                        iter.trig.execute()

    protected static function clearEv(Event e, real r)
        let header = getRealHead(e, r)
      
        if header != null
            for iter in header.tList
                if iter != header
                    iter.deregister()

            header.deregister()

    protected static function destroyEv(Event e)
        let header = getHead(e)

        if header != null
            LinkedList<EvListener> iter = header.rList
            LinkedList<EvListener> iter_shadow = iter.copy()

            for iter1 in iter_shadow
                if iter1 != header
                    clearEv(e, iter1.r)

            clearEv(e, header.r)

            destroy iter_shadow


 
Last edited:

Changelogs:


- (15th of December, 2017) v.1.2.0.0 - Removed compatibility with the Hashtable library, but now optionally uses Bribe's Table.
Reworked the EventStruct to Pseudo Var Event.
Removed the clear= operator due to forgetfulness in reimplementation.

- (9th of October, 2017) v.1.1.1.1 - Changed implementation of Tables to better suit Bribe's Table.
Added description.

- (28th of August, 2017) v.1.1.1.0 - Added a clear= operator for convenience of clearing out certain instances belonging to a certain Event.
Renamed TRVE Struct to Pseudo-Var Event

- (18th of April, 2017) v.1.1.0.0 - getInstances reverted to in-function looping. Added event= and realEvent= operators.

- (10th of April, 2017) v.1.0.0.2 - Now, getInstances have an optional oplimit resetting function and a global to activate or deactivate through static ifs.​

- (4th of April, 2017) v.1.0.0.1 - Added an oplimit resetter for the iteration of every trigger (asymptotic worst-case scenario)

- (27th of March, 2017) v.1.0.0.0 - created​
 
Last edited:
@IcemanBo

My eventual usage of TriggerAddCondition was because of the assumption that triggerconditions can be destroyed when the trigger is destroyed. Thus, I had to typecast the code parameter to boolexpr.

The system can be used in other systems, such as detecting damage events, or timer events and such.

About the list? I had thought of just typing them personally by hand, but I'll see what I can do with the allocation.

EDIT:

Made a thread on my allocation library.

Demo



Assume that GetUnitIndex and its' corresponding setter, and GetInstanceIndex and its' corresponding setter exist.

We can look carefully at the following lines
JASS:
globals
    private Event EnterLeave = 0

An Event instance should be initialized in a module initialization, which explains the module initialization in a textmacro.

When you register triggers to the event and the real it is calibrated to, it creates an EventTrigs instance.

JASS:
call EventTrigs.register(trig1, 1.00, whichEvent)
call EventTrigs.register(trig2, 1.00, whichEvent)
call EventTrigs.register(trig3, 1.00, whichEvent)
call EventTrigs.register(trig4, 1.00, whichEvent)

The method above is instance-safe, checking if the instance exists in the first place and not registering if it is already registered to the event.

You can register a trigger to many events and virtually infinite amount of reals but you can't register a trigger twice to the same event and real.

JASS:
call EventTrigs.register(trig1, 1.00, whichEvent)    // Okay, you may pass!
call EventTrigs.register(trig1, 2.00, whichEvent)    // Okay, you may pass!
call EventTrigs.register(trig1, 2147483647.96875, whichEvent)    // Okay, you may pass!
call EventTrigs.register(trig1, 1.00, whichEvent)    // The Republic of Turkey hereby bans the execution of the following method called thereof. (In short, NO!!)
...Next, we look at the method operator fire=
It evaluates the trigger which will call the fireTrigger method found in the EventTrigs struct.

However, you can directly call it so that you can gain some speed in execution, but that is strongly discouraged.


 
Last edited:
^^ Adjusted variable declaration to minimize usage of optional libraries. Bribe's Table now is the main pseudo-variable creator instead of Hashtable library. In a sense,

JASS:
library EventStruct requires /*

    */AllocationAndLinks, /*
    */recommended Table, /* By Bribe
    */
endlibrary

Added a bit of information on the value of the script. :)
 
^^ Oh, I forgot to update that in my signature. Very well, an example is to be showcased:

Example: (Pseudo-code)
JASS:
    Event damageEvent = Event.create()

    private function onDamage
        damageDealer = GetEventDamageSource()
        damageTarget = GetTriggerUnit()
        damage = GetEventDamage()

        // We fire a group of triggers that are registered to an onDamage event, with a certain priority
        // This fires the first trigger
        damageEvent.fire = 1.00

        // This fires the second trigger
        damageEvent.fire = 2.00
    end

    init
        // First trigger is created
        Trigger.create()
        // Using WurstScript-like syntax since this is just pseudo-code
        Trigger..registerEventPrio(1.00, damageEvent)
        
        // Second trigger is created
        Trigger.create()
        Trigger..registerEventPrio(2.00, damageEvent)
    end

It is now Pseudo Var Event (Since it behaves like TRVE, but does not use it in any way)

Only the library AllocationAndLinks is required. Everything else is for script ordering, and will be removed.
 
I'd hate to make a direct comparison for this (when it comes to superiority in any form), so I just let the results speak for themselves.

The status quo of many Event libraries that I have seen are the following: one has lots of triggerconditions for every trigger (and by extension, Event objects). Another has a lot of triggers attached to an Event object, as a form of linked list. Another Event library I've seen is the one concerning TriggerRegisterVariableEvent, which has the added difficulty of deregistration.

Now, speaking of which, I will address the second Event which I inferred above (lots of triggers, jesus4lyf's Event), and compare them.

This has a TRVE-esque syntax, which makes it a bit easier to read. Moreover, if one needs it, they can register to different real instances of the same Event object, saving lots of Event objects (although it wouldn't really matter).
It combines TRVE and Event, such that one can have the deregistration capabilities of Event, and the syntax as indicated above.
If so needed, a trigger registered to an Event object can be dynamically reassigned to another real assignment (or it would be, anyway)
(Compare first Event later)
When creating this, I did not think of integrating the types of Event libraries that generate a single trigger and multiple triggerconditions, but the aspect of generating a single trigger for use grants a sense of extension.

The monster in the system will be beaten up, reassembled, and repackaged.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
You're probably seeing this resource in such a vastly different paradigm than I am, similar to Nestharus. It's wildly abstract, and because it is I have a hard time understanding what it does.

I used to approve Nes's resources even on the basis that he was the only one who would use them, because I wasn't going to let my own lack of imagination stand in the way of systems whose uses escape me.

But on the other hand, what's the point of hosting them publicly? Nes would argue for the sake of a complete collection of resources or extensions. I always caved to that, because it's a valid point. But onto this specific resource...

What do you imagine the type of system would be to utilize such abstract features? Does this support priority events like Nestharus's did? What use is "changing the real" (as you put it) compared to other events which allow recursive event firing as well?
 
The applications I thought of were on a Damage Event (before damage is dealt, and after), Extra Events (the vJASS one, reworked by Spellbound), a niche Timer System (One timer per interval, which fires an Event object with the specified real stamp based on the interval itself), custom events, and other events which don't exist.

Reason:
Damage Event - If one feels like it, they would declare 2-3 Event objects, when handling on damage events. (Maybe an extra one for evasion...). With this, all they have to do is just declare one Event object, with differing real events to act as the extra instances.

Extra Events - Basically GUI Unit Event. Think of each variable specifically declared for a certain set of triggers and bundle it within one Event object.

Niche Timer System - A one-timer per interval system, with handler triggers running on the expiration of a certain timer.

Perhaps some pseudo-code could help:

Wurst:
constant real EVENT_UNIT_DMG = 1.00
constant real EVENT_UNIT_ZERO_DMG = 2.00
constant real EVENT_UNIT_AFTER_DMG = 3.00

constant Event damageEvent = new Event()

function onAfterDamage()
    damageEvent.fire = EVENT_UNIT_AFTER_DMG

function onDamage()
    if GetEventDamage() != 0.00
        damageEvent.fire = EVENT_UNIT_DMG
    else
        damageEvent.fire = EVENT_UNIT_ZERO_DMG
    // Detect after damage is applied...

Basically, fire here is an operator that actually iterates over all of the triggers with the same real event. However, it is optimized so that the iterator variable checks out the list of head instances, searching for the first instance which has the same real event, and then iterates through another iterator variable (which guarantees the same real event, but different instances).

Code:
// 1.00 is the real event of the head node instance.
damageEvent       - 1.0 - 2.0 - 3.0
                                    |       |        |
                                   2       1      3
                                   4       7
                                   5
                                   6

As for priority events, I think this partially supports such a feature if needed, but it would have to be in a different library altogether.

As for changing the configured real event of an EventReg instance, it is actually an add-on feature (and contradictory to what I have established here).

My intentions on this might actually align well with Nes', by wanting to offer this to the coders of the Hive.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Sorry it has taken me four years to get back to you on this...

I'm looking it over, and contrary to four years ago, I actually understand a lot more about what this does. However, I am a bit lost on where the "AllocationAndLinks" library comes from. Is that hosted somewhere? I'm aware you've since developed more concrete Lua resources that utilize certain characteristics of Event systems, so I'll let you determine the quality of this at this point, and if it is up to your current standards.
 
Looking back at the dependencies, I wrote AllocationAndLinks back when I was starting to code in vJASS. I originally didn't want to use other people's systems before I knew generally how their systems behaved, hence the arbitrary requirement. I wrote this system in an attempt to simulate variable events.

Currently, I think this isn't up to standard. I'll have to update it so that it uses approved libraries instead and reduce the number of static ifs before I can consider it up to my standards now.
 
Top