System GTrigger Event System

Discussion in 'Systems and Snippets' started by Jesus4Lyf, Mar 30, 2009.

  1. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    GTrigger​

    Version 1.05​

    Requirements:
    - Jass NewGen

    Code:
    If you want to see the code, see below. If you want to understand the premise of what this does, go here instead.
    JASS:
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //~~ GT ~~ GTrigger ~~ By Jesus4Lyf ~~ Version 1.05 ~~
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    //
    //  What is GTrigger?
    //		 - GTrigger is an event system that replaces the cumbersome WC3
    //		   event system.
    //		 - GTrigger only launches the necessary threads instead of x threads,
    //		   where x is the number of times the event type occurs in the map.
    //
    //	=Pros=
    //		 - Instead of having 16 events (for "16" players) per use of an,
    //		   event type, you have 0 per use and 16 total for that event type.
    //		 - If you have 100 events of one type in your map, instead of firing
    //		   100 triggers each time any spell is cast, you fire only what's needed.
    //		 - GTrigger is faster to code with, more efficient to execute, and just
    //		   better programming practises and nicer code all round.
    //
    //	=Cons=
    //		 - If a trigger with a GTrigger event is destroyed, it must have its
    //		   event unregistered first or it will leak an event (slows firing down).
    //		 - Shouldn't use "wait" actions anywhere in the triggers.
    //
    //	Functions:
    //		   // General
    //		 - GT_UnregisterTriggeringEvent()
    //
    //		   // Ability events
    //		 - GT_RegisterStartsEffectEvent(trigger, abilityid)       (returns the trigger passed in)
    //		 - GT_RegisterBeginsChanellingEvent(trigger, abilityid)   (returns the trigger passed in)
    //		 - GT_RegisterBeginsCastingEvent(trigger, abilityid)      (returns the trigger passed in)
    //		 - GT_RegisterStopsCastingEvent(trigger, abilityid)       (returns the trigger passed in)
    //		 - GT_RegisterFinishesCastingEvent(trigger, abilityid)    (returns the trigger passed in)
    //		 - GT_RegisterLearnsAbilityEvent(trigger, abilityid)       (returns the trigger passed in)
    //		   // Order events // (can use String2OrderIdBJ("OrderString") for orderid
    //		 - GT_RegisterTargetOrderEvent(trigger, orderid)          (returns the trigger passed in)
    //		 - GT_RegisterPointOrderEvent(trigger, orderid)           (returns the trigger passed in)
    //		 - GT_RegisterNoTargetOrderEvent(trigger, orderid)        (returns the trigger passed in)
    //		   // Item events
    //		 - GT_RegisterItemUsedEvent(trigger, itemtypeid)          (returns the trigger passed in)
    //		 - GT_RegisterItemAcquiredEvent(trigger, itemtypeid)      (returns the trigger passed in)
    //		 - GT_RegisterItemDroppedEvent(trigger, itemtypeid)       (returns the trigger passed in)
    //		   // Unit events
    //		 - GT_RegisterUnitDiesEvent(trigger, unittypeid)          (returns the trigger passed in)
    //
    //		   // Ability Events
    //		 - GT_UnregisterSpellEffectEvent(trigger, abilityid)      (returns the trigger passed in)
    //		 - GT_UnregisterBeginsChanellingEvent(trigger, abilityid) (returns the trigger passed in)
    //		 - GT_UnregisterBeginsCastingEvent(trigger, abilityid)    (returns the trigger passed in)
    //		 - GT_UnregisterStopsCastingEvent(trigger, abilityid)     (returns the trigger passed in)
    //		 - GT_UnregisterFinishesCastingEvent(trigger, abilityid)  (returns the trigger passed in)
    //		 - GT_UnregisterLearnsAbilityEvent(trigger, abilityid)     (returns the trigger passed in)
    //		   // Order events // (can use String2OrderIdBJ("OrderString") for orderid
    //		 - GT_UnregisterTargetOrderEvent(trigger, orderid)        (returns the trigger passed in)
    //		 - GT_UnregisterPointOrderEvent(trigger, orderid)         (returns the trigger passed in)
    //		 - GT_UnregisterNoTargetOrderEvent(trigger, orderid)      (returns the trigger passed in)
    //		   // Item events
    //		 - GT_UnregisterItemUsedEvent(trigger, itemtypeid)        (returns the trigger passed in)
    //		 - GT_UnregisterItemAcquiredEvent(trigger, itemtypeid)    (returns the trigger passed in)
    //		 - GT_UnregisterItemDroppedEvent(trigger, itemtypeid)     (returns the trigger passed in)
    //		   // Unit events
    //		 - GT_UnregisterUnitDiesEvent(trigger, unittypeid)        (returns the trigger passed in)
    //
    //	Alternative interface (not recommended):
    //		If you aren't familiar with how this works, you shouldn't use it.
    //		All funcs must return false. (That is the only reason it isn't recommended.)
    //		   // General
    //		 - GT_RemoveTriggeringAction() // Use this to remove actions.
    //		   // Ability Events
    //		 - GT_AddStartsEffectAction(func, abilityid)
    //		 - GT_AddBeginsChanellingActon(func, abilityid)
    //		 - GT_AddBeginsCastingAction(func, abilityid)
    //		 - GT_AddStopsCastingAction(func, abilityid)
    //		 - GT_AddFinishesCastingAction(func, abilityid)
    //		 - GT_AddLearnsAbilityAction(func, abilityid)
    //		   // Order events // (can use String2OrderIdBJ("OrderString") for orderid
    //		 - GT_AddTargetOrderAction(func, orderid)
    //		 - GT_AddPointOrderAction(func, orderid)
    //		 - GT_AddNoTargetOrderAction(func, orderid)
    //		   // Item events
    //		 - GT_AddItemUsedAction(func, itemtypeid)
    //		 - GT_AddItemAcquiredAction(func, itemtypeid)
    //		 - GT_AddItemDroppedAction(func, itemtypeid)
    //		   // Unit events
    //		 - GT_AddUnitDiesAction(func, unittypeid)
    //
    //  Details:
    //		 - Due to the storage method, only 8191 GTrigger events are possible at any one time.
    //
    //  Thanks:
    //		 - Daxtreme: For voluntarily testing this system and the UnitDies event idea.
    //		 - kenny!: For the Order and Learns Ability event ideas.
    //
    //  How to import:
    //		 - Create a trigger named GT.
    //		 - Convert it to custom text and replace the whole trigger text with this.
    //
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    library GT initializer Init
        //////////////
        // Pointers //
        ////////////////////////////////////////////////////////////////////////////
        // Assigned to abilities, and point to trigger grouping linked lists.
        //
        // Use:
        //  GetPointer --> int (pointer)
        //  FreePointer(int (pointer))
        //  set PointerTarget[int (pointer)]=int (list link)
        //  PointerTarget[int (pointer)] --> int (list link)
        globals
            // Pointer
            private integer array PointerTarget
            private integer PointerMax=0
            // Spare Pointer Stack
            private integer array NextPointer
            private integer NextPointerMaxPlusOne=1
        endglobals
        
        private function GetPointer takes nothing returns integer
            if NextPointerMaxPlusOne==1 then
                set PointerMax=PointerMax+1
                return PointerMax
            endif
            set NextPointerMaxPlusOne=NextPointerMaxPlusOne-1
            return NextPointer[NextPointerMaxPlusOne]
        endfunction
        private function FreePointer takes integer pointer returns nothing
            set PointerTarget[pointer]=0
            set NextPointer[NextPointerMaxPlusOne]=pointer
            set NextPointerMaxPlusOne=NextPointerMaxPlusOne+1
        endfunction
        
        ///////////////////////////////////
        // Trigger Grouping Linked Lists //
        ////////////////////////////////////////////////////////////////////////////
        // Contains a chain of triggers to be executed together.
        //
        // Use:
        //  GetMem() --> int (mem)
        //  FreeMem(int (mem))
        //  Link(int (pointer), int (mem))
        //  Unlink(int (pointer), int (mem))
        globals
            // Spare Link Stack
            private integer array NextMem
            private integer NextMemMaxPlusOne=1
            // Linked list
            private trigger array Trig
            private integer array Next
            private integer array Prev
            private integer TrigMax=0
        endglobals
        
        private function GetMem takes nothing returns integer
            if NextMemMaxPlusOne==1 then
                set TrigMax=TrigMax+1
                return TrigMax
            endif
            set NextMemMaxPlusOne=NextMemMaxPlusOne-1
            return NextMem[NextMemMaxPlusOne]
        endfunction
        private function FreeMem takes integer i returns nothing
            set Trig[i]=null
            set NextMem[NextMemMaxPlusOne]=i
            set NextMemMaxPlusOne=NextMemMaxPlusOne+1
        endfunction
        
        // Linked list functionality
        // NOTE: This means "Next" must be loaded BEFORE executing the trigger, which could delete the current link.
        private function Link takes integer pointer, integer new returns nothing
            set Prev[new]=0
            set Next[new]=PointerTarget[pointer]
            set Prev[PointerTarget[pointer]]=new
            set PointerTarget[pointer]=new
        endfunction
        private function Unlink takes integer pointer, integer rem returns nothing
            if Prev[rem]==0 then
                set PointerTarget[pointer]=Next[rem]
                set Prev[Next[rem]]=0
            endif
            set Next[Prev[rem]]=Next[rem]
            set Prev[Next[rem]]=Prev[rem]
        endfunction
        
        //////////////////////
        // GTrigger General //
        ////////////////////////////////////////////////////////////////////////////
        // Only contains the UnregisterTriggeringEvent action for public use.
        globals
            boolean UnregisterLastEvent=false
        endglobals
        public function UnregisterTriggeringEvent takes nothing returns nothing
            set UnregisterLastEvent=true
        endfunction
        
        /////////////////////////////////////
        // GTrigger Ability Implementation //
        ////////////////////////////////////////////////////////////////////////////
        // The nasty textmacro implementation of special "All Players" events.
        //! textmacro SetupSpecialAllPlayersEvent takes NAME, EVENT, GETSPECIAL
            globals
                private trigger $NAME$Trigger=CreateTrigger()
                // Extendable arrays
                private integer array $NAME$AbilityIdA
                private integer array $NAME$ListPointerA
                private integer array $NAME$AbilityIdB
                private integer array $NAME$ListPointerB
                private integer array $NAME$AbilityIdC
                private integer array $NAME$ListPointerC
                private integer array $NAME$AbilityIdD
                private integer array $NAME$ListPointerD
                private integer array $NAME$AbilityIdE
                private integer array $NAME$ListPointerE
            endglobals
            
            globals//locals
                private integer GetOrCreateListPointer$NAME$AbilHashed
            endglobals
            private function GetOrCreate$NAME$ListPointer takes integer abil returns integer
                set GetOrCreateListPointer$NAME$AbilHashed=abil-(abil/8191)*8191
                if $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
                    set $NAME$AbilityIdA[GetOrCreateListPointer$NAME$AbilHashed]=abil
                    set $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                    return $NAME$ListPointerA[GetOrCreateListPointer$NAME$AbilHashed]
                endif
                if $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
                    set $NAME$AbilityIdB[GetOrCreateListPointer$NAME$AbilHashed]=abil
                    set $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                    return $NAME$ListPointerB[GetOrCreateListPointer$NAME$AbilHashed]
                endif
                if $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
                    set $NAME$AbilityIdC[GetOrCreateListPointer$NAME$AbilHashed]=abil
                    set $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                    return $NAME$ListPointerC[GetOrCreateListPointer$NAME$AbilHashed]
                endif
                if $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
                    set $NAME$AbilityIdD[GetOrCreateListPointer$NAME$AbilHashed]=abil
                    set $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                    return $NAME$ListPointerD[GetOrCreateListPointer$NAME$AbilHashed]
                endif
                if $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]<1 then // Empty
                    set $NAME$AbilityIdE[GetOrCreateListPointer$NAME$AbilHashed]=abil
                    set $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]=GetPointer()
                    return $NAME$ListPointerE[GetOrCreateListPointer$NAME$AbilHashed]
                endif
                call BJDebugMsg("GTrigger Error: Ran out of storage locations for pointers on object "+GetObjectName(abil)+"!")
                set PointerTarget[0]=0
                return 0
            endfunction
            
            globals//locals
                private integer GetListPointer$NAME$AbilHashed
            endglobals
            private function Get$NAME$ListPointer takes integer abil returns integer
                set GetListPointer$NAME$AbilHashed=abil-(abil/8191)*8191
                if $NAME$AbilityIdA[GetListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerA[GetListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdA[GetListPointer$NAME$AbilHashed]<1 then // Empty
                    set PointerTarget[0]=0 // Make sure.
                    return 0
                endif
                if $NAME$AbilityIdB[GetListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerB[GetListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdB[GetListPointer$NAME$AbilHashed]<1 then // Empty
                    set PointerTarget[0]=0 // Make sure.
                    return 0
                endif
                if $NAME$AbilityIdC[GetListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerC[GetListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdC[GetListPointer$NAME$AbilHashed]<1 then // Empty
                    set PointerTarget[0]=0 // Make sure.
                    return 0
                endif
                if $NAME$AbilityIdD[GetListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerD[GetListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdD[GetListPointer$NAME$AbilHashed]<1 then // Empty
                    set PointerTarget[0]=0 // Make sure.
                    return 0
                endif
                if $NAME$AbilityIdE[GetListPointer$NAME$AbilHashed]==abil then // Correct
                    return $NAME$ListPointerE[GetListPointer$NAME$AbilHashed]
                elseif $NAME$AbilityIdE[GetListPointer$NAME$AbilHashed]<1 then // Empty
                    set PointerTarget[0]=0 // Make sure.
                    return 0
                endif
                call BJDebugMsg("GTrigger Error: Ran out of storage locations for pointers at ability "+GetObjectName(abil)+"!")
                set PointerTarget[0]=0
                return 0
            endfunction
            
            globals//locals
                private integer Register$NAME$Mem
            endglobals
            public function Register$NAME$Event takes trigger t, integer abil returns trigger
                set Register$NAME$Mem=GetMem()
                set Trig[Register$NAME$Mem]=t
                call Link(GetOrCreate$NAME$ListPointer(abil),Register$NAME$Mem)
                return t
            endfunction
            
            globals//locals
                private integer Unregister$NAME$Pointer
                private integer Unregister$NAME$Mem
            endglobals
            public function Unregister$NAME$Event takes trigger t, integer abil returns trigger
                set Unregister$NAME$Pointer=Get$NAME$ListPointer(abil)
                set Unregister$NAME$Mem=PointerTarget[Unregister$NAME$Pointer]
                loop
                    exitwhen Trig[Unregister$NAME$Mem]==t
                    if Unregister$NAME$Mem==0 then
                        return t // Not found.
                    endif
                    set Unregister$NAME$Mem=Next[Unregister$NAME$Mem]
                endloop
                call Unlink(Unregister$NAME$Pointer,Unregister$NAME$Mem)
                call FreeMem(Unregister$NAME$Mem)
                return t
            endfunction
            
            private function Trigger$NAME$Event takes nothing returns boolean
                local integer Trigger$NAME$Pointer=Get$NAME$ListPointer($GETSPECIAL$)
                local integer Trigger$NAME$Mem=PointerTarget[Trigger$NAME$Pointer]
                local integer Trigger$NAME$NextMem
                set UnregisterLastEvent=false
                loop
                    exitwhen Trigger$NAME$Mem<1
                    set Trigger$NAME$NextMem=Next[Trigger$NAME$Mem]
                    if TriggerEvaluate(Trig[Trigger$NAME$Mem]) then
                        call TriggerExecute(Trig[Trigger$NAME$Mem])
                    endif
                    if UnregisterLastEvent then
                        set UnregisterLastEvent=false
                        call Unlink(Trigger$NAME$Pointer,Trigger$NAME$Mem)
                        call FreeMem(Trigger$NAME$Mem)
                    endif
                    set Trigger$NAME$Mem=Trigger$NAME$NextMem
                endloop
                return false
            endfunction
            
            private function Init$NAME$ takes nothing returns nothing
                local integer i=bj_MAX_PLAYER_SLOTS
                call TriggerAddCondition($NAME$Trigger,Condition(function Trigger$NAME$Event))
                loop
                    set i=i-1
                    call TriggerRegisterPlayerUnitEvent($NAME$Trigger,Player(i),EVENT_PLAYER_$EVENT$,null)
                    exitwhen i==0
                endloop
            endfunction
        //! endtextmacro
        
        //! runtextmacro SetupSpecialAllPlayersEvent("StartsEffect",     "UNIT_SPELL_EFFECT",        "GetSpellAbilityId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("BeginsChanelling", "UNIT_SPELL_CHANNEL",       "GetSpellAbilityId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("BeginsCasting",    "UNIT_SPELL_CAST",          "GetSpellAbilityId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("StopsCasting",     "UNIT_SPELL_ENDCAST",       "GetSpellAbilityId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("FinishesCasting",  "UNIT_SPELL_FINISH",        "GetSpellAbilityId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("TargetOrder",      "UNIT_ISSUED_TARGET_ORDER", "GetIssuedOrderId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("PointOrder",       "UNIT_ISSUED_POINT_ORDER",  "GetIssuedOrderId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("NoTargetOrder",    "UNIT_ISSUED_ORDER",        "GetIssuedOrderId()")
        //! runtextmacro SetupSpecialAllPlayersEvent("ItemUsed",         "UNIT_USE_ITEM",            "GetItemTypeId(GetManipulatedItem())")
        //! runtextmacro SetupSpecialAllPlayersEvent("ItemAcquired",     "UNIT_PICKUP_ITEM",         "GetItemTypeId(GetManipulatedItem())")
        //! runtextmacro SetupSpecialAllPlayersEvent("ItemDropped",      "UNIT_DROP_ITEM",           "GetItemTypeId(GetManipulatedItem())")
        //! runtextmacro SetupSpecialAllPlayersEvent("UnitDies",         "UNIT_DEATH",               "GetUnitTypeId(GetTriggerUnit())")
        //! runtextmacro SetupSpecialAllPlayersEvent("LearnsAbility",    "HERO_SKILL",               "GetLearnedSkill()")
        // Note to self: Remember to update the Init function.
        
        /////////////////////////////////////////
        // GTrigger All Players Implementation //
        ////////////////////////////////////////////////////////////////////////////
        // The textmacro implementation of other "All Players" events.
        //! textmacro SetupAllPlayersEvent takes NAME, EVENT
            globals
                private trigger $NAME$Trigger=CreateTrigger()
                private integer $NAME$ListPointer=0
            endglobals
            
            globals//locals
                private integer Register$NAME$Mem
            endglobals
            public function Register$NAME$Event takes trigger t returns trigger
                set Register$NAME$Mem=GetMem()
                set Trig[Register$NAME$Mem]=t
                call Link($NAME$ListPointer,Register$NAME$Mem)
                return t
            endfunction
            
            globals//locals
                private integer Unregister$NAME$Pointer
                private integer Unregister$NAME$Mem
            endglobals
            public function Unregister$NAME$Event takes trigger t returns trigger
                set Unregister$NAME$Mem=PointerTarget[$NAME$ListPointer]
                loop
                    exitwhen Trig[Unregister$NAME$Mem]==t
                    if Unregister$NAME$Mem==0 then
                        return t // Not found.
                    endif
                    set Unregister$NAME$Mem=Next[Unregister$NAME$Mem]
                endloop
                call Unlink($NAME$ListPointer,Unregister$NAME$Mem)
                call FreeMem(Unregister$NAME$Mem)
                return t
            endfunction
            
            private function Trigger$NAME$Event takes nothing returns boolean
                local integer Trigger$NAME$Mem=PointerTarget[$NAME$ListPointer]
                local integer Trigger$NAME$NextMem
                set UnregisterLastEvent=false
                loop
                    exitwhen Trigger$NAME$Mem<1
                    set Trigger$NAME$NextMem=Next[Trigger$NAME$Mem]
                    if TriggerEvaluate(Trig[Trigger$NAME$Mem]) then
                        call TriggerExecute(Trig[Trigger$NAME$Mem])
                    endif
                    if UnregisterLastEvent then
                        set UnregisterLastEvent=false
                        call Unlink($NAME$ListPointer,Trigger$NAME$Mem)
                        call FreeMem(Trigger$NAME$Mem)
                    endif
                    set Trigger$NAME$Mem=Trigger$NAME$NextMem
                endloop
                return false
            endfunction
            
            private function Init$NAME$ takes nothing returns nothing
                local integer i=bj_MAX_PLAYER_SLOTS
                call TriggerAddCondition($NAME$Trigger,Condition(function Trigger$NAME$Event))
                loop
                    set i=i-1
                    call TriggerRegisterPlayerUnitEvent($NAME$Trigger,Player(i),EVENT_PLAYER_UNIT_$EVENT$,null)
                    exitwhen i==0
                endloop
                // Initialise the pointer.
                set $NAME$ListPointer=GetPointer()
            endfunction
        //! endtextmacro
        
        // Old: //! runtextmacro SetupAllPlayersEvent("AnyUnitDies", "DEATH")
        
        private function Init takes nothing returns nothing
            // Ability events
            call InitStartsEffect()
            call InitBeginsChanelling()
            call InitBeginsCasting()
            call InitStopsCasting()
            call InitFinishesCasting()
            call InitLearnsAbility()
            // Order events
            call InitTargetOrder()
            call InitPointOrder()
            call InitNoTargetOrder()
            // Item events
            call InitItemUsed()
            call InitItemAcquired()
            call InitItemDropped()
            // Unit events
            call InitUnitDies()
        endfunction
        
        //////////////
        // Wrappers //
        ////////////////////////////////////////////////////////////////////////////
        // Wraps it up, for those who really want this interface.
        
        // General
        public function RemoveTriggeringAction takes nothing returns nothing
            call UnregisterTriggeringEvent()
            call DestroyTrigger(GetTriggeringTrigger())
        endfunction
        
        // Special All Player Events
        //! textmacro AddSpecialAllPlayersWrapper takes EVENT
            public function Add$EVENT$Action takes code func, integer special returns nothing
                call TriggerAddCondition(Register$EVENT$Event(CreateTrigger(),special),Condition(func))
            endfunction
        //! endtextmacro
        //! runtextmacro AddSpecialAllPlayersWrapper("StartsEffect")
        //! runtextmacro AddSpecialAllPlayersWrapper("BeginsChanelling")
        //! runtextmacro AddSpecialAllPlayersWrapper("BeginsCasting")
        //! runtextmacro AddSpecialAllPlayersWrapper("StopsCasting")
        //! runtextmacro AddSpecialAllPlayersWrapper("FinishesCasting")
        //! runtextmacro AddSpecialAllPlayersWrapper("TargetOrder")
        //! runtextmacro AddSpecialAllPlayersWrapper("PointOrder")
        //! runtextmacro AddSpecialAllPlayersWrapper("NoTargetOrder")
        //! runtextmacro AddSpecialAllPlayersWrapper("ItemUsed")
        //! runtextmacro AddSpecialAllPlayersWrapper("ItemAcquired")
        //! runtextmacro AddSpecialAllPlayersWrapper("ItemDropped")
        //! runtextmacro AddSpecialAllPlayersWrapper("UnitDies")
        //! runtextmacro AddSpecialAllPlayersWrapper("LearnsAbility")
        // Note to self: Remember to update the Init function.
        
        // All Player Events
        //! textmacro AddAllPlayersWrapper takes EVENT
            public function Add$EVENT$Action takes code func returns nothing
                call TriggerAddCondition(Register$EVENT$Event(CreateTrigger()),Condition(func))
            endfunction
        //! endtextmacro
        // Old: //! runtextmacro AddAllPlayersWrapper("AnyUnitDies")
    endlibrary
    This changes things like:
    JASS:
    function Trig_Untitled_Trigger_002_Conditions takes nothing returns boolean
        if ( not ( GetSpellAbilityId() == 'A000' ) ) then
            return false
        endif
        return true
    endfunction
    
    function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
    endfunction
    
    //===========================================================================
    function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
        set gg_trg_Untitled_Trigger_002 = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ( gg_trg_Untitled_Trigger_002, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( gg_trg_Untitled_Trigger_002, Condition( function Trig_Untitled_Trigger_002_Conditions ) )
        call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
    endfunction

    to
    JASS:
    function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
    endfunction
    
    //===========================================================================
    function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
        set gg_trg_Untitled_Trigger_002 = CreateTrigger(  )
        call GT_RegisterStartsEffectEvent(gg_trg_Untitled_Trigger_001,'A000')
        call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
    endfunction

    or even
    JASS:
    function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing
    endfunction
    
    //===========================================================================
    function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
        call TriggerAddAction(GT_RegisterStartsEffectEvent(CreateTrigger(),'A000'), function Trig_Untitled_Trigger_002_Actions )
    endfunction


    Why? Three reasons.
    • Instead of having 16 events (for "16" players) per event used, you have 0 per event and 16 total (for each event type).
    • If you have 100 uses of an event in your map, instead of firing 100 triggers each time it fires, you fire only what needs to be fired.
    • This is faster to code with, runs more efficiently, and I'd say it's just better programming practises.

    Downsides as per cons in documentation.

    Updates:
    Version 1.05 - Added LearnsAbility event type.
    Version 1.04 - Attached demo map. Added heaps and heaps of event types. Sorry to those who are already using the system within the first week of it's release, you'll need to switch the order of the parameters and such. I found it more intuitive to have them the other way around, and felt I needed to change them early so I don't regret it forever instead. If you get errors, just look up the new command syntax. :)
    Version 1.03 - Added alternative interface.
    Version 1.02 - Official Release. Removed all previous constraints by adding hashing and linked lists.
    Version 1.00 - Unofficial Release.

    Check out the demo map for more code demonstrations (released with version 1.04).
     

    Attached Files:

    • Like Like x 4
  2. Tom Jones

    Tom Jones N/A

    Ratings:
    +435 / 0 / -0
    Why not implement it then? Maximum must be 'zzzz', minimum logical must be '0000', so I suspect something like this may work:
    JASS:
    function Id2I takes integer id returns integer
        set id = id-'0000'
        return id-(id/(8192))*(8192)
    endfunction
     
    • Like Like x 1
  3. Viikuna

    Viikuna No Marlo no game.

    Ratings:
    +265 / 0 / -0
    I guess Im gonna use this. Looks nice.

    What about making some textmacro for different casting events?

    EVENT_PLAYER_UNIT_SPELL_CHANNEL is sometimes useful, because it triggers before casting time.
    EVENT_PLAYER_UNIT_SPELL_ENDCAST is needed for channeling spells.
     
    • Like Like x 1
  4. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Don't worry, I'm on it... I just had uni today, and I wanted to get this new thread up in the morning before I left, so yeah. :)

    By the way... Actually...

    JASS:
    function Id2I takes integer id returns integer
        return id-(id/8191)*8191
    endfunction


    And I'd inline it explicitly instead. :)

    >What about making some textmacro for different casting events?
    Don't worry, I'll implement those too. I'll make sure it's nice and stable and feature rich but simple to use. :p

    I also intend to imeplement UnregisterAbilityEvent(trigger, ability) for temporary triggers and such. Because otherwise the event will leak. Of course, I don't expect people to actually create a temporary trigger to fire on an ability being cast, but this way they can. :p

    Might even implement UnregisterTriggeringEvent(). :D

    Anyway, stay tuned. :)

    EDIT:
    Version 1.02 released. I've been working on it pretty much since this post was first created until now. :p
     
  5. emjlr3

    emjlr3 Change can be a good thing Staff Member

    Ratings:
    +396 / 0 / -0
    why does this take a trigger?

    why not just a function or string?
     
  6. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Because it's an events system. People are meant to think of it as one.

    Besides, there's no advantage in making it take a function, for example. How would you remove events if you did that? And there's no efficiency gain. Internally this would have to attach it to a trigger as a condition anyway. :D

    There's all kinds of limitations added by making it take a function or string. For example, this way you could remove the event and add another one, so you can hot-swap the effects of spells at runtime.

    PS. Seems you caught me half way through releasing the latest version. Maybe the new version will help explain. o_O
     
  7. SerraAvenger

    SerraAvenger Cuz I can

    Ratings:
    +236 / 0 / -0
    No fear of hash collision when going from 0000 to zzzz?
     
  8. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Basically... no.

    There's 5 slots per hash value, so collisions are very unlikely. If it collides more than this, then an error will be displayed, and you tell me, and I extend it and release a new version. :)

    Thanks for the question. :D
     
  9. Builder Bob

    Builder Bob Live free or don't

    Ratings:
    +249 / 0 / -0
    This looks pretty good.

    Have you ran any benchmarking to see how much more efficient per cast it is to use this compared to regular registering?

    It'd also be interesting to see how the spell cast time scales the more spell cast triggers you register in a map. How many spells would you have to register to see it become a problem?
     
  10. emjlr3

    emjlr3 Change can be a good thing Staff Member

    Ratings:
    +396 / 0 / -0
    just figured it would be easier for ppl not to have to create triggers and actions and whatever - since this is really supposed to do all that for you - you should just be able to code your Actions functions, then call this do have your Actions function ran on some trigger event
     
  11. Viikuna

    Viikuna No Marlo no game.

    Ratings:
    +265 / 0 / -0
    TriggerExecute is slow, so if you want it fast, use only Condition and only TriggerEvaluate. I use only Condition anyways, so this not a problem for me.

    emjlr3 do you mean something like: RegisterSpellEvent( function Action ) ?
     
  12. emjlr3

    emjlr3 Change can be a good thing Staff Member

    Ratings:
    +396 / 0 / -0
    yes, something like

    JASS:
    call RegisterOnEffect(function Blarg)


    or

    JASS:
    call RegisterOnEndCast( function EEk)


    or even

    JASS:
    call RegisterOnEffect('XXXX', function Blarg)
     
  13. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Or
    JASS:
    call TriggerAddAction(GT_RegisterStartsEffectEvent(CreateTrigger(),'A000'), function ActionsFunc )

    Or
    JASS:
    call TriggerAddCondition(GT_RegisterStartsEffectEvent(CreateTrigger(),'A000'), Condition(function ActionsFuncReturningFalse))

    ?

    It already supports that style of coding. That's why it returns the trigger passed in. And if the trigger is temporary, have it call GT_UnregisterTriggeringEven() and DestroyTrigger(GetTriggeringTrigger()) when appropriate.

    *Shrugs*
    I suppose I'll write AttachActionToSpellEffectEvent('A000', function ActionsFuncReturningFalse) and DetachTriggeringAction() wrappers next version.

    >Have you ran any benchmarking to see how much more efficient per cast it is to use this compared to regular registering?
    No, actually, but I might soon. I didn't consider it too important anyway because... well... it doesn't lag either way. It's not like a timer system that is firing constantly in the background, up to hundreds of times in a moment. But when I next get a moment, I'll check it out, perhaps.

    >It'd also be interesting to see how the spell cast time scales the more spell cast triggers you register in a map. How many spells would you have to register to see it become a problem?
    Yeah. Well this system makes it so that will never be a problem, too. I may also bench this and see what it looks like, anyway. :)

    Thanks for all the feedback so far. ^_^
     
  14. Builder Bob

    Builder Bob Live free or don't

    Ratings:
    +249 / 0 / -0
    Yeah, that's what makes me not bother benchmarking it as well. It doesn't lag either way as you say. No need to benchmark unless you become too curious. ;) This is obviously more efficient for maps with a large quantity of ability triggers.
     
  15. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    >This is obviously more efficient for maps with a large quantity of ability triggers.

    Yeah. And of course, that is the only situation where efficiency would matter anyway.

    Of course, I am fairly curious, and will endeavor to provide a reasonable estimate as to how many triggers you need before this system is more efficient, supported by benchmarks. Maybe tonight. Can't right this minute, though.

    EDIT: AND THE RESULTS ARE IN!

    This is very funny.

    Ready for this?

    -REGISTER FUNCTIONALITY-
    call TriggerRegisterAnyUnitEventBJ(trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    ~470 nanosec (~30 margin of error).
    call GT_RegisterStartsEffectEvent(trigger, 'A000')
    ~4 nanosec (~0.7 margin of error).

    Making this GT over 100 times faster for the initial event registration. So go shave a couple of hundred nanoseconds per spell off your map init (who cares). But if you actually do have to add/remove these events mid game, geez it's fast.
    I'm not actually that suprised that it's a lot faster, but I didn't expect it to be THAT much faster. Turns out that register native that BJ calls internally 16 times must be slow.

    So it takes less than 1% as long to call GTrigger's add function.

    And now... for the bit that really matters...

    -FIRING SPEED-

    We all expected GT to inevitably gain efficiency based on how many different spells are triggered off.

    That indeed is a massive number of spells, where "massive" means 2.

    My tests have shown that GTrigger's fire speed is more or less exactly in between the fire speed of having 1 spell and 2 spells, around a 15 nanosec difference either way.

    So if you trigger off 2 or more spells being cast in your map, your map will gain efficiency from using GTrigger, and probably has no reason not to.

    So basically, every map should use GTrigger for ability events.

    I'm intending to expand the domain of GTrigger to register different kinds of events, too.

    I'd upload my tests here, but I don't want to run out of storage space on TheHelper, which is what would happen if I uploaded all my tests by habit. But I'm happy to if people really want. I'm also happy for people to do their own tests.

    Testing the add function was easy, obviously. In case you're wondering how I tested the firing, there's a spell that fires IMMEDIATELY on unit death (as in, if you call KillUnit, the triggers for that spell will fire before the next line of code is run), and that's Phoenix morph. So I simply registered 50 abilities on that spell effect, then started a stop watch, killed a phoenix, and clocked the stop watch. Each test repeated this 50 times, and I repeated each test about 5 times. I then reduced that 50 to 20, still being slower I reduced it to 5, then 2, then 1 failing trigger and 1 working trigger (both attaching using conditions only). Quite a reliable, appropriate test. :)
    Just to clarify that, all triggers in my bench test attached with a single condition only.
     
  16. Viikuna

    Viikuna No Marlo no game.

    Ratings:
    +265 / 0 / -0
    Remember to do benchmarking also by using only conditions, because it should be faster than using actions. Also TriggerExecute should be slower than TriggerEvaluate.
     
  17. Tom Jones

    Tom Jones N/A

    Ratings:
    +435 / 0 / -0
    JASS:
    function TriggerRegisterAnyUnitEventBJ takes trigger trig, playerunitevent whichEvent returns nothing
        local integer index
    
        set index = 0
        loop
            call TriggerRegisterPlayerUnitEvent(trig, Player(index), whichEvent, null)
    
            set index = index + 1
            exitwhen index == bj_MAX_PLAYER_SLOTS
        endloop
    endfunction


    JASS:
            public function Register$NAME$Event takes trigger t, integer abil returns trigger
                set Register$NAME$Mem=GetMem()
                set Trig[Register$NAME$Mem]=t
                call Link(GetOrCreate$NAME$ListPointer(abil),Register$NAME$Mem)
                return t
            endfunction
    Are you really suprised that your registration is faster? Maybe cut blizzard some slack and do a proper benchmark?
     
  18. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    I did.
    As said, no I'm not. And my benchmark is plenty proper.

    Don't randomly say it's not proper, please. I just spent the last couple of hours carefully setting up and executing decent tests. :D
     
  19. Tom Jones

    Tom Jones N/A

    Ratings:
    +435 / 0 / -0
    Mate, your testing your system against a bj. I can't even comprehend why it didn't occur to you that thát is simply stupid? How can you call that proper benchmarking?

    In my honest opinion you'll have to test the best native alternative against your system for a proper benchmarking.
     
  20. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    >I can't even comprehend why it didn't occur to you that thát is simply stupid?
    Maybe it's not.

    You do realise that the alternative is inlining that BJ?

    You need to add the event for all 16 players. I don't know of any alternative, this is always the way I believe it's been done. This is how spells are triggered (unless, like in an AoS, perhaps it is registered on hero selection for only the one player, which would still be apparently slower than GTrigger anyway). This is how spells have always been triggered, to the best of my knowledge.

    Furthermore, why do you care? That's the irrelevant test. Ok, GTrigger is clearly faster, but the relevant test is the firing speed, not the event registration speed.

    If you want to take this up further, tell me another native way to register on any unit casting a spell, and I'll be happy to bench it against that too.
     

Share This Page