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

Level 11
Joined
Nov 4, 2007
Messages
337
Bribe, the PlayerManager loop is the fastest player loop possible.

God, seriously! Who cares about how quickly you can loop through players? It's not worth the harddisk space. There are real problems to solve.

I think 99% of wc3 maps should be using PlayerManager

No wc3 maps should be using PlayerManager.
Who needs crap like this:
JASS:
    //remove units of inactive players at initialization (not PLAYER_SLOT_STATE_PLAYING)
    constant boolean REMOVE_UNITS_ON_INI_FOR_INACTIVE = false
    
    //remove units of a player that leaves
    constant boolean REMOVE_UNITS_ON_LEAVE = false

It's way too hardcoded. And for a system being hardcoded is bad. Really, a thing like that is more a spell than a system. If I'd want to remove players units on the beginning of a map, I'd do that with a single loop. Even in GUI it wouldn't take much more time than 30 seconds.

But you provide a "system" (more a spell) with hundreds lines of codes that does that and furthermore gives you a performance boost so insignificant that it would not even make a difference on a Z3.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Those are constant booleans for a reason. If the feature is disabled, the code is commented out. It is hundreds of lines of code because of crappy vjass static ifs, otherwise it might be 200 lines tops with everything enabled =).

Anyways, I used it more for the loops than anything else, lol.

And making it optional would be rather difficult because of crappy vjass static ifs >.>.

Anyways, if you have any player loops in your other system, just switch it up to Playermanager ;D. I'm switching up all player loops in all systems to be using PlayerManager btw, so this isn't the only one =). If you look at newer systems by me with even 1 player loop, it uses PlayerManager. I even use it just for the p.get. nowadays.
 
Level 11
Joined
Nov 4, 2007
Messages
337
Yes.
Altough this system is actually not coded badly, I can't see a real good purpose for it.

You could rather make a system called onPlayerInit that contains functions as PlayerRemoveUnits(player p) and does al lthe stuff you'd like to do at the beginning of a game or if any player leaves the game.

It could either extend or require this library, but it should not be inside, since these are solutions for different problems.

You could do that.
But I would not use this system, since its bonus is irrelevant. You could theoritcally also make a system that makes initialising vars 0,09% faster, I wouldn't use that as well.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
I agree with you that those 2 features have many other possible solutions, but those 2 are the most common in simple maps, hence why they are included ;). If they are disabled, it literally removes all of the code associated with those features. PlayerManager is like 800 lines long because I really worked those static ifs ;P. There's a huge chain of like 15 static ifs, all containing the exact same code to ensure that there is nothing extra.


Anyways, I didn't use PlayerManager for speed in this case. As I said before, I did it because the looping is faster to write, easier to read, and yar ;P.


I also use PlayerManager in everything I write now, and nothing is going to change that. If there is even 1 player loop, you can be sure that the system will be using PlayerManager ;P.

The main purpose for PlayerManager was for easy player loops (all, humans, computers, non playing, playing), which is exactly what I use it for ;).


edit
Given this uses it only in ini, I can remove it if you guys are that critical of it.

edit
updated
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Thanks for the update!

The system is really clean at this point and has come a long way. I am currently using it in all of the systems I'm releasing soon. A few months ago you asked me about my opinion on the static [] method operator. I've changed my mind on that because it fits the context really well, so to see that operator added in the next version of UnitIndexStruct would be a nice fit.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
object editor id is now 100% stable!

Updated to run on a slew of lua scripts that I created to stabilize all scripts that generate objects ; O.

Installation is now 1 extra step and requires some special ini code, but it is now stable!! Fuahahahaha...

This is what my system library map generated for me-

jass\luajass.SYSTEM_LIBRARY.j
JASS:
globals//globals
constant integer ABILITIES_UNIT_INDEXER='A!!!'
endglobals//endglobals

Then to use
//! import "luajass.SYSTEM_LIBRARY.j"

and inside of the script, rather than using the old 'OUIN' value, it now uses ABILITIES_UNIT_INDEXER

JASS:
call UnitAddAbility(filterUnit, ABILITIES_UNIT_INDEXER)
call UnitMakeAbilityPermanent(filterUnit, true, ABILITIES_UNIT_INDEXER)

I will be updating all of my other scripts that generated objects to run through this method. It may be more of a pain than the little one liner, but it is 100% STABLE!!. Using the 1 liner method means you have to pick an object editor id that you think nobody will use, meaning that there is a chance you might overwrite an object or collide with another system. With some of the larger systems that use lua, like the bonus ones or jesus4lyf's stuff, this can be a major problem.

