System Advanced Indexing & Data Storage

Discussion in 'Systems and Snippets' started by Jesus4Lyf, Jun 29, 2009.

  1. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Advanced Indexing and Data Storage​

    Version 1.1.0​

    Requirements:
    - Jass NewGen

    JASS:
    //  
    //        _   ___ ___  ___    _______________________________________________
    //       /_\ |_ _|   \/ __|   ||     A D V A N C E D   I N D E X I N G     ||
    //      / _ \ | || |) \__ \   ||                  A N D                    ||
    //     /_/ \_\___|___/|___/   ||         D A T A   S T O R A G E           ||
    //            By Jesus4Lyf    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //                                                                    v 1.1.0
    //      What is AIDS?
    //     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //          AIDS assigns unique integers between 1 and 8191 to units which enter
    //          the map. These can be used for arrays and data attaching.
    //          
    //          AIDS also allows you to define structs which are created automatically
    //          when units enter the map, and filtering which units should be indexed
    //          as well as for which units these structs should be created.
    //          
    //      How to implement?
    //     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //          Simply create a new trigger object called AIDS, go to 'Edit -> Convert
    //          to Custom Text', and replace everything that's there with this script.
    //
    //          Save the map, close it, reopen it, and then delete the "!" from the
    //          FAR left side of the next lines (so "external" will line up with this line):
    //!          external ObjectMerger w3a Adef AIDS anam "State Detection" ansf "(AIDS)" aart "" arac 0
    //
    //          At the top of the script, there is a 'UnitIndexingFilter' constant
    //          function. If the function returns true for the unit, then that unit
    //          will be automatically indexed. Setting this to true will automatically
    //          index all units. Setting it to false will disable automatic indexing.
    //
    //      Functions:
    //     ¯¯¯¯¯¯¯¯¯¯¯¯
    //          function GetUnitId takes unit u returns integer
    //              - This returns the index of an indexed unit. This will return 0
    //                if the unit has not been indexed.
    //              - This function inlines. It does not check if the unit needs an
    //                index. This function is for the speed freaks.
    //              - Always use this if 'UnitIndexingFilter' simply returns true.
    //
    //          function GetUnitIndex takes unit u returns integer
    //              - This will return the index of a unit if it has one, or assign
    //                an index if the unit doesn't have one (and return the new index).
    //              - Use this if 'UnitIndexingFilter' doesn't return true.
    //
    //          function GetIndexUnit takes integer index returns unit
    //              - This returns the unit which has been assigned the 'index'.
    //
    //      AIDS Structs:
    //     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //          - Insert: //! runtextmacro AIDS() at the top of a struct to make it
    //            an AIDS struct.
    //          - AIDS structs cannot be created or destroyed manually. Instead, they
    //            are automatically created when an appropriate unit enters the map.
    //          - You cannot give members default values in their declaration.
    //            (eg: private integer i=5 is not allowed)
    //          - You cannot use array members.
    //          - AIDS structs must "extend array". This will remove some unused
    //            functions and enforce the above so there will be no mistakes.
    //          - There are four optional methods you can use in AIDS structs:
    //              - AIDS_onCreate takes nothing returns nothing
    //                  - This is called when the struct is 'created' for the unit.
    //                  - In here you can assign members their default values, which
    //                    you would usually assign in their declarations.
    //                    (eg: set this.i=5)
    //              - AIDS_onDestroy takes nothing returns nothing
    //                  - This is called when the struct is 'destroyed' for the unit.
    //                  - This is your substitute to the normal onDestroy method.
    //              - AIDS_filter takes unit u returns boolean
    //                  - This is similar to the constant filter in the main system.
    //                  - Each unit that enters the map will be tested by each AIDS
    //                    struct filter. If it returns true for that unit, that unit
    //                    will be indexed if it was not already, the AIDS struct will
    //                    have its AIDS_onCreate method called, and later have its
    //                    AIDS_onDestroy method called when the index is recycled.
    //                  - Not declaring this will use the default AIDS filter instead.
    //              - AIDS_onInit takes nothing returns nothing
    //                  - This is because I stole your onInit function with my textmacro.
    //          - You can use '.unit' from any AIDS struct to get the unit for which
    //            the struct is for.
    //          - The structs id will be the units index, so getting the struct for
    //            a unit inlines to a single native call, and you can typecast between
    //            different AIDS structs. This is the premise of AIDS.
    //          - Never create or destroy AIDS structs directly.
    //          - You can call .AIDS_addLock() and AIDS_removeLock() to increase or
    //            decrease the lock level on the struct. If a struct's lock level is
    //            not 0, it will not be destroyed until it is reduced to 0. Locks just
    //            put off AIDS struct destruction in case you wish to attach to a timer
    //            or something which must expire before the struct data disappears.
    //            Hence, not freeing all locks will leak the struct (and index).
    //
    //      PUI and AutoIndex:
    //     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //          - AIDS includes the PUI textmacros and the AutoIndex module, because
    //            these systems are not compatible with AIDS but have valid and distinct
    //            uses.
    //          - The PUI textmacros are better to use for spells than AIDS structs,
    //            because they are not created for all units, just those targetted by
    //            the spell (or whatever else is necessary).
    //          - The AutoData module is good for very simple array syntax for data
    //            attachment (although I don't recommend that people actually use it,
    //            it's here mostly for compatability). Note that unlike the PUI textmacros,
    //            units must pass the AIDS filter in order for this module to work with
    //            them. This is exactly as the same as in AutoIndex itself (AutoIndex
    //            has a filter too).
    //          
    //      Thanks:
    //     ¯¯¯¯¯¯¯¯¯
    //          - Romek, for writing 90% of this user documentation, challenging my
    //            interface, doing some testing, suggesting improvements and inspiring
    //            me to re-do my code to include GetUnitIndex as non-inlining.
    //          - grim001, for writing the AutoData module, and AutoIndex. I used the
    //            on-enter-map method that he used. Full credits for the AutoData module.
    //          - Cohadar, for writing his PUI textmacros. Full credits to him for these,
    //            except for my slight optimisations for this system.
    //            Also, I have used an optimised version of his PeriodicRecycler from
    //            PUI in this system to avoid needing a RemoveUnitEx function.
    //          - Vexorian, for helping Cohadar on the PUI textmacro.
    //          - Larcenist, for suggesting the AIDS acronym. Originally he suggested
    //            'Alternative Index Detection System', but obviously I came up with
    //            something better. In fact, I'd say it looks like the acronym was
    //            an accident. Kinda neat, don't you think? :P
    //
    //      Final Notes:
    //     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    //          - With most systems, I wouldn't usually add substitutes for alternative
    //            systems. However, UnitData systems are an exception, because they
    //            are incompatible with eachother. Since using this system forbids
    //            people from using the real PUI or AutoIndex, and a lot of resources
    //            use either of these, it made sense not to break them all.
    //
    //          - If this documentation confused you as to how to use the system, just
    //            leave everything as default and use GetUnitId everywhere.
    //
    //          - To use this like PUI (if you don't like spamming indices) simply
    //            make the AIDS filter return false, and use GetUnitIndex.
    //
    library AIDS initializer InitAIDS
        //==============================================================================
        // Configurables
        //
        globals
            private constant boolean USE_PERIODIC_RECYCLER = false
            private constant real PERIOD = 0.03125 // Recycles 32 units/second max.
                                                   // Lower to be able to recycle faster.
                                                   // Only used if USE_PERIODIC_RECYCLER
                                                   // is set to true.
            
            private constant integer LEAVE_DETECTION_ABILITY = 'AIDS'
        endglobals
        
        private function UnitIndexingFilter takes unit u returns boolean
            return true
        endfunction
        
        //==============================================================================
        // System code
        //
        globals
            // The unit stored at an index.
            private unit array IndexUnit
            private integer array LockLevel
        endglobals
        
        //==============================================================================
        globals
            // Recycle stack
            private integer array RecycledIndex
            private integer MaxRecycledIndex = 0
            
            // Previous highest index
            private integer MaxIndex = 0
        endglobals
        
        //==============================================================================
        globals
            private integer array DecayingIndex
            private integer MaxDecayingIndex=0
            private integer DecayChecker=0
        endglobals
        
        globals
            private timer UndefendTimer=CreateTimer()
            private integer array UndefendIndex
            private integer UndefendStackIndex=0
        endglobals
        globals
            private integer array UndefendExpiringIndex
            private integer UndefendExpiringIndexLevel=0
        endglobals
        
        //==============================================================================
        globals
            // The Add/Remove stack (or assign/recycle stack).
            // 
            // Indexing can become recusive since units can be created on index
            // assignment or deallocation.
            // To support this, a stack is used to store the event response results.
            private integer ARStackLevel=0
            private integer array ARStackIndex
            private unit    array ARStackUnit
            
            // A later discovery revealed that the Add/Remove stack did not need to be
            // used for deallocation. The alternative used works fine...
        endglobals
        
        public constant function GetEnteringIndexUnit takes nothing returns unit
            return ARStackUnit[ARStackLevel]
        endfunction
        
        public function GetIndexOfEnteringUnit takes nothing returns integer
            // Called in AIDS structs when units do not pass the initial AIDS filter.
            
            if ARStackIndex[ARStackLevel]==0 then
                // Get new index, from recycler first, else new.
                // Store the current index on the (new) top level of the AR stack.
                if MaxRecycledIndex==0 then // Get new.
                    set MaxIndex=MaxIndex+1
                    set ARStackIndex[ARStackLevel]=MaxIndex
                else // Get from recycle stack.
                    set ARStackIndex[ARStackLevel]=RecycledIndex[MaxRecycledIndex]
                    set MaxRecycledIndex=MaxRecycledIndex-1
                endif
                
                // Store index on unit.
                call SetUnitUserData(ARStackUnit[ARStackLevel],ARStackIndex[ARStackLevel])
                set IndexUnit[ARStackIndex[ARStackLevel]]=ARStackUnit[ARStackLevel]
                
                // Add index to recycle list.
                set MaxDecayingIndex=MaxDecayingIndex+1
                set DecayingIndex[MaxDecayingIndex]=ARStackIndex[ARStackLevel]
            endif
            
            return ARStackIndex[ARStackLevel]
        endfunction
        
        public constant function GetIndexOfEnteringUnitAllocated takes nothing returns integer
            // Called in AIDS structs when units have passed the initial AIDS filter.
            return ARStackIndex[ARStackLevel]
        endfunction
        public constant function GetDecayingIndex takes nothing returns integer
            static if USE_PERIODIC_RECYCLER then
                return DecayingIndex[DecayChecker]
            else
                return UndefendExpiringIndex[UndefendExpiringIndexLevel]
            endif
        endfunction
        
        //==============================================================================
        globals
            // For structs and such which need to do things on unit index assignment.
            private trigger OnEnter=CreateTrigger()
            // The same, but for when units pass the initial filter anyway.
            private trigger OnEnterAllocated=CreateTrigger()
            // For structs and such which need to do things on unit index deallocation.
            private trigger OnDeallocate=CreateTrigger()
        endglobals
        
        public function RegisterOnEnter takes boolexpr b returns triggercondition
            return TriggerAddCondition(OnEnter,b)
        endfunction
        public function RegisterOnEnterAllocated takes boolexpr b returns triggercondition
            return TriggerAddCondition(OnEnterAllocated,b)
        endfunction
        public function RegisterOnDeallocate takes boolexpr b returns triggercondition
            return TriggerAddCondition(OnDeallocate,b)
        endfunction
        
        //==============================================================================
        function GetIndexUnit takes integer index returns unit
            debug if index==0 then
            debug   call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the unit of index 0.")
            debug elseif IndexUnit[index]==null then
            debug   call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the unit of unassigned index.")
            debug endif
            
            return IndexUnit[index]
        endfunction
        
        function GetUnitId takes unit u returns integer
            debug if u==null then
            debug   call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the id (inlines) of null unit.")
            debug elseif GetUnitUserData(u)==0 then
            debug   call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to use GetUnitId (inlines) when you should be using GetUnitIndex (unit didn't pass filter).")
            debug endif
            
            return GetUnitUserData(u)
        endfunction
        
        globals//locals
            private integer getindex
        endglobals
        function GetUnitIndex takes unit u returns integer // Cannot be recursive.
            debug if u==null then
            debug   call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the index of null unit.")
            debug endif
            
            set getindex=GetUnitUserData(u)
            
            if getindex==0 then
                // Get new index, from recycler first, else new.
                // Store the current index in getindex.
                if MaxRecycledIndex==0 then // Get new.
                    set MaxIndex=MaxIndex+1
                    set getindex=MaxIndex
                else // Get from recycle stack.
                    set getindex=RecycledIndex[MaxRecycledIndex]
                    set MaxRecycledIndex=MaxRecycledIndex-1
                endif
                
                // Store index on unit.
                call SetUnitUserData(u,getindex)
                set IndexUnit[getindex]=u
                
                static if USE_PERIODIC_RECYCLER then
                    
                    // Add index to recycle list.
                    set MaxDecayingIndex=MaxDecayingIndex+1
                    set DecayingIndex[MaxDecayingIndex]=getindex
                    
                else
                
                    // Add leave detection ability.
                    call UnitAddAbility(ARStackUnit[ARStackLevel],LEAVE_DETECTION_ABILITY)
                    call UnitMakeAbilityPermanent(ARStackUnit[ARStackLevel],true,LEAVE_DETECTION_ABILITY)
                    
                endif
                
                // Do not fire things here. No AIDS structs will be made at this point.
            endif
            
            return getindex
        endfunction
        
        //==============================================================================
        public function AddLock takes integer index returns nothing
            set LockLevel[index]=LockLevel[index]+1
        endfunction
        public function RemoveLock takes integer index returns nothing
            set LockLevel[index]=LockLevel[index]-1
            
            static if not USE_PERIODIC_RECYCLER then
                if GetUnitUserData(IndexUnit[index])==0 and LockLevel[index]==0 then
                    
                    // Increment stack for recursion.
                    set UndefendExpiringIndexLevel=UndefendExpiringIndexLevel+1
                    set UndefendExpiringIndex[UndefendExpiringIndexLevel]=index
                    
                    // Fire things.
                    call TriggerEvaluate(OnDeallocate)
                    
                    // Decrement stack for recursion.
                    set UndefendExpiringIndexLevel=UndefendExpiringIndexLevel-1
                    
                    // Add the index to the recycler stack.
                    set MaxRecycledIndex=MaxRecycledIndex+1
                    set RecycledIndex[MaxRecycledIndex]=index
                    
                    // Null the unit.
                    set IndexUnit[index]=null
                    
                endif
            endif
        endfunction
        
        //==============================================================================
        static if USE_PERIODIC_RECYCLER then
            
            private function PeriodicRecycler takes nothing returns nothing
                if MaxDecayingIndex>0 then
                    set DecayChecker=DecayChecker+1
                    if DecayChecker>MaxDecayingIndex then
                        set DecayChecker=1
                    endif
                    if GetUnitUserData(IndexUnit[DecayingIndex[DecayChecker]])==0 then
                    if LockLevel[DecayingIndex[DecayChecker]]==0 then
                        
                        // Fire things.
                        call TriggerEvaluate(OnDeallocate)
                        
                        // Add the index to the recycler stack.
                        set MaxRecycledIndex=MaxRecycledIndex+1
                        set RecycledIndex[MaxRecycledIndex]=DecayingIndex[DecayChecker]
                        
                        // Null the unit.
                        set IndexUnit[DecayingIndex[DecayChecker]]=null
                        
                        // Remove index from decay list.
                        set DecayingIndex[DecayChecker]=DecayingIndex[MaxDecayingIndex]
                        set MaxDecayingIndex=MaxDecayingIndex-1
                        
                    endif
                    endif
                endif
            endfunction
            
        else
            
            private function UndefendFilter takes nothing returns boolean
                return IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD)
            endfunction
            
            private function OnUndefendTimer takes nothing returns nothing
                loop
                    exitwhen UndefendStackIndex==0
                    
                    set UndefendStackIndex=UndefendStackIndex-1
                    set UndefendExpiringIndex[0]=UndefendIndex[UndefendStackIndex]
                    
                    if IndexUnit[UndefendExpiringIndex[0]]!=null then
                    if GetUnitUserData(IndexUnit[UndefendExpiringIndex[0]])==0 then
                    if LockLevel[UndefendExpiringIndex[0]]==0 then
                        
                        // Fire things.
                        call TriggerEvaluate(OnDeallocate)
                        
                        // Add the index to the recycler stack.
                        set MaxRecycledIndex=MaxRecycledIndex+1
                        set RecycledIndex[MaxRecycledIndex]=UndefendExpiringIndex[0]
                        
                        // Null the unit.
                        set IndexUnit[UndefendExpiringIndex[0]]=null
                        
                    endif
                    endif
                    endif
                    
                endloop
            endfunction
            
            globals//locals
                private integer UndefendFilterIndex
            endglobals
            private function OnUndefend takes nothing returns boolean
                if GetIssuedOrderId()==852056 then // If undefend then...
                    set UndefendFilterIndex=GetUnitUserData(GetOrderedUnit())
                    
                    if UndefendIndex[UndefendStackIndex-1]!=UndefendFilterIndex then // Efficiency perk.
                        set UndefendIndex[UndefendStackIndex]=UndefendFilterIndex
                        set UndefendStackIndex=UndefendStackIndex+1
                        
                        call TimerStart(UndefendTimer,0,false,function OnUndefendTimer)
                    endif
                endif
                
                return false
            endfunction
            
        endif
        
        //==============================================================================
        public function IndexEnum takes nothing returns boolean // Can be recursive...
            // Start by adding another level on the AR stack (for recursion's sake).
            set ARStackLevel=ARStackLevel+1
            
            // Store the current unit on the (new) top level of the AR stack.
            set ARStackUnit[ARStackLevel]=GetFilterUnit()
            
            if GetUnitUserData(ARStackUnit[ARStackLevel])==0 then // Has not been indexed.
                
                if UnitIndexingFilter(ARStackUnit[ARStackLevel]) then
                    
                    // Get new index, from recycler first, else new.
                    // Store the current index on the (new) top level of the AR stack.
                    if MaxRecycledIndex==0 then // Get new.
                        set MaxIndex=MaxIndex+1
                        set ARStackIndex[ARStackLevel]=MaxIndex
                    else // Get from recycle stack.
                        set ARStackIndex[ARStackLevel]=RecycledIndex[MaxRecycledIndex]
                        set MaxRecycledIndex=MaxRecycledIndex-1
                    endif
                    
                    // Store index on unit.
                    call SetUnitUserData(ARStackUnit[ARStackLevel],ARStackIndex[ARStackLevel])
                    set IndexUnit[ARStackIndex[ARStackLevel]]=ARStackUnit[ARStackLevel]
                    
                    static if USE_PERIODIC_RECYCLER then
                        
                        // Add index to recycle list.
                        set MaxDecayingIndex=MaxDecayingIndex+1
                        set DecayingIndex[MaxDecayingIndex]=ARStackIndex[ARStackLevel]
                        
                    else
                        
                        // Add leave detection ability.
                        call UnitAddAbility(ARStackUnit[ARStackLevel],LEAVE_DETECTION_ABILITY)
                        call UnitMakeAbilityPermanent(ARStackUnit[ARStackLevel],true,LEAVE_DETECTION_ABILITY)
                        
                    endif
                    
                    // Fire things.
                    call TriggerEvaluate(OnEnter)
                    
                else
                    
                    // The unit did not pass the filters, so does not need to be auto indexed.
                    // However, for certain AIDS structs, it may still require indexing.
                    // These structs may index the unit on their creation.
                    // We flag that an index must be assigned by setting the current index to 0.
                    set ARStackIndex[ARStackLevel]=0
                    
                    // Fire things.
                    call TriggerEvaluate(OnEnter)
                    
                endif
                
            endif
            
            // Decrement the stack.
            set ARStackLevel=ARStackLevel-1
            
            return false
        endfunction
        
        //==============================================================================
        private function InitAIDS takes nothing returns nothing
            local region r=CreateRegion()
            
            local group g=CreateGroup()
            local integer n=15
            
            static if USE_PERIODIC_RECYCLER then
                
                call TimerStart(UndefendTimer,PERIOD,true,function PeriodicRecycler)
                
            else
                
                local trigger t=CreateTrigger()
                
                loop
                    call TriggerRegisterPlayerUnitEvent(t,Player(n),EVENT_PLAYER_UNIT_ISSUED_ORDER,Filter(function UndefendFilter))
                    call SetPlayerAbilityAvailable(Player(n),LEAVE_DETECTION_ABILITY,false)
                    // Capture "undefend" orders.
                    exitwhen n==0
                    set n=n-1
                endloop
                set n=15
                
                call TriggerAddCondition(t,Filter(function OnUndefend))
                set t=null
                
            endif
            
            // This must be done first, due to recursion. :)
            call RegionAddRect(r,GetWorldBounds())
            call TriggerRegisterEnterRegion(CreateTrigger(),r,Filter(function IndexEnum))
            set r=null
            
            loop
                call GroupEnumUnitsOfPlayer(g,Player(n),Filter(function IndexEnum))
                //Enum every non-filtered unit on the map during initialization and assign it a unique
                //index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
                exitwhen n==0
                set n=n-1
            endloop
            call DestroyGroup(g)
            set g=null
        endfunction
        
        //==============================================================================
        public struct DEFAULT extends array
            method AIDS_onCreate takes nothing returns nothing
            endmethod
            method AIDS_onDestroy takes nothing returns nothing
            endmethod
            
            static method AIDS_filter takes unit u returns boolean
                return UnitIndexingFilter(u)
            endmethod
            
            static method AIDS_onInit takes nothing returns nothing
            endmethod
        endstruct
        
        //===========================================================================
        //  Never create or destroy AIDS structs directly.
        //  Also, do not initialise members except by using the AIDS_onCreate method.
        //===========================================================================
        //! textmacro AIDS
            // This magic line makes default methods get called which do nothing
            // if the methods are otherwise undefined.
            private static delegate AIDS_DEFAULT AIDS_DELEGATE=0
            
            //-----------------------------------------------------------------------
            // Gotta know whether or not to destroy on deallocation...
            private boolean AIDS_instanciated
            
            //-----------------------------------------------------------------------
            static method operator[] takes unit whichUnit returns thistype
                return GetUnitId(whichUnit)
            endmethod
            
            method operator unit takes nothing returns unit
                // Allows structVar.unit to return the unit.
                return GetIndexUnit(this)
            endmethod
            
            //-----------------------------------------------------------------------
            method AIDS_addLock takes nothing returns nothing
                call AIDS_AddLock(this)
            endmethod
            method AIDS_removeLock takes nothing returns nothing
                call AIDS_RemoveLock(this)
            endmethod
            
            //-----------------------------------------------------------------------
            private static method AIDS_onEnter takes nothing returns boolean
                // At this point, the unit might not have been assigned an index.
                if thistype.AIDS_filter(AIDS_GetEnteringIndexUnit()) then
                    // Flag it for destruction on deallocation.
                    set thistype(AIDS_GetIndexOfEnteringUnit()).AIDS_instanciated=true
                    // Can use inlining "Assigned" function now, as it must be assigned.
                    call thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_onCreate()
                endif
                
                return false
            endmethod
            
            private static method AIDS_onEnterAllocated takes nothing returns boolean
                // At this point, the unit must have been assigned an index.
                if thistype.AIDS_filter(AIDS_GetEnteringIndexUnit()) then
                    // Flag it for destruction on deallocation. Slightly faster!
                    set thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_instanciated=true
                    // Can use inlining "Assigned" function now, as it must be assigned.
                    call thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_onCreate()
                endif
                
                return false
            endmethod
            
            private static method AIDS_onDeallocate takes nothing returns boolean
                if thistype(AIDS_GetDecayingIndex()).AIDS_instanciated then
                    call thistype(AIDS_GetDecayingIndex()).AIDS_onDestroy()
                    // Unflag destruction on deallocation.
                    set thistype(AIDS_GetDecayingIndex()).AIDS_instanciated=false
                endif
                
                return false
            endmethod
            
            //-----------------------------------------------------------------------
            private static method onInit takes nothing returns nothing
                call AIDS_RegisterOnEnter(Filter(function thistype.AIDS_onEnter))
                call AIDS_RegisterOnEnterAllocated(Filter(function thistype.AIDS_onEnterAllocated))
                call AIDS_RegisterOnDeallocate(Filter(function thistype.AIDS_onDeallocate))
                
                // Because I robbed you of your struct's onInit method.
                call thistype.AIDS_onInit()
            endmethod
        //! endtextmacro
    endlibrary
    
    library PUI uses AIDS
        //===========================================================================
        //  Allowed PUI_PROPERTY TYPES are: unit, integer, real, boolean, string
        //  Do NOT put handles that need to be destroyed here (timer, trigger, ...)
        //  Instead put them in a struct and use PUI textmacro
        //===========================================================================
        //! textmacro PUI_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
        $VISIBILITY$ struct $NAME$
            private static unit   array pui_unit
            private static $TYPE$ array pui_data
            
            //-----------------------------------------------------------------------
            //  Returns default value when first time used
            //-----------------------------------------------------------------------
            static method operator[] takes unit whichUnit returns $TYPE$
                local integer pui = GetUnitId(whichUnit) // Changed from GetUnitIndex.
                if .pui_unit[pui] != whichUnit then
                    set .pui_unit[pui] = whichUnit
                    set .pui_data[pui] = $DEFAULT$
                endif
                return .pui_data[pui]
            endmethod
            
            //-----------------------------------------------------------------------
            static method operator[]= takes unit whichUnit, $TYPE$ whichData returns nothing
                local integer pui = GetUnitIndex(whichUnit)
                set .pui_unit[pui] = whichUnit
                set .pui_data[pui] = whichData
            endmethod
        endstruct
        //! endtextmacro
    
        //===========================================================================
        //  Never destroy PUI structs directly.
        //  Use .release() instead, will call .destroy()
        //===========================================================================
        //! textmacro PUI
            private static unit    array pui_unit
            private static integer array pui_data
            private static integer array pui_id
            
            //-----------------------------------------------------------------------
            //  Returns zero if no struct is attached to unit
            //-----------------------------------------------------------------------
            static method operator[] takes unit whichUnit returns integer
                local integer pui = GetUnitId(whichUnit) // Changed from GetUnitIndex.
                // Switched the next two lines for optimisation.
                if .pui_unit[pui] != whichUnit then
                    if .pui_data[pui] != 0 then
                        // recycled index detected
                        call .destroy(.pui_data[pui])
                        set .pui_unit[pui] = null
                        set .pui_data[pui] = 0            
                    endif
                endif
                return .pui_data[pui]
            endmethod
            
            //-----------------------------------------------------------------------
            //  This will overwrite already attached struct if any
            //-----------------------------------------------------------------------
            static method operator[]= takes unit whichUnit, integer whichData returns nothing
                local integer pui = GetUnitIndex(whichUnit)
                if .pui_data[pui] != 0 then
                    call .destroy(.pui_data[pui])
                endif
                set .pui_unit[pui] = whichUnit
                set .pui_data[pui] = whichData
                set .pui_id[whichData] = pui
            endmethod
    
            //-----------------------------------------------------------------------
            //  If you do not call release struct will be destroyed when unit handle gets recycled
            //-----------------------------------------------------------------------
            method release takes nothing returns nothing
                local integer pui= .pui_id[integer(this)]
                call .destroy()
                set .pui_unit[pui] = null
                set .pui_data[pui] = 0
            endmethod
        //! endtextmacro
    endlibrary
    
    library AutoIndex uses AIDS
        module AutoData
            private static thistype array data
            
            // Fixed up the below to use thsitype instead of integer.
            static method operator []= takes unit u, thistype i returns nothing
                set .data[GetUnitId(u)] = i //Just attaching a struct to the unit
            endmethod                       //using the module's thistype array.
            
            static method operator [] takes unit u returns thistype
                return .data[GetUnitId(u)] //Just returning the attached struct.
            endmethod
        endmodule
    endlibrary

    The documentation should explain everything (thanks to Romek for his help writing it). Feel free to ask any questions. This is basically another indexing system, but with the neat feature of being able to make unit structs (AIDS structs).

    These structs are created for all units that pass the struct's filter. So, for example, in an RPG where NPC's have a disposition to the player, you could use this to make a struct to store that. Even when the unit dies, if the unit is resurrected it will retain its data. These structs are automatically destroyed when the unit's index is freed up (not when it is recycled like PUI). Also, getting the struct for a unit is a single native call, and you can typecast between AIDS structs.

    Example (the one in the example map below):
    This gives every unit (except footmen) in your map two footman guardians, who additionally take all damage taken by the unit they're for...
    JASS:
    struct FootmanGuardians extends array
        //! runtextmacro AIDS()
        // Footmen
        private unit a
        private unit b
        private trigger t
        
        private static method OnDamage takes nothing returns boolean
            // Forwards damage as pure damage.
            local thistype d=thistype[GetTriggerUnit()]
            call UnitDamageTargetBJ( GetEventDamageSource(), d.a, GetEventDamage(), ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL )
            call UnitDamageTargetBJ( GetEventDamageSource(), d.b, GetEventDamage(), ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL )
            return false
        endmethod
        
        private static method AIDS_filter takes unit u returns boolean
            return GetUnitTypeId(u)!='hfoo' // Don't make this struct for footmen.
        endmethod
        private method AIDS_onCreate takes nothing returns nothing
            set this.a=CreateUnit(Player(0),'hfoo',GetUnitX(this.unit),GetUnitY(this.unit),0)
            set this.b=CreateUnit(Player(0),'hfoo',GetUnitX(this.unit),GetUnitY(this.unit),0)
            
            set this.t=CreateTrigger()
            call TriggerAddCondition(this.t,Condition(function thistype.OnDamage))
            call TriggerRegisterUnitEvent(this.t,this.unit, EVENT_UNIT_DAMAGED )
        endmethod
        private method AIDS_onDestroy takes nothing returns nothing
            call DestroyTrigger(this.t)
            set this.t=null
            call RemoveUnit(this.a)
            call RemoveUnit(this.b)
            set this.a=null
            set this.b=null
        endmethod
    endstruct

    Aside from that, you have classic unit userdata indexing - the mapper's choice of index spamming (default configuration) or only indexing when used, or whatever compromise of the two that advanced mappers may prefer. See the documentation for more details.

    Happy mapping. :thup:

    Updates:
    - Version 1.1.0: Added support for undefend unit leaves map detection. A constant boolean can be used to determine whether or not the system runs on undefend removal detection or a periodic timer. Made the AIDS filter function non-constant.
    - Version 1.0.4: Fixed potential bug that could occur when a unit re-entered the playable map area.
    - Version 1.0.3: Added debug messages (to GetUnitId, GetUnitIndex and GetIndexUnit).
    - Version 1.0.2: Changed GetUnitIndex in AIDS structs' static array operator to GetUnitId. This was a mistake, just did a fragment more processing than necessary.
    - Version 1.0.1: Added struct locking.
    - Version 1.0.0: Release.

    (Yay for this being my 777th post?)

    GUI AIDS​

    Version 1.1.1​
    Not recommended. Only here for Mac users who need an indexing system.
    Trigger:
    • AIDS Assign
      • Events
      • Conditions
      • Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (Custom value of (Matching unit)) Equal to 0
          • Then - Actions
            • -------- New slot for review list for timer. --------
            • Set AIDS_ReviewMax = (AIDS_ReviewMax + 1)
            • -------- Pull off recycle stack, else assign new. Put it in the last slot for review from timer. --------
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • AIDS_RecycleMax Equal to 0
              • Then - Actions
                • Set AIDS_MaxIndex = (AIDS_MaxIndex + 1)
                • Set AIDS_Review[AIDS_ReviewMax] = AIDS_MaxIndex
              • Else - Actions
                • Set AIDS_Review[AIDS_ReviewMax] = AIDS_Recycle[AIDS_RecycleMax]
                • Set AIDS_RecycleMax = (AIDS_RecycleMax - 1)
            • -------- Associate index and unit. --------
            • Set AIDS_IndexUnit[AIDS_Review[AIDS_ReviewMax]] = (Matching unit)
            • Unit - Set the custom value of (Matching unit) to AIDS_Review[AIDS_ReviewMax]
          • Else - Actions

    Trigger Title: AIDS Assign Event
    JASS:
    function AIDS_FireAssign takes nothing returns boolean
        call TriggerExecute(gg_trg_AIDS_Assign)
        return false
    endfunction
    
    //===========================================================================
    function InitTrig_AIDS_Assign_Event takes nothing returns nothing
        local region r=CreateRegion()
        call RegionAddRect(r,GetWorldBounds())
        call TriggerRegisterEnterRegion(CreateTrigger(),r,Filter(function AIDS_FireAssign))
    endfunction

    Trigger:
    • AIDS Review
      • Events
        • Time - Every (3125.00 / 100000.00) seconds of game time
      • Conditions
      • Actions
        • -------- There are no units on the map? --------
        • If (AIDS_ReviewMax Equal to 0) then do (Skip remaining actions) else do (Do nothing)
        • -------- Get index to review. --------
        • Set AIDS_ReviewCurrent = (AIDS_ReviewCurrent - 1)
        • If (AIDS_ReviewCurrent Less than or equal to 0) then do (Set AIDS_ReviewCurrent = AIDS_ReviewMax) else do (Do nothing)
        • -------- Check if unit has been removed. --------
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • (Custom value of AIDS_IndexUnit[AIDS_Review[AIDS_ReviewCurrent]]) Equal to 0
          • Then - Actions
            • -------- If it has been removed, add its index to the recycle stack. --------
            • Set AIDS_RecycleMax = (AIDS_RecycleMax + 1)
            • Set AIDS_Recycle[AIDS_RecycleMax] = AIDS_Review[AIDS_ReviewCurrent]
            • -------- Remove from review list. First move top entry to here, then decrement. --------
            • Set AIDS_Review[AIDS_ReviewCurrent] = AIDS_Review[AIDS_ReviewMax]
            • Set AIDS_ReviewMax = (AIDS_ReviewMax - 1)
          • Else - Actions

    Trigger:
    • AIDS Init
      • Events
        • Map initialization
      • Conditions
      • Actions
        • For each (Integer A) from 1 to 16, do (Actions)
          • Loop - Actions
            • -------- Looping through each player this way means locusted units are indexed. --------
            • Custom script: set bj_wantDestroyGroup=true
            • Unit Group - Pick every unit in (Units owned by (Player((Integer A)))) and do (Actions)
              • Loop - Actions
                • -------- New slot for review list for timer. --------
                • Set AIDS_ReviewMax = (AIDS_ReviewMax + 1)
                • -------- Assign a new index new. Put it in the last slot for review from timer. --------
                • Set AIDS_MaxIndex = (AIDS_MaxIndex + 1)
                • Set AIDS_Review[AIDS_ReviewMax] = AIDS_MaxIndex
                • -------- Associate index and unit. --------
                • Set AIDS_IndexUnit[AIDS_MaxIndex] = (Picked unit)
                • Unit - Set the custom value of (Picked unit) to AIDS_MaxIndex

    This has some limitations - Units may not be indexed by the time they enter a region (but should be), and obviously you can't use AIDS structs and other features.

    Download:
    View attachment GUI AIDS.w3x
     

    Attached Files:

    • AIDS.w3m
      File size:
      40.4 KB
      Views:
      680
    • Like Like x 10
  2. Renendaru

    Renendaru (Evol)ution is nothing without love.

    Ratings:
    +309 / 0 / -0
    Thought I'd see one of these from you, but why not post the example here?
     
    • Like Like x 1
  3. Switch33

    Switch33 New Member

    Ratings:
    +12 / 0 / -0
    I lol'd AIDS is a disease!

    On a more serious note, is this any better then autoindex 2.8 or wtever it's at now that grim made? If so in what way?
     
  4. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    I didn't. But then I realised no systems support that AIDS style struct.
    And there is an example there... I'll put the code in the post though, if that's what you mean.

    In the first version of the documentation, that's pretty much all Romek wrote... o.o

    Firstly, this supports the AIDS struct, which no other system does. If you use your imagination a little, you'll find it quite useful for map-specific system creation. It's actually... Really nice for that.

    Now, aside from that, I can't speak to better or worse. But I can give you a comparison of some sort. AutoIndex has a much bigger trigger for leave detection which takes more processing, and requires mappers to use RemoveUnitEx (which it doesn't state on the AutoIndex page as far as I saw, only within the system documentation). This has a limit to how many units it can recycle each second.

    Of course, AutoIndex also supports revive and transport events, which are kind of neat. If people like, I may be able to incorporate these (crediting, of course) because I assume they're not compatible with this system otherwise.
     
  5. Renendaru

    Renendaru (Evol)ution is nothing without love.

    Ratings:
    +309 / 0 / -0
    Off: I haven't used any indexing systems as of yet, would you reccomend any for creep revival? (ie, saving x, y, and unit type)
     
  6. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    You mean...
    JASS:
    struct CreepRevival extends array
        //! runtextmacro AIDS()
        private trigger t
        private real x
        private real y
        private integer utype
        
        private static method OnDeath takes nothing returns nothing
            local thistype d=thistype[GetTriggerUnit()]
            
            // Because the struct may be destroyed before the wait finishes.
            local trigger t=d.t
            local real x=d.x
            local real y=d.y
            local integer utype=d.utype
            set d.t=null
            
            call TriggerSleepAction(3.0)
            call CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE),utype,x,y,0)
            call DestroyTrigger(t)
            set t=null
        endmethod
        
        private static method AIDS_filter takes unit u returns boolean
            return GetOwningPlayer(u)==Player(PLAYER_NEUTRAL_AGGRESSIVE)
        endmethod
        private method AIDS_onCreate takes nothing returns nothing
            set this.utype=GetUnitTypeId(this.unit)
            set this.x=GetUnitX(this.unit)
            set this.y=GetUnitY(this.unit)
            
            set this.t=CreateTrigger()
            call TriggerAddAction(this.t,function thistype.OnDeath)
            call TriggerRegisterUnitEvent(this.t,this.unit, EVENT_UNIT_DEATH )
        endmethod
    endstruct
    ? :D

    I kinda had to. For each unit owned by neutral hostile, that will store its x/y/type, and on death wait 3.0 seconds and then spawn a new unit of that type at that original x and y, regardless where it died. Change 3.0 to your liking. This is why I made AIDS... It makes map-wide unit data systems sooo easy to code. <_<

    Edit: I may add something called Locks (which won't break anything currently made) which will stop an index from being freed (and hence all AIDS structs on it) until the same number of Unlocks have been called. :)

    ... So, you'd be able to attach the struct to a timer, and not worry about it getting destroyed, because it won't be until the timer unlocks it after its callback... (Which is what I should've done here, not that it matters.)

    Edit again:
    In answer to your question, I'd recommend you give your map AIDS.
     
  7. kingkingyyk3

    kingkingyyk3 Visitor (Welcome to the Jungle, Baby!)

    Ratings:
    +216 / 0 / -0
    Can you make it no need to use UserData()?
     
  8. wraithseeker

    wraithseeker Tired.

    Ratings:
    +122 / 0 / -0
    It's an indexing system.. or he could make it use table but it would be slower.
     
  9. kingkingyyk3

    kingkingyyk3 Visitor (Welcome to the Jungle, Baby!)

    Ratings:
    +216 / 0 / -0
    Same with AutoIndex??!! :eek:
     
  10. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    I could probably do a version using Table, but it just seems a bit miserable to me... What did you have in mind?

    Well, released v1.0.1, added struct locking (really quite useful :D).
    So for Renendaru, here's a AIDS/KT version of a simple creep respawn system. You could even add an AIDS_onDestroy so if creeps were REMOVED from the game, they'd STILL respawn. XD
    JASS:
    struct CreepRevival extends array
        //! runtextmacro AIDS()
        private trigger t
        private real x
        private real y
        private integer utype
        
        private static method Callback takes nothing returns boolean
            local thistype this=KT_GetData()
            call CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE),this.utype,this.x,this.y,0)
            call this.AIDS_removeLock()
            return true
        endmethod
        
        private static method OnDeath takes nothing returns boolean
            local thistype this=thistype[GetTriggerUnit()]
            call this.AIDS_addLock() // So the struct can't disappear before the timer ticks.
            call KT_Add(function thistype.Callback,this,3.0)
            call DestroyTrigger(this.t)
            set this.t=null
            return false
        endmethod
        
        private static method AIDS_filter takes unit u returns boolean
            return GetOwningPlayer(u)==Player(PLAYER_NEUTRAL_AGGRESSIVE)
        endmethod
        private method AIDS_onCreate takes nothing returns nothing
            set this.utype=GetUnitTypeId(this.unit)
            set this.x=GetUnitX(this.unit)
            set this.y=GetUnitY(this.unit)
            
            set this.t=CreateTrigger()
            call TriggerAddCondition(this.t,Condition(function thistype.OnDeath))
            call TriggerRegisterUnitEvent(this.t,this.unit, EVENT_UNIT_DEATH )
        endmethod
    endstruct
    PS. The previous version leaked trigger actions, I forgot. lol.
     
  11. kingkingyyk3

    kingkingyyk3 Visitor (Welcome to the Jungle, Baby!)

    Ratings:
    +216 / 0 / -0
    I could probably do a version using Table, but it just seems a bit miserable to me... What did you have in mind?

    :p Just want to try some new stuffs..
     
  12. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Like?

    ... I'm not recoding and then maintaining two versions of this system because "I want to try something". o.o
     
  13. BlackRose

    BlackRose Forum User

    Ratings:
    +239 / 0 / -0
    LOL. I thought that first as well. AIDS = Disease
    Acquired-Immune-Deficiency-Syndrome.

    Eh, I guess I just made a spam post ultimately.
     
  14. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    Post number 1,111 for you, no less. :D

    (I kind of expected some lol-posts, obviously. As long as at least half of posts are about the system though, I'm happy. Can't speak for mods, of course.)
     
  15. Gwypaas

    Gwypaas hook DoNothing MakeGUIUsersCrash -Vexorian

    Ratings:
    +50 / 0 / -0
    Yoo nawb make it use models, text macros are fugly.
     
  16. wraithseeker

    wraithseeker Tired.

    Ratings:
    +122 / 0 / -0
    I don't really get why you implemented the PUI textmacro and AutoData which to me you only need 1 forever for your map, I don't see much difference in my spells.
     
  17. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    If you don't know the difference, just use AutoData and destroy your structs, or PUI and don't. As said, it's mostly compatability, because this otherwise breaks everything that uses one of those systems.

    I would if they compiled right on the latest version of NewGen's JassHelper. I don't feel inclined to tell everyone to upgrade each time they say it doesn't compile. But, I probably will do this in the future, and make the textmacro implement the module. That's easy to change at any time without breaking anything, heh. :thup:
     
  18. Tyrande_ma3x

    Tyrande_ma3x .

    Ratings:
    +91 / 0 / -0
    Awesomeee, you just copied PUI's textmacros without even editing them. If that's not a great ripoff then I don't know what it is. ^^
    Seriously, why combine two systems into one when you'd need only one of them at a time (even forever)? They work perfectly fine.
     
  19. Viikuna

    Viikuna No Marlo no game.

    Ratings:
    +265 / 0 / -0
    Just make some system that recycles units instead of index´s and it would be awesome.
     
  20. Jesus4Lyf

    Jesus4Lyf Good Idea™

    Ratings:
    +394 / 0 / -0
    @Tyrande_ma3x
    Read the doc. You realise the main point of this system is the AIDS structs...? <_<

    Excerpts:
    >without even editing them
    Actually, I did edit PUI's macro. I even commented the edits. Just some optimisation (so it actually should run faster in AIDS than PUI lol).

    >why combine two systems into one
    Compatability. AIDS is actually different.

    >Just make some system that recycles units instead of index´s and it would be awesome.
    Not always viable. What if you have 200 of a unit type at once, and then never again? lol...

    Anyway, why would you? This doesn't need to be mad efficient... You'd need to code your own exp systems and stuff, gold assignment for kills... What's laggier? Yuck.
     

Share This Page