1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. The Lich King demands your service! We've reached the 19th edition of the Icon Contest. Come along and make some chilling servants for the one true king.
    Dismiss Notice
  4. The 4th SFX Contest has started. Be sure to participate and have a fun factor in it.
    Dismiss Notice
  5. The poll for the 21st Terraining Contest is LIVE. Be sure to check out the entries and vote for one.
    Dismiss Notice
  6. The results are out! Check them out.
    Dismiss Notice
  7. Don’t forget to sign up for the Hive Cup. There’s a 555 EUR prize pool. Sign up now!
    Dismiss Notice
  8. The Hive Workshop Cup contest results have been announced! See the maps that'll be featured in the Hive Workshop Cup tournament!
    Dismiss Notice
  9. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Generic Unit Event

Discussion in 'Submissions' started by MyPad, Dec 22, 2017.

  1. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    A simple unit event generalizing library that uses UnitDex (by TriggerHappy) for leak clean-up. It can be used for creating generic versions of unit events which do not have a corresponding player unit event and either perfecting the behavior of the event to be detectable or be used for further insight on certain unit events.

    This utilizes a naive bucket method, wherein the distribution of units to their own buckets is done procedurally, analogous to filling up 10 buckets with water, one bucket at a time.

    Code (vJASS):

    library GenericUnitEvent /*
        --------------
        */
    requires /*
        --------------
       
            --------------
            */
    UnitDex  /*
            --------------
                #? GroupUtils
                #? WorldBounds
           
                By TriggerHappy:
                link: https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
               
            --------------
            */
    Table    /*
            --------------
                By Bribe:
                link: https://www.hiveworkshop.com/threads/snippet-new-table.188084/
               
            --------------
            */
    ListT    /*
            --------------
                ## Table
                #  Alloc
               
                By Bannar:
                link: https://www.hiveworkshop.com/threads/containers-list-t.249011/page-2#post-3271599
               
             ----------------------------------------------------
            |                                                    |
            |       GenericUnitEvent                             |
            |           - MyPad                                  |
            |                                                    |
             ----------------------------------------------------
           
             -------------------------------------------------------------------------------
            |
            |   Restrictions:
            |
            |       - Units cannot be manually deindexed, as the system treats such an event
            |       as a unit removal event.
            |
            |-------------------------------------------------------------------------------
            |
            |   Description:
            |  
            |       - A library which makes unitevent exclusive events easy to register and
            |       listen to. Essentially extends unitevent to pseudo-playerunitevent.
            |
            |       - Makes damage detection a breeze, as well as target acquisition
            |       and target in range events.
            |
            |
            |--------------------------------------------------------------------------------
            |
            |   API:
            |
            |   struct GenericUnitEvent:
            |       static method listen(unitevent whichEvent) -> StubUnitEvent
            |           - Enables the system to listen to that specified unitevent.
            |
            |       static method registerUnit(unit whichUnit) -> boolean
            |           - Registers a unit to the list of listened unitevents.
            |             This is automatically called on unit creation
            |             if REGISTER_ON_STARTUP is set to true. (Default)
            |
            |   struct StubUnitEvent:
            |       method addHandler(code callback) -> triggercondition
            |           - Adds a callback function to be invoked upon the execution
            |             of a certain event.
            |
            |       method removeHandler(triggercondition cond)
            |           - Removes a callback function from a certain event.
            |
            |   Functions:
            |       function RegisterUnitEvent(unitevent whichEvent, code callback) -> triggercondition
            |           - Executes a condensed version of the code, without having to deal too much
            |             with structs.
            |
            |       function RegisterAnyUnitEvent(unitevent uv, code c) -> triggercondition
            |           - A deprecated function that invokes RegisterUnitEvent.
            |
            |       function RegisterUnitEventById(integer id, code callback) -> trggercondition
            |           - Internally calls RegisterUnitEvent
            |
            |       function GetUnitEventId() -> unitevent
            |           - Returns the triggering event in parallel with GetTriggerEventId()
            |             Can also persist where GetTriggerEventId might fail.
            |
            |--------------------------------------------------------------------------------
            |
            |   Changelogs:
            |
            |       v.1.1 - Rewritten the entire library.
            |             - Removed SetUnitBucketSize.
            |
            |       v.1.0 - Release
            |
            |---------------------------------------------------------------------------------
            |
            |   Programmer notes:
            |
            |       In the previous version, the following would compile:
            |           local integer i
            |           call GenericUnitEvent(i).addHandler(code callback)
            |
            |       Now, the lines above would no longer compile. Instead, try this:
            |
            |           call GenericUnitEvent.listen(yourUnitEvent).addHandler(code callback)
            |
             -----------------------------------------------------------------------------------
           
             -----------------------
            |
            |   Credits:
            |
            |       - AGD for the inputs on this system.
            |       - Nestharus for the ideal management
            |           of the linked list structure
            |           (Which the rewrite was guided on)
            |       - Bribe for Table (The HIVE one)
            |       - Bannar for ListT
            |
             -----------------------
        */


    native UnitAlive takes unit id returns Boolean

    globals
        private constant boolean REGISTER_ON_STARTUP    = true
        private constant boolean ADVANCED_DEBUG         = false
        private constant integer MAX_BUCKET_SIZE        = 5
        private constant integer REFRESH_AMOUNT         = 3
    endglobals

    private function DuplicateList takes IntegerList whichList returns IntegerList
        local IntegerList newList   = IntegerList.create()
        local IntegerListItem iter  = whichList.first
        loop
            exitwhen iter == 0
            call newList.push(iter.data)
            set iter = iter.next
        endloop
        return newList
    endfunction

    private module Initializer
        private static method onInit takes nothing returns nothing
            call thistype.init()
        endmethod
    endmodule

    private struct StubUnitEvent extends array
        implement Alloc
       
        private static Table eventMap                   = 0
       
        readonly unitevent event
        readonly trigger handlerTrig
       
        method removeHandler takes triggercondition cond returns nothing
            call TriggerRemoveCondition(this.handlerTrig, cond)
        endmethod
       
        method addHandler takes code callback returns triggercondition
            return TriggerAddCondition(this.handlerTrig, Condition(callback))
        endmethod
       
        static method peek takes eventid whichId returns thistype
            return eventMap[GetHandleId(whichId)]
        endmethod
       
        static method request takes unitevent whichEvent returns thistype
            local thistype this         = eventMap[GetHandleId(whichEvent)]
            if this == 0 then
                set this                = .allocate()
                set this.event          = whichEvent
                set this.handlerTrig    = CreateTrigger()
                set eventMap[GetHandleId(whichEvent)] = this
            endif
            return this
        endmethod
       
        private static method init takes nothing returns nothing
            set thistype.eventMap       = Table.create()
        endmethod
       
        implement Initializer
    endstruct

    struct GenericUnitEvent extends array
        implement Alloc
       
        private static code  registerPointer    = null
       
        private static group swap           = null
        private static group container      = null
        private static group tempRegContain = null
       
        private static Table stubMap        = 0
        private static IntegerList stubList = 0
       
        private static IntegerList array reqList
        private static integer removeCount  = 0
       
        //  For GenericUnitEvent members
        private trigger detector
        private group   groupHolder
        private integer unitsRegistered
        private integer unitsRemoved
       
        private IntegerList reqListPointer
        private IntegerListItem reqListItem
       
        //  For UnitDex members only
        private static boolean array existsForIndex
        private static thistype array keyGroup
       
        //  Iterate over global group
        private static StubUnitEvent tempStub   = 0
       
    static if ADVANCED_DEBUG then
        debug private static boolean stackTrace     = false
        debug private static thistype stackObject   = 0
    endif
        readonly static unitevent execUnitEvent     = null
       
        private static method onEventExecution takes nothing returns nothing
            local StubUnitEvent object  = StubUnitEvent.peek(GetTriggerEventId())
            local unitevent lastEv
            if object != 0 then
                set lastEv          = .execUnitEvent
                set .execUnitEvent  = object.event
               
                if IsTriggerEnabled(object.handlerTrig) then
                    call TriggerEvaluate(object.handlerTrig)
                endif
               
                set .execUnitEvent  = lastEv
                set lastEv          = null
            endif
        endmethod
       
        private method destruct takes nothing returns nothing
            local unit u
            local integer id
           
            call DestroyTrigger(this.detector)
            loop
                set u   = FirstOfGroup(this.groupHolder)
                set id  = GetUnitId(u)
               
                exitwhen u == null
               
                set keyGroup[id]        = 0
                set existsForIndex[id]  = false
               
                call GroupRemoveUnit(.container, u)
                call GroupRemoveUnit(this.groupHolder, u)
                call GroupAddUnit(.tempRegContain, u)
            endloop
            call DestroyGroup(this.groupHolder)
            call this.reqListPointer.erase(this.reqListItem)
           
        static if ADVANCED_DEBUG then
            debug set .stackTrace   = true
            debug set .stackObject  = this
        endif
       
            call ForForce(bj_FORCE_PLAYER[0], .registerPointer)
       
        static if ADVANCED_DEBUG then
            debug set .stackTrace   = false
        endif
       
            set .removeCount         = .removeCount - this.unitsRemoved
                   
            set this.unitsRegistered = 0
            set this.unitsRemoved    = 0
            set this.reqListPointer  = 0
            set this.reqListItem     = 0
            set this.groupHolder     = null
            set this.detector        = null
           
            call this.deallocate()
        endmethod
       
        private static method construct takes nothing returns thistype
            local thistype this     = .allocate()
            set this.detector       = CreateTrigger()
            set this.groupHolder    = CreateGroup()
            call TriggerAddCondition(this.detector, Condition(function thistype.onEventExecution))
            return this
        endmethod
       
        private static method onNewStubUnitEvent takes StubUnitEvent temp returns nothing
            local unit u            = FirstOfGroup(.container)
            local group tempSwap    = .swap
            local integer id        = GetUnitId(u)
            loop
                exitwhen u == null
               
                call GroupRemoveUnit(.container, u)
                call GroupAddUnit(.swap, u)
               
                call TriggerRegisterUnitEvent(keyGroup[id].detector, u, temp.event)
               
                set u   = FirstOfGroup(.container)
                set id  = GetUnitId(u)
            endloop
           
            set .swap           = .container
            set .container      = tempSwap
            set tempSwap        = null
            set u               = null
        endmethod
       
        private static method onListenCallback takes nothing returns nothing
            call thistype.onNewStubUnitEvent(.tempStub)
        endmethod
       
        static method registerUnit takes unit whichUnit returns boolean
            local integer id = GetUnitId(whichUnit)
            local thistype this
            local IntegerListItem iter
           
            if (id == 0) or existsForIndex[id] then
                return false
            endif
            // Find out if there are any instances that need to be filled up.
            if .reqList[1].size() != 0 then
                set this = .reqList[1].first.data
                static if ADVANCED_DEBUG then
                    debug if .stackTrace then
                        debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "GenericUnitEvent::registerUnit -> Bucket retrieval type:: Node reference")
                    debug endif
                endif
            else
                set this = thistype.construct()
                static if ADVANCED_DEBUG then
                    debug if .stackTrace then
                        debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "GenericUnitEvent::registerUnit -> Bucket retrieval type:: Node creation")
                    debug endif
                endif
               
                call .reqList[0].push(this)
               
                set this.reqListPointer = .reqList[1]
                set this.reqListItem    = .reqList[1].push(this).last
            endif
            static if ADVANCED_DEBUG then
                debug if .stackTrace then
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "GenericUnitEvent::registerUnit -> Bucket instance identified {" + I2S(this) + "}")
                debug endif
            endif
            call GroupAddUnit(.container, whichUnit)
           
            set this.unitsRegistered    = this.unitsRegistered + 1
            call GroupAddUnit(this.groupHolder, whichUnit)
            set existsForIndex[id]      = true
            set keyGroup[id]            = this
           
            //  Register all StubUnitEvents for the unit
            set iter = .stubList.first
            loop
                exitwhen iter == 0
                call TriggerRegisterUnitEvent(this.detector, whichUnit, StubUnitEvent(iter.data).event)
                set iter = iter.next
            endloop
            if this.unitsRegistered >= MAX_BUCKET_SIZE then
                call this.reqListPointer.erase(this.reqListItem)
               
                set this.reqListPointer = .reqList[2]
                set this.reqListItem    = .reqList[2].push(this).last
            endif
            return true
        endmethod
       
        private static method deregisterUnit takes unit whichUnit returns boolean
            local integer id = GetUnitId(whichUnit)
            local thistype this
           
            if (id == 0) or (not existsForIndex[id]) or UnitAlive(whichUnit) then
                return false
            endif
           
            set this = keyGroup[id]
            //  If previously belonging to a full list, move it to list that requests filling up
            if this.reqListPointer == .reqList[2] then
                call .reqList[2].erase(this.reqListItem)
               
                set this.reqListPointer = .reqList[1]
                set this.reqListItem    = .reqList[1].unshift(this).first
            endif
            call GroupRemoveUnit(.container, whichUnit)
           
            call GroupRemoveUnit(this.groupHolder, whichUnit)
            set keyGroup[id]        = 0
            set existsForIndex[id]  = false
           
            set this.unitsRegistered = this.unitsRegistered - 1
            set this.unitsRemoved    = this.unitsRemoved + 1
            set .removeCount         = .removeCount + 1
           
            if (this.unitsRemoved >= REFRESH_AMOUNT) then
                static if ADVANCED_DEBUG then
                    debug if .stackTrace then
                        debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "GenericUnitEvent::deregisterUnit -> calling .destruct() {Attribute: " + I2S(this) + "}")
                    debug endif
                endif
                call this.destruct()
                static if ADVANCED_DEBUG then
                    debug if .stackTrace then
                        debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 50000, "GenericUnitEvent::deregisterUnit -> .destruct() procedure finished!")
                    debug endif
                endif          
            elseif ((.reqList[0].size() > 2) and (.removeCount >= REFRESH_AMOUNT*.reqList[0].size()/2)) then
                set .reqList[3]     = DuplicateList(.reqList[1])
                loop
                    exitwhen .reqList[3].first == 0
                   
                    call thistype(.reqList[3].first.data).destruct()
                    call .reqList[3].shift()
                endloop
                call .reqList[3].destroy()
                set .reqList[3]     = 0
            endif
            return true
        endmethod
       
        static method listen takes unitevent whichEvent returns StubUnitEvent
            local StubUnitEvent object  = StubUnitEvent.request(whichEvent)
            if not .stubMap.has(object) then
                set .stubMap[object]     = stubList.push(object).last
                set .tempStub            = object
               
                call ForForce(bj_FORCE_PLAYER[0], function thistype.onListenCallback)
            endif
            return object
        endmethod
        private static method onReRegister takes nothing returns nothing
            local unit u
            local integer id
            loop
                set u   = FirstOfGroup(.tempRegContain)
                set id  = GetUnitId(u)
               
                exitwhen u == null
               
                call thistype.registerUnit(u)
                call GroupRemoveUnit(.tempRegContain, u)
            endloop
        endmethod
       
        private static method onUnitExit takes nothing returns nothing
            call thistype.deregisterUnit(GetIndexedUnit())
        endmethod
    static if REGISTER_ON_STARTUP then
        private static method onUnitEnter takes nothing returns nothing
            call thistype.registerUnit(GetIndexedUnit())
        endmethod
    endif
       
        private static method initListener takes nothing returns nothing
        static if REGISTER_ON_STARTUP then
            call OnUnitIndex(function thistype.onUnitEnter)
        endif
            call OnUnitDeindex(function thistype.onUnitExit)
        endmethod
        private static method init takes nothing returns nothing
            set thistype.swap               = CreateGroup()
            set thistype.container          = CreateGroup()
            set thistype.tempRegContain     = CreateGroup()
           
            set thistype.registerPointer    = function thistype.onReRegister
            set thistype.stubMap            = Table.create()
            set thistype.stubList           = IntegerList.create()
           
            //  Holds all instances
            set thistype.reqList[0]         = IntegerList.create()
           
            //  Holds instances that are in need of filling up
            set thistype.reqList[1]         = IntegerList.create()
            //  Holds instances that are already filled up.
            set thistype.reqList[2]         = IntegerList.create()
           
            call thistype.initListener()
        endmethod
       
        implement Initializer
    endstruct

    function RegisterUnitEvent takes unitevent whichEvent, code callback returns triggercondition
        return GenericUnitEvent.listen(whichEvent).addHandler(callback)
    endfunction

    function RegisterAnyUnitEvent takes unitevent uv, code c returns triggercondition
        debug call BJDebugMsg("GenericUnitEvent::RegisterAnyUnitEvent -> This has been deprecated, please use GenericUnitEvent::RegisterUnitEvent instead.")
        return RegisterUnitEvent(uv, c)
    endfunction

    function RegisterUnitEventById takes integer id, code callback returns triggercondition
        return RegisterUnitEvent(ConvertUnitEvent(id), callback)
    endfunction

    function GetUnitEventId takes nothing returns unitevent
        return GenericUnitEvent.execUnitEvent
    endfunction

    endlibrary
     


    This could be used to unearth possible exploits which have not been seen before or add a twist to existing ones.
     
    Last edited: Feb 5, 2019
  2. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Nice Idea, a lib like RegisterPlayerUnitEvent but for specific instead of playerunit events =).

    But there are important issues to be resolved and some of these issues are also present in your other submissions.

    First, it is important to observe modularity when making a resource (This issue is also apparent in your AllocationAndLinks).
    For instance, this resource is about registering events but you included a functionality of a unit indexer (Not only for system use as a backup, but you also made its functionalities public). I think it would be more preferable to make UnitDex a hardcoded requirement here.

    Another thing is to please be consistent with your naming style. In some parts, you followed the JPAG but in other parts, you used C-like namings.

    Maybe you could also support something like
    RegisterGenericUnitEvent(unitevent, code)
    and an equivalent for Deregister/Unregister because sometimes we need dynamic event handlers.

    I have not looked into details yet, these are just those apparent from skimming.
     
    Last edited: Dec 24, 2017
  3. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    I got the idea of generating specific unit events from @Spellbound who wanted to examine the behaviour of some natives.

    You're right, I just included that because I don't want others to be burdened with getting the necessary libraries, but I'll change that in the future.
    I actually added some more textmacros with the AllocationAndLinks library for easy recreation.

    Okay, I didn't notice that. Is it the part where I initialise the variable Num01?

    I could support something like that, though that would require my Pseudo-Var library.

    Overall, I think I should keep the modules as they are. , but I would need to generate two structs that would specifically resolve the Register and Deregister function (will add a library for that). It might take some time, but I'll see with what I could put up. :)
    [/s]

    EDIT:

    New extension library made! Bumping!

    EDIT (2):

    Wurst version added!
     
    Last edited: Apr 23, 2018
  4. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Another bump! Having recoded everything from scratch, the size of the libraries have decreased from before, and some functionality might be lost, but I will see to it that I can re-add them.
     
  5. KILLCIDE

    KILLCIDE

    Administrator

    Joined:
    Jul 22, 2015
    Messages:
    3,498
    Resources:
    20
    Models:
    2
    Icons:
    10
    Spells:
    7
    Tutorials:
    1
    Resources:
    20
    Given that Wurst and JASS/vJASS submissions are in seperate forums, it might be a better idea to have this as two seperate threads.
     
  6. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Yeah. Later on that.
     
  7. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Currently, the only downside I can see with the design is that it greatly increases the script size greatly with each module implementation. Otherwise, it looks good. This can easily be solved by putting your the system's core inside a struct, and then provide an api for event registration/deregistration. Something like for example
    GenericUnitEvent.registerHandler(unitevent whichEvent, code handler)
    and
    GenericUnitEvent.register(unit u)
    . Then, what the module would only do is to automatically call these methods for every newly indexed unit who passes a filter and likewise unregister every deindexed unit. By doing this, you would also be able to cover the functionality of your UnitEventUtils in the same library.
     
  8. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    That would require some rewriting to accommodate said changes, but I'll see if I can merge them into one library, or add another module to the extension.
    The reason I split them apart into two libraries in the first place is to observe modularity the way I see it.

     
  9. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    It's okay imo since the aim of the two libs is quite the same. The only difference is their way of implementation - the first uses module while the second directly uses functions, and also a textmacro.
     
    Last edited: Jul 16, 2018
  10. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Btw, by applying my proposed design, you can also greatly minimize the handle count by registering all the events into a single bucket (what I mean is that there's no need to restrict one event per bucket). Instead, every time a user registers an event, you add the event to all currently active buckets. Then as for calling the proper event handlers, you can do:
    Code (vJASS):
    private static method onEventFire ...
        //...
        call TriggerEvaluate(handlerTrigger[GetTriggerEventId()])
        //...
    endmethod
     
     
  11. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    That approach is basically what I did in the second library. Since I don't have a reliable internet connection, it might take me a while to update this.

    However, I worry about natives not returning the correct values in the handler function due to how they are implemented to always check for the event. A forced trigger evaluation is my primary consideration on this case, since GetTriggerEventId returns the current event ID of the current trigger, at least to my knowledge.

    To work around this, (if needed), I'll add the ability to assign global values to the system based on the natives themselves.
     
  12. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    I'm not so sure on what you mean but if you mean something like this
    Code (vJASS):

    function OnEventFire()
        // The event that fired this function is a spell effect event
        //...
        TriggerEvaluate(table.trigger[GetSpellAbilityId()])
        // ^ Are you unsure if the event responses like GetSpellTargetUnit() will not return the proper value since the trigger was being forced evaluated?
     

    In this case, those event responses inside the forced evaluated trigger would return the same value inside the function calling the TriggerEvaluate() so it's safe (problems would only arise during recursions coz some event responses aren't designed to be recursion safe, but then again, not doing that forced evaluate still face that same issue). Again, I'm not sure if this is what you mean so please tell me if not.
     
  13. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Ahh, so that is how it will behave? Your presumption is correct. I might not be able to update this now, but I will do so.
     
  14. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Updated to version 1.1 with severe consequences:

    Changelog

    • Destroyed backwards-compatibility. (This will be a lot more impactful towards those already using this)
    • Combined both Main library and Add-on library, as per @AGD 's suggestions.
    • Removed a lot of functions (Convenience)
    • Removed the module.


    Now, writing up a damage handler would be too easy, now without a lot of the complexity of having to attach the units to buckets manually.
     
    Last edited: Aug 18, 2018
  15. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,053
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Just an FYI - the .nodeMap references should probably be a sized or arithmetically-2D array. Why? Because if you know the size and indices of the Table before you even start, you're able to do this:

    nodeMap[this*maxIndex + index]

    It's much faster than using a hashtable.

    Edit: I see - so you have a bunch of different types stored in there. Well then the performance trade-off isn't really substantial to merit creating a bunch of array variables.
     
  16. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    397
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Code (vJASS):

                set this.nodeMap               = Table.create()
                //  Next
                set this.nodeMap[1]            = Table.create()
                //  Unit's Pointer
                set this.nodeMap[0]            = Table.create()
                //  Previous
                set this.nodeMap[-1]           = Table.create()
                //  Internal instance's event count
                set this.nodeMap[-2]           = Table.create()
                //  Internal instance's unit count
                set this.nodeMap[-3]           = Table.create()
                //  Internal instance's group
                set this.nodeMap[-4]           = Table.create()
                //  Internal instance's trigger
                set this.nodeMap[-5]           = Table.create()

                //  Internal allocator of boolexprs
                set this.nodeMap[2]            = Table.create()
                //  Internal next of boolexprs
                set this.nodeMap[3]            = Table.create()
                //  Internal prev of boolexprs
                set this.nodeMap[4]            = Table.create()
                //  Internal boolexpr holder
                set this.nodeMap[5]            = Table.create()
                //  Internal boolexpr's pointer
                set this.nodeMap[6]            = Table.create()
     

    Instead of manually allocating many tables, you can shorten and optimize it by using a TableArray
    Code (vJASS):

    private TableArray nodeMap
    //...
    static method create takes nothing returns thistype
        //...
        set this.nodeMap = TableArray[13] //TableArray allocation is O(1)
        //...
    endmethod
     


    You also don't need the variable 'this.size' since its value is constant, so you can directly use 'DEFAULT_MAX_SIZE'

    It seemed to me that you are not using the bucket technique despite of saying so. Basically, what you did is have a single trigger per unitevent. Then all units are registered into each of those trigger. A bucket technique should divide the total number of units into groups. Each group has a certain size and is assigned its own trigger. What the technique does then achieve is that everytime the maximum event leaks is reached (for a certain group/trigger), the trigger rebuilding is not so expensive in performance since the trigger only contains a group and not all of the units.

    But it is up to you if you will implement the said technique since it also has its cons. I just pointed out your apparent misconception of the bucket method.
    Code (vJASS):
    /*
        *       It utilizes the bucket method of storing events, so that not too many handles will
        *       be created.
    */

    The contrary is true. Bucket method results in more trigger handles but it's purpose is to make the trigger refreshing not so heavy.

    As for the downside of the bucket method that I mentioned, not only does it increase the number of triggers (which is negligible) but also the maximum allowable leaked events. If N is the max number of leaked events without the bucket method, then it will be N times the current number of buckets with the bucket method. Note that the potential number of buckets has no set limit if the bucket size is constant. So with an average of 500 units in a map at any given time and a bucket size of 50, the total number of buckets is 10 and the maximum potential event leaks will be 10*N.

    And lastly, the way you currently handle leaked events is problematic:
    Code (vJASS):

    //[-3] -> registered unit count
    //[-2] -> unitevents handles created
    if (this.nodeMap[-3].integer[curIndex] <= 0) and (this.nodeMap[-2].integer[curIndex] >= this.maxSize) then
        call DestroyTrigger(this.getTrigger(curIndex))
        call DestroyGroup(this.getGroup(curIndex))
        //...
     

    This will continue to leak so long as there is a single unit registered to the system. And it's possible that a map will never run out of a registered unit at any given time while there will constantly be unit registration and unregistration (which causes the leak) the happens along the line.
    It should be
    Code (vJASS):

    if [number of unregistered units] >= [max allowable unitevent handles leaks];
        recreate trigger;
        add again all the currently registered units into the trigger;
     



    The current API is good. You could also add equivalent for handler deregistration, but is not mandatory especially if it complicates the code.
     
  17. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    Code (vJASS):

                set this.nodeMap              = Table.create()
                //  Next
                set this.nodeMap[1]           = Table.create()
                //  Unit's Pointer
                set this.nodeMap[0]           = Table.create()
                //  Previous
                set this.nodeMap[-1]          = Table.create()
                //  Internal instance's event count
                set this.nodeMap[-2]          = Table.create()
                //  Internal instance's unit count
                set this.nodeMap[-3]          = Table.create()
                //  Internal instance's group
                set this.nodeMap[-4]          = Table.create()
                //  Internal instance's trigger
                set this.nodeMap[-5]          = Table.create()

                //  Internal allocator of boolexprs
                set this.nodeMap[2]           = Table.create()
                //  Internal next of boolexprs
                set this.nodeMap[3]           = Table.create()
                //  Internal prev of boolexprs
                set this.nodeMap[4]           = Table.create()
                //  Internal boolexpr holder
                set this.nodeMap[5]           = Table.create()
                //  Internal boolexpr's pointer
                set this.nodeMap[6]           = Table.create()
     


    Yeah, using TableArray would be more intuitive in this case. Given that this was actually a procedural approach, I first thought of making it work. Then, I would only optimize it later on.

    Indeed, though there is a reason why it is named DEFAULT_MAX_SIZE instead of MAX_SIZE. (I forgot to include the member maxSize in the previous version, or did I?)

    If that is the bucket method, then what I must be doing is a naive version of it. What I am sure of in my approach is that it does not divide the total number of units and register them onto certain buckets; rather, it fills up each bucket as registration goes. Then, we assume that one starts with a predefined amount of buckets of 10 on a typical bucket technique.

    Case in point, we have those 500 units divided by 50 units per trigger. Rather than evenly dividing the 500 units into those ten triggers, the systems goes about it one by one, eventually filling up ten buckets. Now, if we compare it to the above method, the results would end up being similar to each other.

    Now, we lower it from 500 to 300 units. We would end up with (300/50) 6 buckets filled up, compared to that bucket technique above, which would have 30 units each. (We have to evenly distribute the number of units here).

    Now, suppose we lower it even further. That is where your point becomes clear; for a small number of units, using the bucket technique would prove to be better than my naive bucket technique. However, for a large number of units, the benefits of the original bucket technique greatly diminish to the point of irrelevance, since the number of registered units would reach some sort of equilibrium in either of these techniques.

    Thus, I will keep the method above, but I will redefine bucket to a naive bucket, since for a large amount of units, it wouldn't matter anyway.

    I agree on that. The one above was designed in consideration for melee maps.
    Resolved with the upcoming update on the script.

    That could be arranged, since I've got the linked list setup going for the triggers. All I need to do now is, maybe declare another Table instance that will hold the TableArrays that are mapped to the handle ids of the equivalent boolean expressions.

    EDIT:

    Updated the system with a new function, SetUnitEventBucketSize. I forgot to incorporate it into the changelog beforehand.

    Changelog:
    • Fixed addUnit such that removeUnit will work properly. (removeUnit was not working due to a missing line in addUnit that binds a unit to a certain bucket)
    • Changed the condition of the statement in removeUnit to allow bucket clearance. (removeUnit now clears the bucket if the bucket is half-empty, and if the number of instances have exceeded the maximum size of the bucket (since maxSize is alterable).
     
    Last edited: Aug 21, 2018
  18. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,320
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    I have now rewritten the library completely from scratch:

    Changelogs:


    • Now, it no longer uses the naïve bucket method. It will fill up the buckets until they reach the defined threshold.
      When a unit belonging to a full bucket is removed from the game, the bucket will obtain the highest priority of being filled up.

    • Refresh has been reworked. Now, the list refreshes locally after reaching another defined threshold (must be less than max), and globally if no refreshes have occurred prior to exceeding the following value: (Such a case is when most of the buckets to be filled have a certain amount that does not deviate a lot, or tend to be more evenly-distributed)
      listOfBuckets_size*threshold/2


    • Removed SetUnitBucketSize in favor for a global trigger group, with a list of events per unit, and a list of units per bucket.