This new method should be used over the old method as it is, you guessed it, 100% stable.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Have you thought about using a hashtable if the user wants UnitUserData, like AutoIndex does? Just use GetUnitId for your system instead of GetUnitUserData, and add a couple static if's. Aside from that, perhaps add a tip in the description that users can simply copy the Unit Indexer ability from the demo map if they don't want to bother with objectmerger.

Not sure why you care about the Status library, either, since Status uses AIDS which automatically conflicts with this system.

99.99999% of systems don't rely on ObjectMerger, so the chance of clash is that much lower. It's seriously no reason to require five other libraries to import that ability, since there are tons of reasonable alternatives.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
hashtable if the user wants UnitUserData

Out of the question

so the chance of clash is that much lower

>0% is >0%.

Believe me, I am trying to figure out a better way to do the installation... the only prob is I can't figure out a good way to retrieve the file name of the current map via lua >.>

If I can get across this hurtle, then the scripts will be able to be installed one time for all maps and this shouldn't be an issue anymore. As it is, they are a pain to use, a pain to collect, and a pain to put into the maps : P, but a pain that must be taken for stability : O.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
'#UI#'

Use that as the ObjectMerger code. No other system uses it. Object Editor can't make those symbols to my knowledge, even using JNGP, so only the ObjectMerger would be an issue, but it's not. Don't complicate it. This is as useless as replacing TriggerRegisterAnyUnitEventBJ with a PlayerManager loop.

I think the University hours are getting to you. Just trim out that stuff because it's never going to clash in the first place.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Mate, I'm doing lua... look at all the scripts I wrote and you'll see my major determination to do lua ; P.

I'm a stubborn person. The only way you'll change my mind is through pure logic, and given what you are proposing is less stable than what I'm doing, you have a 0% chance, no matter how hard you try, to change my mind on the matter ; ). Notice the only changes I've ever made are those changes that don't effect the script or that are faster/less code same speed/more stable.

