• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[System] Unit Indexer

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
If you want maximum efficiency for your indexer's sturcts, then you'd need two separate modules (that will have to be implemented together ...). That is because regardless where you implement the module, some of the stuff will have to be called via trigger evaluations.

And since you are the type of guy who detests trigger evaluations or any other slowdowns in the code, why not make one module for the "unit" and "allocated" methods and the [] method operator (that should be implemented at the top), and a separate one for calling the "index" and "deindex" methods (that should be implemented at the bottom)?
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Well, it'll generate the trigs yea, but it'll still inline.

Save the map not in debug mode, then open the JASS file inside of the archive.

Weird. When it's done with a module (AutoIndex and UnitIndexer), it indeed gets inlined, but when it's done with a textmacro (AIDS), it doesn't.

But yeah, I guess you're right.

EDIT: Okay, it doesn't matter whether it's a module or a textmacro. It's just how the libraries are coded and how JassHelper's inlining is done. My bad.
 
Last edited:
Level 17
Joined
Apr 27, 2008
Messages
2,455
Nothing revelant to the code, but you don't need to open the map with a mopaq editor if you use the JNGP, the scripts files extracted and injected in the map are in the subfolder "logs" of JNGP.

(Vexorian rocks even if he is no more active)
 
  • Like
Reactions: BBQ

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
AIDS is better. AutoIndex is better. Try removing a paused unit in a transport with this system.

About this, tooltiperror does have a point Nes. Either you
can do what I did with transported units (simply unpause
them if they are paused while loaded) or you can hook
RemoveUnit.

I've been thinking about what if this had more of a swing
towards the method I used in GUI Unit Indexer (recycling
every 15 new units). Then a second resource called
"Undefend Event" which can catch the moment the unit
leaves scope.

This accomplishes three important things:
  1. Make UnitIndexer easier to install
  2. Place the blame on Undefend Event for stuff like this.
    This resource could optionally hook RemoveUnit in order
    to guarantee in-scope deindex detection.
  3. Undefend would be a good resource which I could also
    use in my CargoEvent system and would make things like
    UnitEvent possible without needing to be coupled with Unit
    Indexer.

Also, in the "eval" method for a registered trigger, you can
shorten this:

JASS:
                exitwhen 0==f
                if (IsTriggerEnabled(t)) then
                    set o=f
                    if (TriggerEvaluate(t)) then
                        call TriggerExecute(t)
                    endif
                else
                    exitwhen true
                endif

To this:

JASS:
                exitwhen 0==f or not IsTriggerEnabled(t)
                set o=f
                if TriggerEvaluate(t) then
                    call TriggerExecute(t)
                endif
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
All I can say is to unpause your paused units before you remove them.


The pause native shouldn't normally be used anyways and I've never made a map that actually used it. For stuns, you use a stun ability.


Pausing units is for special scenarios, and in those scenarios, you should know whether it's paused or not =).


It's too much overhead to try and bandaid paused units, so paused units need to be managed by the people who use the PauseUnit native =P.


I think that you should take this approach CargoEvent too =).
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Your indexer also fails when sleeping neutrals are killed by a non-damaging source, namely SetUnitState(), SetWidgetLife(), RemoveUnit() and KillUnit().


EDIT: I posted this at Wc3C, but I think that the following misconception should be addressed here as well.

Anyhow. I adapted my testmap for proving AutoIndex's flaw. It seems like UnitIndexer handles the situation properly (since you don't like links to wc3c, I'll upload the map here).
That is not AutoIndex's flaw. It is caused by GetHandleId() and the hashtable. In case you didn't notice, you had configured AutoIndex to use a hashtable instead of user data. The issue will be present in any indexer that uses a hashtable instead of user data.

Change AutoIndex's configuration by setting the UseUnitUserData boolean to true and watch as the "flaw" magically goes away.

And AutoIndex is by far the safest indexer out there.
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
It's also by far the slowest and most cumbersome >: P

Yeah, I guess.

Also, after re-reading Unit Indexer and Unit Event, I concluded that my above point (the one about sleeping neutrals) is only true as far as reincarnation events are concerned (the indexer per se does not fail), which doesn't really matter.
 
All I can say is to unpause your paused units before you remove them.


The pause native shouldn't normally be used anyways and I've never made a map that actually used it. For stuns, you use a stun ability.


Pausing units is for special scenarios, and in those scenarios, you should know whether it's paused or not =).


It's too much overhead to try and bandaid paused units, so paused units need to be managed by the people who use the PauseUnit native =P.


I think that you should take this approach CargoEvent too =).