edit
Sadly changing the API around to run off of Event : (.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
So I've been rewriting this a bit to have a simpler API and to make the code easier to manage. There is one extra function call on overhead and some changes in the unit indexing, but other than that it seems pretty good = ).

This is looking like the API
JASS:
UnitIndexer.INDEX
UnitIndexer.DEINDEX
UnitIndexer.enabled

function CodeRegisterUnitIndexEvent takes boolexpr c, integer ev returns nothing
function TriggerRegisterUnitIndexEvent takes trigger t, integer ev returns nothing
function GetUnitById takes integer index returns unit
function GetUnitId takes unit u returns integer
function IsUnitIndexed takes unit u returns boolean
function GetIndexedUnit takes nothing returns unit
function GetIndexedUnitId takes nothing returns integer

module UnitIndexStruct
    Interface:
        private static method unitFilter takes unit u returns boolean
        private method unitIndex takes nothing returns nothing
        private method unitDeindex takes nothing returns nothing

    API:
        static method operator [] takes unit u returns thistype
        method operator unit takes nothing returns unit
        readonly boolean allocated

I know what you are saying... does this mean you have to update your system? It certainly does because I don't believe in supporting deprecated APIs as those are wasted xtra function wrappers ; ).

But, if you don't feel like updating-
JASS:
function OnUnitIndex takes boolexpr c returns nothing
    call CodeRegisterUnitIndexEvent(c, UnitIndexer.INDEX)
endfunction

function OnUnitDeindex takes boolexpr c returns nothing
    call CodeRegisterUnitIndexEvent(c, UnitIndexer.DEINDEX)
endfunction

And those are the only ones I think anyone has used thus far.

This is the current code for UnitIndexer if anyone wants to check it out

JASS:
library UnitIndexer uses /*
    */WorldBounds /*hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
    */Event //hiveworkshop.com/forums/submissions-414/snippet-event-186555/

    globals
        private trigger enter = CreateTrigger()
        private trigger leave = CreateTrigger()
        private unit array units
        
        private integer instanceCount = 0
        private integer array recycle
        private integer recycleCount = 0
        
        private integer indexedUnit = 0
        
        private boolean started = false
    endglobals
    
    private struct PreLoader extends array
        public thistype next
        public thistype previous
        
        public static method run takes nothing returns nothing
            call DestroyTimer(GetExpiredTimer())
            set started = true
        endmethod
        
        public static method eval takes trigger t returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                set indexedUnit = this
                call TriggerEvaluate(t)
                set this = next
            endloop
        endmethod
        
        public static method evalb takes boolexpr c returns nothing
            local trigger t = CreateTrigger()
            call TriggerAddCondition(t, c)
            call eval(t)
            call DestroyTrigger(t)
            set t = null
        endmethod
    endstruct
    
    //! runtextmacro optional UNIT_EVENT_MACRO()
    
    private module UnitIndexerInit
        private static method onInit takes nothing returns nothing
            local integer i = 16
            local boolexpr bc = Condition(function thistype.onLeave)
            local boolexpr bc2 = Condition(function thistype.onEnter)
            local group g = CreateGroup()
            
            set INDEX = CreateEvent()
            set DEINDEX = CreateEvent()
            
            call TriggerRegisterEnterRegion(enter, WorldBounds.worldRegion, bc2)
            
            loop
                set i = i - 1
                call TriggerRegisterPlayerUnitEvent(leave, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, bc)
                call SetPlayerAbilityAvailable(Player(i), ABILITIES_UNIT_INDEXER, false)
                call GroupEnumUnitsOfPlayer(g, Player(i), bc2)
                exitwhen i == 0
            endloop
            
            call DestroyGroup(g)
            set bc = null
            set g = null
            set bc2 = null
            
            call TimerStart(CreateTimer(), 0, false, function PreLoader.run)
        endmethod
    endmodule
    
    struct UnitIndexer extends array
        readonly static integer INDEX
        readonly static integer DEINDEX
        static boolean enabled = true
        
        public static method onEnter takes nothing returns boolean
            //retrieve unit and unit type id
            local unit filterUnit = GetFilterUnit()
            local integer i = indexedUnit
            if (enabled and units[GetUnitUserData(filterUnit)] != filterUnit) then
                if (recycleCount == 0) then
                    set instanceCount = instanceCount + 1
                    set indexedUnit = instanceCount
                else
                    set recycleCount = recycleCount - 1
                    set indexedUnit = recycle[recycleCount]
                endif
                set units[indexedUnit] = filterUnit
                
                call UnitAddAbility(filterUnit, ABILITIES_UNIT_INDEXER)
                call UnitMakeAbilityPermanent(filterUnit, true, ABILITIES_UNIT_INDEXER)
                call SetUnitUserData(filterUnit, indexedUnit)
                    
                if (not started)then
                    set PreLoader(indexedUnit).previous = PreLoader(0).previous
                    set PreLoader(indexedUnit).previous.next = indexedUnit
                    set PreLoader(indexedUnit).next = 0
                    set PreLoader(0).previous = indexedUnit
                endif
                
                call FireEvent(INDEX)
                
                set indexedUnit = i
            endif
            
            set filterUnit = null
            
            return false
        endmethod
        
        private static method onLeave takes nothing returns boolean
            static if LIBRARY_UnitEvent then
                implement optional UnitEventModule
            else
                local integer i = indexedUnit
                local unit u = GetFilterUnit()
                set indexedUnit = GetUnitUserData(u)
                if (GetUnitAbilityLevel(u, ABILITIES_UNIT_INDEXER) == 0 and units[indexedUnit] == u) then
                    if (not started)then
                        set PreLoader(indexedUnit).previous.next = PreLoader(indexedUnit).next
                        set PreLoader(indexedUnit).next.previous = PreLoader(indexedUnit).previous
                    endif
                    
                    call FireEvent(DEINDEX)
                    
                    set recycle[recycleCount] = indexedUnit
                    set recycleCount = recycleCount + 1
                    set units[indexedUnit] = null
                endif
                
                set indexedUnit = i
            endif
            return false
        endmethod
        
        implement UnitIndexerInit
    endstruct
    
    function CodeRegisterUnitIndexEvent takes boolexpr c, integer ev returns nothing
        call CodeRegisterEvent(c, ev)
        
        if (not started and PreLoader(0).next != 0) then
            call PreLoader.evalb(c)
        endif
    endfunction
    
    function TriggerRegisterUnitIndexEvent takes trigger t, integer ev returns nothing
        call TriggerRegisterEvent(t, ev)
        
        if (not started and PreLoader(0).next != 0) then
            call PreLoader.eval(t)
        endif
    endfunction
    
    function GetUnitById takes integer index returns unit
        return units[index]
    endfunction
    
    function GetUnitId takes unit u returns integer
        return GetUnitUserData(u)
    endfunction
    
    function IsUnitIndexed takes unit u returns boolean
        return units[GetUnitUserData(u)] == u
    endfunction
    
    function GetIndexedUnit takes nothing returns unit
        return units[indexedUnit]
    endfunction
    
    function GetIndexedUnitId takes nothing returns integer
        return indexedUnit
    endfunction
    
    module UnitIndexStruct
        static method operator [] takes unit u returns thistype
            return GetUnitUserData(u)
        endmethod
        method operator unit takes nothing returns unit
            return units[this]
        endmethod
        
        static if thistype.unitFilter.exists then
            static if thistype.unitIndex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return unitFilter(units[this])
                endmethod
            endif
        elseif thistype.unitIndex.exists then
            static if thistype.unitDeindex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return GetUnitUserData(units[this]) == this
                endmethod
            endif
        else
            method operator allocated takes nothing returns boolean
                return GetUnitUserData(units[this]) == this
            endmethod
        endif
        
        static if thistype.unitIndex.exists then
            private static method onIndexEvent takes nothing returns boolean
                static if thistype.unitFilter.exists then
                    if (unitFilter(GetIndexedUnit())) then
                        set thistype(GetIndexedUnitId()).allocated = true
                        call thistype(GetIndexedUnitId()).unitIndex()
                    endif
                else
                    static if thistype.unitDeindex.exists then
                        set thistype(GetIndexedUnitId()).allocated = true
                    endif
                    call thistype(GetIndexedUnitId()).unitIndex()
                endif
                return false
            endmethod
        endif
        
        static if thistype.unitDeindex.exists then
            private static method onDeindexEvent takes nothing returns boolean
                static if thistype.unitFilter.exists then
                    static if thistype.unitIndex.exists then
                        if (thistype(GetIndexedUnitId()).allocated) then
                            set thistype(GetIndexedUnitId()).allocated = false
                            call thistype(GetIndexedUnitId()).unitDeindex()
                        endif
                    else
                        if (unitFilter(GetIndexedUnit())) then
                            call thistype(GetIndexedUnitId()).unitDeindex()
                        endif
                    endif
                else
                    static if thistype.unitIndex.exists then
                        set thistype(GetIndexedUnitId()).allocated = false
                    endif
                    call thistype(GetIndexedUnitId()).unitDeindex()
                endif
                return false
            endmethod
        endif
        
        static if thistype.unitIndex.exists then
            static if thistype.unitDeindex.exists then
                private static method onInit takes nothing returns nothing
                    call CodeRegisterUnitIndexEvent(Condition(function thistype.onIndexEvent), UnitIndexer.INDEX)
                    call CodeRegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent), UnitIndexer.DEINDEX)
                endmethod
            else
                private static method onInit takes nothing returns nothing
                    call CodeRegisterUnitIndexEvent(Condition(function thistype.onIndexEvent), UnitIndexer.INDEX)
                endmethod
            endif
        elseif thistype.unitDeindex.exists then
            private static method onInit takes nothing returns nothing
                call CodeRegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent), UnitIndexer.DEINDEX)
            endmethod
        endif
    endmodule
endlibrary

The code is easier to read and a few things have been removed. When this is recoupled with UnitEvent, I expect the code will again turn unreadable, but until then : P.

This is UnitEvent code (untested and no module atm) if anyone wants to check it out

It has a myriad of improvements, including the movement from an integer array to a stack in the on reincarnation function to save on a global var and math (yes, a few improvements can still be made, but I'm tired atm, which is why this isn't going to be submitted yet).

Don't comment on the UnitEvent code as it is just to show you my WIP on rewrite : P.
JASS:
library UnitEvent uses UnitIndexer
    native UnitAlive takes unit id returns boolean
    
    //! textmacro UNIT_EVENT_MACRO
    
    globals
        private real decayTime = 1000
        
        private trigger death = CreateTrigger()
        private timer time
        private real array timestamp
        
        private boolean array animated
        private boolean array dead
        private boolean array reincarnating
        private boolean array exploded
        
        private integer array reincarnations
        private timer reincarnationTimer = CreateTimer()
        
        private integer unitEvent
    endglobals
    
    private function OnReincarnateStart takes nothing returns nothing
        local integer i = unitEvent
        set unitEvent = reincarnations[0]
        loop
            if (reincarnating[unitEvent]) then
                call FireEvent(UnitEvent.START_REINCARNATE)
            endif
            set unitEvent = reincarnations[unitEvent]
            exitwhen unitEvent == 0
        endloop
        set reincarnations[0] = 0
        set unitEvent = i
    endfunction
    
    private function OnDeath takes nothing returns boolean
        local unit u = GetFilterUnit()
        local integer i = GetUnitUserData(u)
        local integer i2
        if (units[i] == u) then
            set dead[i] = true
            set reincarnating[i] = false
            set i2 = unitEvent
            set unitEvent = i
            if (not animated[i]) then
                set timestamp[i] = TimerGetElapsed(time)
                call FireEvent(UnitEvent.DEATH)
            else
                set exploded[i] = true
                set animated[i] = false
                call FireEvent(UnitEvent.EXPLODE)
            endif
            set unitEvent = i2
        endif
        set u = null
        return false
    endfunction
    
    private module UnitEventModule
        local unit u = GetFilterUnit()
        local integer i = indexedUnit
        local integer i2 = unitEvent
        local integer ui = i
        set indexedUnit = GetUnitUserData(u)
        set unitEvent = indexedUnit
        
        if (units[ui] == u) then
            if (GetUnitAbilityLevel(u, ABILITIES_UNIT_INDEXER) == 0) then
                set reincarnating[ui] = false
                set animated[ui] = false
                if (not exploded[ui]) then
                    if (dead[ui] and TimerGetElapsed(time)-timestamp[ui] >= decayTime) then
                        set dead[ui] = false
                        call FireEvent(UnitEvent.DECAY)
                    else
                        set dead[ui] = false
                        call FireEvent(UnitEvent.REMOVE)
                    endif
                else
                    set dead[ui] = false
                    set exploded[ui] = false
                endif
                
                if (not started) then
                    set PreLoader(ui).previous.next = PreLoader(ui).next
                    set PreLoader(ui).next.previous = PreLoader(ui).previous
                endif
                
                call FireEvent(DEINDEX)
                
                set recycle[recycleCount] = indexedUnit
                set recycleCount = recycleCount + 1
                set units[ui] = null
            elseif (UnitAlive(u)) then
                if (reincarnating[ui]) then
                    call FireEvent(UnitEvent.REINCARNATE)
                    set reincarnating[ui] = false
                elseif (dead[ui]) then
                    set dead[ui] = false
                    if (IsUnitType(u, UNIT_TYPE_SUMMONED)) then
                        set animated[ui] = true
                        call FireEvent(UnitEvent.ANIMATE)
                    else
                        call FireEvent(UnitEvent.RESURRECT)
                    endif
                else
                    set reincarnating[ui] = true
                    set reincarnations[ui] = reincarnations[0]
                    set reincarnations[0] = ui
                    call TimerStart(reincarnationTimer, 0, false, function OnReincarnateStart)
                endif
            endif
        endif
        
        set indexedUnit = i
        set unitEvent = i2
        set u = null
    endmodule
    
    private module UnitEventInits
        private static method decayer takes nothing returns boolean
            set decayTime = TimerGetElapsed(time)-.9375
            call DestroyTrigger(GetTriggeringTrigger())
            return false
        endmethod
        
        private static method onInit takes nothing returns nothing
            local integer i = 16
            local boolexpr bc = Condition(function OnDeath)
            local trigger t = CreateTrigger()
            local unit u
            
            set DEATH = CreateEvent()
            set REMOVE = CreateEvent()
            set DECAY = CreateEvent()
            set EXPLODE = CreateEvent()
            set RESURRECT = CreateEvent()
            set REINCARNATE = CreateEvent()
            set ANIMATE = CreateEvent()
            set START_REINCARNATE = CreateEvent()
            set UnitIndexer.enabled = false
            set u = CreateUnit(Player(14), UNITS_UNIT_EVENT, WorldBounds.maxX, WorldBounds.maxY, 0)
            set UnitIndexer.enabled = true
            call KillUnit(u)
            call ShowUnit(u, false)
            call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_ISSUED_ORDER)
            call TriggerAddCondition(t, function thistype.decayer)
            
            loop
                set i = i - 1
                call TriggerRegisterPlayerUnitEvent(death, Player(i), EVENT_PLAYER_UNIT_DEATH, bc)
                exitwhen i == 0
            endloop
            
            call TimerStart(time, 1000000, false, null)
            
            set u = null
            set t = null
            set bc = null
        endmethod
    endmodule
    
    struct UnitEvent extends array
        readonly static integer DEATH
        readonly static integer REMOVE
        readonly static integer DECAY
        readonly static integer EXPLODE
        readonly static integer RESURRECT
        readonly static integer REINCARNATE
        readonly static integer ANIMATE
        readonly static integer START_REINCARNATE
        
        implement UnitEventInits
    endstruct
    
    //! endtextmacro