Same here :p
I only use "PauseUnit" when I want to end the game :p (Pause All Units, kill them, spawn sfx, show dialogs, end.)
 
Level 10
Joined
May 27, 2009
Messages
494
well i'm using this code in replacement of the AIDS struct declaration of the Damage Library
anything wrong?

JASS:
private struct Detector extends array // Uses AIDS.
        private trigger t

        private method index takes nothing returns nothing
            set this.t = CreateTrigger()
            call TriggerAddCondition(this.t,Condition(function OnDamageActions))
            call TriggerRegisterUnitEvent(this.t,unit,EVENT_UNIT_DAMAGED)
        endmethod
        
        private method deindex takes nothing returns nothing
            call DestroyTrigger(this.t)
        endmethod
        
        implement UnitIndexStruct
endstruct
 
Level 10
Joined
May 27, 2009
Messages
494
meh.. but i'm using damage's physical/spell/custom damage detection of damage and its block function, if only i can use that library's extension without the buff/ability problem (since that library itself doesn't have the physical detection/attack whatsoever) o.o even though it is easy to add a block function .........
 
Level 10
Joined
May 27, 2009
Messages
494
Don't you really need to just differentiate between physical and
triggered/spell damage? Well I agree Nestharus should make a
stable AdvancedDamageEvent to compliment his unstable but
more featured current version.

well in fact i have 4 damage to differentiate with, 1 physical, 1 spell, 1 pure and a custom made one (for detection of a spell in my map) so for now, Damage library will be useful on my part
:goblin_boom:
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Well, i know that you are basically ignoring me but plz could you add an "unsafe" version for your library, all this lua stuff just for a rawcode which can be easily handle manually is crazy, more : annoying.
Actually that's the only one real thing why i refuse to use it (there are others like the horrible short names and the requirement of WorldBounds when you could easily inline it, but i can live with them).

If you care that much you can add a red big warning, or whatever else before posting this version.

Ofc i could do it myself i just feel it's so much overhead, and not every one can bypass these lua libraries.
 
Here's your non-Lua script:

JASS:
library UnitIndexer /* v4.0.2.3
*************************************************************************************
*
*   Assigns unique indexes to units via unit user data.
*
*************************************************************************************
*
*   */uses/*
*       */ WorldBounds /*       hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
*       */ Event /*             hiveworkshop.com/forums/submissions-414/snippet-event-186555/
*
************************************************************************************
*
*    Functions
*
*       function RegisterUnitIndexEvent takes boolexpr codeToRegister, Event unitIndexEvent returns nothing
*       function TriggerRegisterUnitIndexEvent takes trigger triggerToRegister, Event unitIndexEvent returns nothing
*
*       function GetUnitById takes integer index returns unit
*           -   Returns unit given a unit index
*       function GetUnitId takes unit u returns integer
*           -   Returns unit index given a unit
*
*       function IsUnitIndexed takes unit u returns boolean
*
*       function GetIndexedUnitId takes nothing returns integer
*       function GetIndexedUnit takes nothing returns unit
*
************************************************************************************
*
*   module UnitIndexStruct
*
*       -   A pseudo module interface that runs a set of methods if they exist and provides
*       -   a few fields and operators. Runs on static ifs to minimize code.
*
*       static method operator [] takes unit u returns thistype
*           -   Return GetUnitUserData(u)
*
*       readonly unit unit
*           -   The indexed unit of the struct
*       readonly boolean allocated
*           -   Is unit allocated for the struct
*
*       Interface:
*
*           -   These methods don't have to exist. If they don't exist, the code
*           -   that calls them won't even be in the module.
*
*           private method index takes nothing returns nothing
*               -   called when a unit is indexed and passes the filter.
*               -
*               -   thistype this:              Unit's index
*           private method deindex takes nothing returns nothing
*               -   called when a unit is deindexed and is allocated for struct
*               -
*               -   thistype this:              Unit's index
*           private static method filter takes unit unitToIndex returns boolean
*               -   Determines whether or not to allocate struct for unit
*               -
*               -   unit unitToIndex:           Unit being filtered
*
************************************************************************************
*
*   struct UnitIndexer extends array
*
*        -    Controls the unit indexer system.
*
*       static constant Event UnitIndexer.INDEX
*       static constant Event UnitIndexer.DEINDEX
*           -   Don't register functions and triggers directly to the events. Register them via
*           -   RegisterUnitIndexEvent and TriggerRegisterUnitIndexEvent.
*
*       static boolean enabled
*           -   Enables and disables unit indexing. Useful for filtering out dummy units.
*
************************************************************************************
*
*   struct UnitIndex extends array
*
*       -    Constrols specific unit indexes.
*
*       method lock takes nothing returns nothing
*           -   Locks an index. When an index is locked, it will not be recycled
*           -   when the unit is deindexed until all locks are removed. Deindex
*           -   events still fire at the appropriate times, the index just doesn't
*           -   get thrown into the recycler.
*       method unlock takes nothing returns nothing
*           -   Unlocks an index.
*
************************************************************************************/
    globals
        private constant integer ABILITIES_UNIT_INDEXER = 'A@@!' // Configure this
        private trigger q=CreateTrigger()
        private trigger l=CreateTrigger()
        private unit array e
        private integer r=0
        private integer y=0
        private integer o=0
        private boolean a=false
        private integer array n
        private integer array p
        private integer array lc
    endglobals
    function GetIndexedUnitId takes nothing returns integer
        return o
    endfunction
    function GetIndexedUnit takes nothing returns unit
        return e[o]
    endfunction
    //! runtextmacro optional UNIT_LIST_LIB()
    private struct PreLoader extends array
        public static method run takes nothing returns nothing
            call DestroyTimer(GetExpiredTimer())
            set a=true
        endmethod
        public static method eval takes trigger t returns nothing
            local integer f=n[0]
            local integer d=o
            loop
                exitwhen 0==f
                if (IsTriggerEnabled(t)) then
                    set o=f
                    if (TriggerEvaluate(t)) then
                        call TriggerExecute(t)
                    endif
                else
                    exitwhen true
                endif
                set f=n[f]
            endloop
            set o=d
        endmethod
        public static method evalb takes boolexpr c returns nothing
            local trigger t=CreateTrigger()
            local thistype f=n[0]
            local integer d=o
            call TriggerAddCondition(t,c)
            loop
                exitwhen 0==f
                set o=f
                call TriggerEvaluate(t)
                set f=n[f]
            endloop
            call DestroyTrigger(t)
            set t=null
            set o=d
        endmethod
    endstruct
    //! runtextmacro optional UNIT_EVENT_MACRO()
    private module UnitIndexerInit
        private static method onInit takes nothing returns nothing
            local integer i=15
            local boolexpr bc=Condition(function thistype.onLeave)
            local boolexpr bc2=Condition(function thistype.onEnter)
            local group g=CreateGroup()
            local player p
            set INDEX=CreateEvent()
            set DEINDEX=CreateEvent()
            call TriggerRegisterEnterRegion(q,WorldBounds.worldRegion,bc2)
            loop
                set p=Player(i)
                call TriggerRegisterPlayerUnitEvent(l,p,EVENT_PLAYER_UNIT_ISSUED_ORDER,bc)
                call SetPlayerAbilityAvailable(p,ABILITIES_UNIT_INDEXER,false)
                call GroupEnumUnitsOfPlayer(g,p,bc2)
                exitwhen 0==i
                set i=i-1
            endloop
            call DestroyGroup(g)
            set bc=null
            set g=null
            set bc2=null
            set p=null
            call TimerStart(CreateTimer(),0,false,function PreLoader.run)
        endmethod
    endmodule
    struct UnitIndex extends array
        method lock takes nothing returns nothing
            debug if (null!=e[this]) then
                set lc[this]=lc[this]+1
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"UNIT INDEXER ERROR: ATTEMPT TO LOCK NULL INDEX")
            debug endif
        endmethod
        method unlock takes nothing returns nothing
            debug if (0<lc[this]) then
                set lc[this]=lc[this]-1
                if (0==lc[this] and null==e[this]) then
                    set n[this]=y
                    set y=this
                endif
            debug else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"UNIT INDEXER ERROR: ATTEMPT TO UNLOCK UNLOCKED INDEX")
            debug endif
        endmethod
    endstruct
    struct UnitIndexer extends array
        readonly static Event INDEX
        readonly static Event DEINDEX
        static boolean enabled=true
        private static method onEnter takes nothing returns boolean
            local unit Q=GetFilterUnit()
            local integer i
            local integer d=o
            if (enabled and Q!=e[GetUnitUserData(Q)]) then
                if (0==y) then
                    set r=r+1
                    set i=r
                else
                    set i=y
                    set y=n[y]
                endif
                call UnitAddAbility(Q,ABILITIES_UNIT_INDEXER)
                call UnitMakeAbilityPermanent(Q,true,ABILITIES_UNIT_INDEXER)
                call SetUnitUserData(Q,i)
                set e[i]=Q
                static if not LIBRARY_UnitList then
                    if (not a)then
                        set p[i]=p[0]
                        set n[p[0]]=i
                        set n[i]=0
                        set p[0]=i
                    endif
                else
                    set p[i]=p[0]
                    set n[p[0]]=i
                    set n[i]=0
                    set p[0]=i
                    call GroupAddUnit(g,e[i])
                endif
                set o=i
                call FireEvent(INDEX)
                set o=d
            endif
            set Q=null
            return false
        endmethod
        private static method onLeave takes nothing returns boolean
            static if LIBRARY_UnitEvent then
                implement optional UnitEventModule
            else
                local unit u=GetFilterUnit()
                local integer i=GetUnitUserData(u)
                local integer d=o
                if (0==GetUnitAbilityLevel(u,ABILITIES_UNIT_INDEXER) and u==e[i]) then
                    static if not LIBRARY_UnitList then
                        if (not a)then
                            set n[p[i]]=n[i]
                            set p[n[i]]=p[i]
                        endif
                    else
                        set n[p[i]]=n[i]
                        set p[n[i]]=p[i]
                        call GroupRemoveUnit(g,e[i])
                    endif
                    set o=i
                    call FireEvent(DEINDEX)
                    set o=d
                    if (0==lc[i]) then
                        set n[i]=y
                        set y=i
                    endif
                    set e[i]=null
                endif
                set u=null
            endif
            return false
        endmethod
        implement UnitIndexerInit
    endstruct
    //! runtextmacro optional UNIT_EVENT_MACRO_2()
    function RegisterUnitIndexEvent takes boolexpr c,integer ev returns nothing
        call RegisterEvent(c, ev)
        if (not a and ev==UnitIndexer.INDEX and 0!=n[0]) then
            call PreLoader.evalb(c)
        endif
    endfunction
    function TriggerRegisterUnitIndexEvent takes trigger t,integer ev returns nothing
        call TriggerRegisterEvent(t,ev)
        if (not a and ev == UnitIndexer.INDEX and 0!=n[0]) then
            call PreLoader.eval(t)
        endif
    endfunction
    function GetUnitById takes integer W returns unit
        return e[W]
    endfunction
    function GetUnitId takes unit u returns integer
        return GetUnitUserData(u)
    endfunction
    function IsUnitIndexed takes unit u returns boolean
        return u==e[GetUnitUserData(u)]
    endfunction
    module UnitIndexStruct
        static method operator [] takes unit u returns thistype
            return GetUnitUserData(u)
        endmethod
        method operator unit takes nothing returns unit
            return e[this]
        endmethod
        static if thistype.filter.exists then
            static if thistype.index.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return filter(e[this])
                endmethod
            endif
        elseif thistype.index.exists then
            static if thistype.deindex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return this==GetUnitUserData(e[this])
                endmethod
            endif
        else
            method operator allocated takes nothing returns boolean
                return this==GetUnitUserData(e[this])
            endmethod
        endif
        static if thistype.index.exists then
            private static method onIndexEvent takes nothing returns boolean
                static if thistype.filter.exists then
                    if (filter(e[o])) then
                        set thistype(o).allocated=true
                        call thistype(o).index()
                    endif
                else
                    static if thistype.deindex.exists then
                        set thistype(o).allocated=true
                    endif
                    call thistype(o).index()
                endif
                return false
            endmethod
        endif
        static if thistype.deindex.exists then
            private static method onDeindexEvent takes nothing returns boolean
                static if thistype.filter.exists then
                    static if thistype.index.exists then
                        if (thistype(o).allocated) then
                            set thistype(o).allocated=false
                            call thistype(o).deindex()
                        endif
                    else
                        if (filter(e[o])) then
                            call thistype(o).deindex()
                        endif
                    endif
                else
                    static if thistype.index.exists then
                        set thistype(o).allocated=false
                    endif
                    call thistype(o).deindex()
                endif
                return false
            endmethod
        endif
        static if thistype.index.exists then
            static if thistype.deindex.exists then
                private static method onInit takes nothing returns nothing
                    call RegisterUnitIndexEvent(Condition(function thistype.onIndexEvent),UnitIndexer.INDEX)
                    call RegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent),UnitIndexer.DEINDEX)
                endmethod
            else
                private static method onInit takes nothing returns nothing
                    call RegisterUnitIndexEvent(Condition(function thistype.onIndexEvent),UnitIndexer.INDEX)
                endmethod
            endif
        elseif thistype.deindex.exists then
            private static method onInit takes nothing returns nothing
                call RegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent),UnitIndexer.DEINDEX)
            endmethod
        endif
    endmodule
endlibrary
 
Top