endlibrary
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
You will need this, in case the trigger has actions attached to it:

JASS:
        public static method eval takes trigger t returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                set indexedUnit = this
                if (TriggerEvaluate(t)) then
                    call TriggerExecute(t)
                endif
                set this = next
            endloop
        endmethod

Also, I might split up the differences between UI, AIDS and AutoIndex into a neat table to give users perspective on them.

Is there a good reason why UnitIndexer.onEnter is a public method?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->Is there a good reason why UnitIndexer.onEnter is a public method?

nice eye ;P, I'll fix.

->You will need this, in case the trigger has actions attached to it:
I'll split evalb and eval up then, but gj ;D.


edit
Fixed and spotted another prob that was also fixed. If the trigger is destroyed or disabled during the eval, the loop needs to break ;P.

JASS:
        public static method eval takes trigger t returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                set indexedUnit = this
                if (IsTriggerEnabled(t)) then
                    if (TriggerEvaluate(t)) then
                        call TriggerExecute(t)
                    endif
                else
                    return
                endif
                set this = next
            endloop
        endmethod
        
        public static method evalb takes boolexpr c returns nothing
            local trigger t = CreateTrigger()
            local thistype this = thistype(0).next
            call TriggerAddCondition(t, c)
            loop
                exitwhen this == 0
                set indexedUnit = this
                call TriggerEvaluate(t)
                set this = next
            endloop
            call DestroyTrigger(t)
            set t = null
        endmethod

private static method onEnter takes nothing returns boolean
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Not worth it? Think about it. You rather shorten every individual variable name yet keep them all with the same long prefix. You could achieve the same effect by lengthening the variable names back to normal but shortening the scope-prefix.

If you're going to do one, do the one that keeps it more readable. If not, just don't bother shortening the variable names at all.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
This is an implementation that has an option to not depend on UnitUserData.

JASS:
library UnitIndexer requires /*
    */WorldBounds, /*hiveworkshop.com/forums/showthread.php?t=180494
    */Event, /*hiveworkshop.com/forums/showthread.php?t=186555
    */optional Table //hiveworkshop.com/forums/showthread.php?t=188084

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ Unit Indexer ~~ By Nestharus ~~ Version 4.1.0.0 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Events:
//      static constant Event UnitIndexer.INDEX
//      static constant Event UnitIndexer.DEINDEX
//
//Fields:
//      static boolean UnitIndexer.enabled
//          Enables/Disables unit indexing
//
//Functions:
//      function RegisterUnitIndexEvent takes boolexpr c, integer ev returns nothing
//      function TriggerRegisterUnitIndexEvent takes trigger t, integer ev 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
//
//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 GetUnitId(u)
//
//      readonly unit unit
//          The indexed unit of the struct
//
//      allocated
//          Is unit allocated for the struct
//
//      Interface:
//          private method index takes nothing returns nothing
//          private method deindex takes nothing returns nothing
//
//          private static method filter takes unit u returns boolean
//              Determines whether or not to apply struct to unit
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    globals
        // If this is false and the Table library is present, a hashtable will
        // be used instead of UnitUserData.
        private constant boolean USER_DATA = true
        private keyword PreLoader
        
        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
    endglobals
    
    function GetIndexedUnitId takes nothing returns integer
        return o
    endfunction
    
    function GetIndexedUnit takes nothing returns unit
        return e[o]
    endfunction
    
    function GetUnitId takes unit u returns integer
        static if (LIBRARY_Table and not USER_DATA) then
            return PreLoader.tb[GetHandleId(u)]
        else
            return GetUnitUserData(u)
        endif
    endfunction
    
    function GetUnitById takes integer W returns unit
        return e[W]
    endfunction
    
    function IsUnitIndexed takes unit u returns boolean
        return e[GetUnitId(u)] == u
    endfunction
    
    private module tbMod
        static if (LIBRARY_Table and not USER_DATA) then
            Table tb
            private static method onInit takes nothing returns nothing
                set tb = Table.create()
            endmethod
        endif
    endmodule
    
    private struct PreLoader extends array
        
        implement tbMod
        
        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 f == 0
                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 f == 0
                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 = 16
            local boolexpr bc = Condition(function thistype.onLeave)
            local boolexpr bc2 = Condition(function thistype.onEnter)
            local group g = CreateGroup()
            
            set INDEX = CreateEvent()
            set DEINDEX = CreateEvent()
            
            call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, bc2)
            
            loop
                set i = i - 1
                call TriggerRegisterPlayerUnitEvent(l, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, bc)
                call SetPlayerAbilityAvailable(Player(i), ABILITIES_UNIT_INDEXER, false)
                call GroupEnumUnitsOfPlayer(g, Player(i), bc2)
                exitwhen i == 0
            endloop
            
            call DestroyGroup(g)
            set bc = null
            set g = null
            set bc2 = null
            
            call TimerStart(CreateTimer(), 0, false, function PreLoader.run)
        endmethod
    endmodule
    
    struct UnitIndexer extends array
        readonly static Event INDEX
        readonly static Event DEINDEX
        static boolean enabled = true
        
        private static method onEnter takes nothing returns boolean
            //retrieve unit and unit type id
            local unit Q = GetFilterUnit()
            local integer i
            local integer d = o
            if (enabled and e[GetUnitId(Q)] != Q) then
                if (y == 0) 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)
                
                static if (LIBRARY_Table and not USER_DATA) then
                    set tb[GetHandleId(Q)] = i
                else
                    call SetUnitUserData(Q, i)
                endif
                
                set e[i] = Q
                    
                if (not a)then
                    set p[i] = p[0]
                    set n[p[i]] = i
                    set n[i] = 0
                    set p[0] = 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 = GetUnitId(u)
                local integer d = o
                if (GetUnitAbilityLevel(u, ABILITIES_UNIT_INDEXER) == 0 and e[i] == u) then
                    if (not a)then
                        set n[p[i]] = n[i]
                        set p[n[i]] = p[i]
                    endif
                    
                    set o = i
                    call FireEvent(DEINDEX)
                    set o = d
                    
                    static if (LIBRARY_Table and not USER_DATA) then
                        call tb.remove(GetHandleId(u))
                    endif
                    
                    set n[i] = y
                    set y = i
                    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 n[0] != 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 n[0] != 0) then
            call PreLoader.eval(t)
        endif
    endfunction
    
    module UnitIndexStruct
        static method operator [] takes unit u returns thistype
            return GetUnitId(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 GetUnitId(e[this]) == this
                endmethod
            endif
        else
            method operator allocated takes nothing returns boolean
                return GetUnitId(e[this]) == 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.unitDeindex.exists then
            private static method onInit takes nothing returns nothing
                call RegisterUnitIndexEvent(Condition(function thistype.onDeindexEvent), UnitIndexer.DEINDEX)
            endmethod
        endif
    endmodule
endlibrary
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
->This is an implementation that has an option to not depend on UnitUserData.

Denied.


UnitUserData only. If you use it, auto incompatible, done.

If you use a hashtable in a unit indexer, it'd be faster to just SaveInteger(mySpellTable, GetHandleId(targetUnit), 0, spellid). At that point, there is absolutely no point to using a unit indexer.

Don't waste your time arguing because I am never going to implement it.

If you use xe, then xe will leak every time it makes a dummy unit.

It is my strong belief that SetUnitUserData should never be used except by a unit indexing system or by the mapper (not both ofc, lol). I've had that belief almost since I started modding in wc3 and that belief isn't going to change. I won't go out of my way to support resources that commit the taboo ;P.
 
Last edited:
Level 22
Joined
Nov 14, 2008
Messages
3,256
Why do I get this object merger error?

installerror.jpg
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
Updated to now support locks. Fully tested.

JASS:
struct tester extends array
    private static integer count = 0
    private static integer min = 0
    private static integer max = 0
    private method index takes nothing returns nothing
        local UnitIndex i = 0
        if (max > 0) then
            if (GetIndexedUnitId() > max or GetIndexedUnitId() < min) then
                call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "BUG")
            endif
            if (GetUnitTypeId(GetIndexedUnit()) == 'hfoo') then
                set i = GetIndexedUnitId()
                call i.lock()
                set max = max + 1
            endif
            set count = count + 1
            if (count < 5) then
                if (count - count/2*2 == 0) then
                    call CreateUnit(Player(0), 'hpea', WorldBounds.centerX, WorldBounds.centerY, 0)
                else
                    call CreateUnit(Player(0), 'hfoo', WorldBounds.centerX, WorldBounds.centerY, 0)
                endif
            endif
            call RemoveUnit(GetIndexedUnit())
            if (i != 0) then
                call i.unlock()
                set max = max - 1
            endif
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, I2S(GetIndexedUnitId()))
        else
            set count = GetIndexedUnitId()
        endif
    endmethod
    private method deindex takes nothing returns nothing
        if (max > 0) then
            set count = count - 1
        endif
    endmethod
    private static method runs takes nothing returns nothing
        if (max == 0) then
            set min = count+1
            set count = 0
            set max = min+5
            call CreateUnit(Player(0), 'hpea', WorldBounds.centerX, WorldBounds.centerY, 0)
        else
            call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "--------------------")
            call CreateUnit(Player(0), 'hpea', WorldBounds.centerX, WorldBounds.centerY, 0)
        endif
    endmethod
    private static method run takes nothing returns nothing
        call TimerStart(CreateTimer(), 1, true, function thistype.runs)
    endmethod
    implement Test
    implement UnitIndexStruct
endstruct

Whenever storing unit index pointers and not planning on cleaning them up on deindex, always lock them first. You don't have to do this when storing unit pointers, but you do it with unit index pointers. It's a good idea to clean them up on deindex and avoid locking altogether, but there are some cases where locking has much less overhead than the cleanup (like in AdvDamageEvent and DamageEvent).
 
Level 4
Joined
Jan 27, 2010
Messages
133
The only reason given on the first post for using this before AutoIndex is "speed". Speed for indexing systems ONLY matters on the lookups. AutoIndex is equally fast as your system on the lookups.

The real difference here is that AutoIndex can be used in ALL maps, because of the option to use HashTable. Me basing a script/system on AutoIndex ensures that the script/system can be used by everyone.
 
Level 4
Joined
Jan 27, 2010
Messages
133
using hashtable makes AutoIndex lookup slower than this btw.

Of course, a little bit slower. Back in gamecache + H2I days it might even have mattered. Hashtable is not that slow.

That's not the point though. AutoIndex is equally fast in any application where you can use UnitIndexer. The only difference is that AutoIndex can also work in maps which already use UnitUserData (making it much, MUCH, more attractive to use as a base for systems/scripts).

Then there's of course the problem with installation. How many map makers will want to use a system that requires a system that requires installation of a lua file that requires installation of another lua files? Just to avoid a very low probability problem with the ObjectMerger? Which can easily be solved in the maps where the problem would arise? If you are really concerned about that "problem", you should work directly on JASSHelper; not working around it.

Also, other points covered were the craziness it has with events + how it hooks RemoveUnit.

I totally understand how you feel about it. "Lots of code doing 'nothing' ". It's just that... I don't think it matters in any real application. Wc3 spends so much CPU on other stuff (like 3d models and pathing algorithms).

Or do you have another reason to dislike the hook?
 
Level 4
Joined
Jan 27, 2010
Messages
133
Doesn't change the fact that AutoIndex is still the worst of the 3 indexing systems, lol.

And indexing becomes pretty pointless if you use a hashtable >.>.

Yeah, but you can design s system for optimal performance and still let people who use UnitUserData use the system. Making AutoIndex a better foundation to build systems on.

AutoIndex is pretty much cnp. If you got rid of the installation procedure and made it possible to use Hash, UnitIndexer could be a good replacement for AutoIndex.

I've made a testmap showing AutoIndex fail. It would be interesting to know if UnitIndexer can handle the test better (I'd love to test, but I'd rather not go through the installation procedure :slp:)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You do realize how easy it is to install lua stuff right?

A lot of people dread it, but once they learn how they're surprised at just how easy it is. Bribe is a prime example. He didn't touch the stuff for a long time, then one day he tried it out and was like, "wow, it's really easy o-o"

The mini tut can get Lua running in your map in like 15 seconds. From there, the systems are one time installations (install one time on your computer via saving in a map and you're done). And the mini tut tells you exactly which lines to change as well as how to run each type of Lua script. This post took me longer to write than it would have taken you to put Lua into your map.

For a more in-depth explanation on Lua, there is this tutorial
http://www.hiveworkshop.com/forums/jass-ai-scripts-tutorials-280/lua-object-generation-191740/
 
Level 4
Joined
Jan 27, 2010
Messages
133
You do realize how easy it is to install lua stuff right?

Not for me, no :p It took like 30 minutes. What was all that talk about separate files? It confused me. Also, it was not that amusing to play "follow the dependency" cross the site...

-----

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).

As I see it UnitIndexer is more reliable. I would use UnitIndexer if it was a bit more flexible.

  • No installation procedures. "Normal" persons won't want to install a system made by me if it's depending on a system which is depending on a lua file which is depending on a lua file which...
  • Add the HASH option. Use static ifs and you won't lose the precious performance.

Some elaboration:

User A wants to use system B (depending on an indexing system for detecting RemoveUnit). User A is a 'n00b' (rawcode-pun intended); who has used UnitUserData already.

If the indexing system allows for the Hash-approach, user A can happily continue his work. At least unless he starts using too many units. If the indexing system does not support the Hash-approach; the jass-vultures of the forums will tell him to rewrite the code/triggers of his map. He will not want to do that, and as a result, he won't use system B.

Allowing the indexing system to switch to hash can thus be a large convenience for some users; while not affecting the speed of the professional users at all (which is why I think it's a nice backup feature to have in an indexing system).
 

Attachments

  • test.w3x
    42.4 KB · Views: 71
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I've also argued the case for using a hashtable in static-if's, there is absolutely no reason not to include it. And people have asked for it in the past. With the hashtable, there's no reason to use AutoIndex. So your "competition" is eliminated.

Regardless of what Nestharus prefers himself, the system should support it.
 
Top