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

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
An alternative version to AII. Credits to Vexiorian for original ItemDex idea. Since I'm unsure which item indexer most of you have been using if any, I've decided to uniform module API with powerfull UnitIndexer by Nestharus - it's basicaly the same. Thanks to you Nes.
Table is in use because of lacking GetTriggerItem() native. Some simple hash method could be used instead but for now it's Table.

(Old) I'm off for week or two, will fix my stuff as soon as possible. Thanks for your opinions.
JASS:
/*****************************************************************************
*
*    ItemIndexer v2.0.0.0
*       by Bannar aka Spinnaker
*
*    Attaches unique indexes to items.
*
******************************************************************************
*
*    Important:
*       Due to lack of perfect index method, ItemIndexer uses GetItemIndex function to attach index
*       if needed to given item before actually returning the value.
*       Use NewItem function instead of CreateItem in order to create and index item at once.
*
******************************************************************************
*
*    Optionaly uses:
*       Event by Nestharus
*          hiveworkshop.com/forums/jass-resources-412/snippet-event-186555/
*       RegisterPlayerUnitEvent by Magtheridon96
*          hiveworkshop.com/forums/jass-functions-413/snippet-registerplayerunitevent-203338/
*       Table by Bribe
*          hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*       WorldBounds by Nestharus
*          hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
*       RestoreTrigger by Bannar (Spinnaker)
*          hiveworkshop.com/forums/submissions-414/snippet-restoretrigger-233655/
*
******************************************************************************
*
*    globals
*
*       lastCreatedItem - reference to last created item, similar to bj_lastCreatedItem
*
*
*    functions:
*
*       function NewItem takes integer itemid, real x, real y returns item
*       function LastCreatedItem takes nothing returns item
*
*       function GetIndexedItemId takes nothing returns integer
*       function GetIndexedItem takes nothing returns item
*
*       function GetItemById takes integer v returns item
*       function GetItemId takes item it returns integer
*       function IsItemIndexed takes item it returns boolean
*
*       function RegisterItemIndexEvent takes code c, integer ev returns nothing
*       function TriggerRegisterItemIndexEvent takes trigger t, integer ev returns nothing
*
*
*    module ItemIndexModule:
*
*       static method operator [] takes item it returns thistype
*       method operator item takes nothing returns item
*       readonly boolean allocated
*
*       private method onIndex takes nothing returns nothing
*       private method onDeindex takes nothing returns nothing
*       private static method filterItem takes item it returns boolean
*
*
*    struct ItemIndex - represents and manages item index
*
*       method incRef takes nothing returns nothing
*       method decRef takes nothing returns nothing
*
*
*    struct ItemIndexEvent - child struct to Event, contains and handles item index events
*
*       readonly static integer INDEX
*       readonly static integer DEINDEX
*       static method register takes code c, integer ev returns nothing
*       - only if parent struct Event does not exist
*
*
*    struct ItemIndex - "almost" private struct handling all index and deindex operations
*
*       static boolean enabled
*       static method indexItem takes item it returns boolean
*
*
*****************************************************************************/
library ItemIndexer uses /*
    */ optional Event /*
    */ optional RegisterPlayerUnitEvent /*
    */ optional Table /*
    */ optional WorldBounds /*
    */ optional RestoreTrigger

    globals
        private integer eventIndex = 0
        private item lastCreatedItem = null
        private item array itemArray
        private constant integer TRIG_RESTORE_THRESHOLD = 50
        private integer emptyEvents = 0
    endglobals

static if not LIBRARY_Event then
    globals
        private real evCaller = -1
        private trigger array evTrigs
    endglobals
endif

    private module ItemIndexEventInit
        private static method onInit takes nothing returns nothing
            static if LIBRARY_Event then
                set INDEX = CreateEvent()
                set DEINDEX = CreateEvent()
            else
                set evTrigs[INDEX] = CreateTrigger()
                set evTrigs[DEINDEX] = CreateTrigger()
            endif

            call TimerStart(CreateTimer(), 0, false, function thistype.refireIndexEvent)
        endmethod
    endmodule

// ItemIndexEvent struct realted to INDEX and DEINDEX events
    struct ItemIndexEvent extends array
        static if LIBRARY_Event then
            readonly static Event INDEX
            readonly static Event DEINDEX
        else
            readonly static integer INDEX = 0
            readonly static integer DEINDEX = 1

            static method register takes code c, integer ev returns nothing
                call TriggerAddCondition(evTrigs[ev], Filter(c))
            endmethod
        endif

        static method fireEvent takes integer index, integer ev returns nothing
            local integer saveIndex = eventIndex
            set eventIndex = index

            static if LIBRARY_Event then
                call FireEvent(ev)
            else
                set evCaller = ev
                call TriggerEvaluate(evTrigs[ev])
                set evCaller = -1
            endif

            set eventIndex = saveIndex
        endmethod

        private static method refireIndexEvent takes nothing returns nothing
            local ItemIndex this = ItemIndex(0).next

            loop
                exitwhen 0==this
                call ItemIndexEvent.fireEvent(this, ItemIndexEvent.INDEX)
                set this = this.next
            endloop

            call DestroyTimer(GetExpiredTimer())
        endmethod
    
        implement ItemIndexEventInit
    endstruct

// ItemIndex struct which instance represents given item's index
    struct ItemIndex extends array
        private static integer idCount = 0
        private static thistype recycle = 0
        readonly thistype next
        private thistype prev
        private integer refCount

        static method allocate takes nothing returns thistype
            local thistype this
            if ( 0 == recycle ) then
                set idCount = idCount+1
                set this = idCount
            else
                set this = recycle
                set recycle = recycle.next
            endif

            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set this.next = 0
            set thistype(0).prev = this

            return this
        endmethod

        method deallocate takes nothing returns nothing
            set this.prev.next = this.next
            set this.next.prev = this.prev
            if ( this.refCount == 0 ) then
                set this.next = recycle
                set recycle = this
            endif
        endmethod

        method incRef takes nothing returns nothing
            if (itemArray[this] != null) then
                set this.refCount = this.refCount+1
            endif
        endmethod

        method decRef takes nothing returns nothing
            if ( this.refCount > 0 ) then
                set this.refCount = this.refCount - 1
                if ( this.refCount == 0 and null == itemArray[this] ) then
                    set this.next = recycle
                    set recycle = this
                endif
            endif
        endmethod
    endstruct

// ItemIndexer initializer
    private module ItemIndexerInit
        private static method onInit takes nothing returns nothing
            local group g = CreateGroup()
            local rect world
            local region worldRegion
            local trigger t

            static if LIBRARY_Table then
                set itemTable = Table.create()
            endif

            set funcLeaving = Condition(function thistype.deindexLeaving)

            static if LIBRARY_RestoreTrigger then
                set trig = Trigger.create()
                call trig.addCondition(function thistype.deindexLeaving)
            else
                set trig = CreateTrigger()
                call TriggerAddCondition(trig, function thistype.deindexLeaving)
            endif

            static if (LIBRARY_WorldBounds) then
                call EnumItemsInRect(WorldBounds.world, Condition(function thistype.indexPreplaced), null)
                call GroupEnumUnitsInRect(g, WorldBounds.world, Condition(function thistype.indexInventory))
            else
                set world = GetWorldBounds()
                set worldRegion = CreateRegion()
                call RegionAddRect(worldRegion, world)

                call EnumItemsInRect(world, Condition(function thistype.indexPreplaced), null)
                call GroupEnumUnitsInRect(g, world, Condition(function thistype.indexInventory))

                call RemoveRect(world)
                call RemoveRegion(worldRegion)
                set world = null
                set worldRegion = null
            endif

            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function thistype.indexManipulated)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELL_ITEM, function thistype.indexManipulated)
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PAWN_ITEM, function thistype.deindexSold)
            else
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SELL_ITEM)
                call TriggerAddCondition(t, Condition(function thistype.indexManipulated))
                set t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
                call TriggerAddCondition(t, Condition(function thistype.deindexSold))
                set t = null
            endif

            call DestroyGroup(g)
            set g = null
        endmethod
    endmodule

// "almost" private struct ItemIndex; enable and indexItem are the only public members
    struct ItemIndexer extends array
        static boolean enabled = true
        // prevents cb for deindexLeaving method from being generated
        private static boolexpr funcLeaving = null

        static if LIBRARY_RestoreTrigger then
            private static Trigger trig
        else
            private static trigger trig
        endif

        static if LIBRARY_Table then
            private static Table itemTable
        else
            private static hashtable itemTable = InitHashtable()
        endif

        // wrappers for storing & removing handle id, exist to make code cleaner
        private static method storeHandleId takes item it returns nothing
            static if LIBRARY_Table then
                set itemTable.item[GetHandleId(it)] = it
            else
                call SaveItemHandle(itemTable, 0, GetHandleId(it), it)
            endif
        endmethod

        private static method removeHandleId takes item it returns nothing
            static if LIBRARY_Table then
                call itemTable.remove(GetHandleId(it))
            else
                call RemoveSavedHandle(itemTable, 0, GetHandleId(it))
            endif
        endmethod

        // wrappers for refreshing and registering trigger data, exist to make code cleaner
        private static method registerDeathEvent takes item it returns nothing
            static if LIBRARY_RestoreTrigger then
                call TriggerRegisterDeathEvent(trig.trigger, it)
            else
                call TriggerRegisterDeathEvent(trig, it)
            endif
        endmethod

        private static method refreshMainTrigger takes nothing returns nothing
            local ItemIndex this = ItemIndex(0).next

            static if LIBRARY_RestoreTrigger then
                call trig.restore()
            else
                call DestroyTrigger(trig)
                set trig = CreateTrigger()
                call TriggerAddCondition(trig, funcLeaving)
            endif

            loop
                exitwhen 0 == this
                call registerDeathEvent(itemArray[this])
                set this = this.next
            endloop
        endmethod

        static method indexItem takes item it returns boolean
            local ItemIndex this

            if ( enabled and it != itemArray[GetItemUserData(it)] ) then
                set this = ItemIndex.allocate()
                call SetItemUserData(it, this)
                set itemArray[this] = it
                call storeHandleId(it)

                call registerDeathEvent(it)
                call ItemIndexEvent.fireEvent(this, ItemIndexEvent.INDEX)
            endif
            return false
        endmethod

        // index helper methods
        private static method indexPreplaced takes nothing returns boolean
            return indexItem(GetFilterItem())
        endmethod

        private static method indexManipulated takes nothing returns boolean
            return indexItem(GetManipulatedItem())
        endmethod

        private static method indexInventory takes nothing returns boolean
            local unit u = GetFilterUnit()
            local integer slot = 0
            local integer last = UnitInventorySize(u)
            loop
                exitwhen slot == last
                if ( UnitItemInSlot(u, slot) != null ) then
                    call indexItem(UnitItemInSlot(u, slot))
                endif
                set slot = slot+1
            endloop
            set u = null
            return false
        endmethod

        private static method deindexItem takes item it returns boolean
            local ItemIndex this = GetItemUserData(it)

            if ( it == itemArray[this] ) then
                call ItemIndexEvent.fireEvent(this, ItemIndexEvent.DEINDEX)

                call this.deallocate()
                set itemArray[this] = null
                call removeHandleId(it)

                set emptyEvents = emptyEvents + 1
                if ( emptyEvents >= TRIG_RESTORE_THRESHOLD ) then
                    call refreshMainTrigger()
                    set emptyEvents = 0
                endif

            endif
            return false
        endmethod

        // deindex helper methods
        private static method deindexLeaving takes nothing returns boolean
            static if LIBRARY_Table then
                return deindexItem(itemTable.item[GetHandleId(GetTriggerWidget())])
            else
                return deindexItem(LoadItemHandle(itemTable, 0, GetHandleId(GetTriggerWidget())))
            endif
        endmethod

        private static method deindexSold takes nothing returns boolean
            return deindexItem(GetSoldItem())
        endmethod

        implement ItemIndexerInit
    endstruct

// function API
    function NewItem takes integer itemid, real x, real y returns item
        set lastCreatedItem = CreateItem(itemid, x, y)
        call ItemIndexer.indexItem(lastCreatedItem)
        return lastCreatedItem
    endfunction

    function LastCreatedItem takes nothing returns item
        return lastCreatedItem
    endfunction

    function GetIndexedItemId takes nothing returns integer
        return eventIndex
    endfunction

    function GetIndexedItem takes nothing returns item
        return itemArray[eventIndex]
    endfunction

    function IsItemIndexed takes item it returns boolean
        return ( it == itemArray[GetItemUserData(it)] )
    endfunction

    function GetItemIndex takes item it returns integer
        if not ( IsItemIndexed(it) ) then
           call ItemIndexer.indexItem(it)
        endif
        return GetItemUserData(it)
    endfunction

    function GetItemByIndex takes integer index returns item
        return itemArray[index]
    endfunction

    function RegisterItemIndexEvent takes code c, integer ev returns nothing
        static if LIBRARY_Event then
            call RegisterEvent(Filter(c), ev)
        else
            call ItemIndexEvent.register(c, ev)
        endif
        return
    endfunction

    function TriggerRegisterItemIndexEvent takes trigger t, integer ev returns nothing
        static if LIBRARY_Event then
            call TriggerRegisterEvent(t, ev)
        else
            call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "evCaller", EQUAL, ev)
        endif
        return
    endfunction

// module API
    module ItemIndexModule
        static method operator [] takes item it returns thistype
            return GetItemUserData(it)
        endmethod

        method operator item takes nothing returns item
            return itemArray[this]
        endmethod

        static if thistype.filterItem.exists then
            static if thistype.onIndex.exists then

                static if thistype.onDeindex.exists then
                    readonly boolean allocated
                else
                    method operator allocated takes nothing returns boolean
                        return filterItem(item)
                    endmethod
                endif
            else
                method operator allocated takes nothing returns boolean
                    return filterItem(item)
                endmethod
            endif

        elseif ( thistype.onIndex.exists ) then

            static if thistype.onDeindex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return ( this == GetItemUserData(item) )
                endmethod
            endif
        else
            method operator allocated takes nothing returns boolean
                return ( this == GetItemUserData(item) )
            endmethod
        endif

        static if thistype.onIndex.exists then

            private static method onIndexEvent takes nothing returns boolean
                static if thistype.filterItem.exists then

                    if ( filterItem( itemArray[eventIndex] ) ) then
                        static if thistype.onDeindex.exists then
                            set thistype(eventIndex).allocated=true
                        endif
                        call thistype(eventIndex).onIndex()
                    endif
                else
                    static if thistype.onDeindex.exists then
                        set thistype(eventIndex).allocated=true
                    endif
                    call thistype(eventIndex).onIndex()
                endif

                return false
            endmethod
        endif

        static if thistype.onDeindex.exists then

            private static method onDeindexEvent takes nothing returns boolean
                static if thistype.filterItem.exists then

                    static if thistype.onIndex.exists then
                        if ( thistype(eventIndex).allocated ) then
                            set thistype(eventIndex).allocated=false
                            call thistype(eventIndex).onDeindex()
                        endif
                    else
                        if ( filterItem( itemArray[eventIndex] ) ) then
                            call thistype(eventIndex).onDeindex()
                        endif
                    endif
                else
                    static if thistype.onIndex.exists then
                        set thistype(eventIndex).allocated=false
                    endif
                    call thistype(eventIndex).onDeindex()
                endif

                return false
            endmethod
        endif

        static if thistype.onIndex.exists then

            static if thistype.onDeindex.exists then

                private static method onInit takes nothing returns nothing
                    call RegisterItemIndexEvent(function thistype.onIndexEvent, ItemIndexEvent.INDEX)
                    call RegisterItemIndexEvent(function thistype.onDeindexEvent, ItemIndexEvent.DEINDEX)
                endmethod
            else
                private static method onInit takes nothing returns nothing
                    call RegisterItemIndexEvent(function thistype.onIndexEvent, ItemIndexEvent.INDEX)
                endmethod
            endif

        elseif thistype.onDeindex.exists then

            private static method onInit takes nothing returns nothing
                call RegisterItemIndexEvent(function thistype.onDeindexEvent, ItemIndexEvent.DEINDEX)
            endmethod
        endif
    endmodule

endlibrary
Demo:
JASS:
struct ItemIndexDemo extends array
    private method onIndex takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, GetItemName(item) + " was indexed (" + I2S(this) + ")")
    endmethod

    private method onDeindex takes nothing returns nothing
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, GetItemName(item) + " was deindexed (" + I2S(this) + ")")
    endmethod
    
    private static method filterItem takes item it returns boolean
        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, GetItemName(it) + " was filtered")
        return true
    endmethod

    private static method runTest takes nothing returns nothing
        local item it =  NewItem('rin1', 50, 50)
        call RemoveItem(it)
    endmethod
    
    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 2, false, function thistype.runTest)
    endmethod

    implement ItemIndexModule
endstruct
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
An Item Indexer should be able to catch all indexed/deindexed items like a unit indexer does (the instant the event occurs).

It should not use item in rect enumerations (doing so incurs overhead + doesn't occur at exactly the same moment the event occurs).

It should not use a cleanup timer (same issue as above).


For example, when an item is sold, that event can be captured. When a hero picks up an item, that event can be captured. When an item is dropped by a monster? :\.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
I can create ItemDropSystem and ItemPool for this Indexer in order to fix monster drop problem.

Nes, there are far greater problems in my DestDex. DeathEvent fires: a) widget dies b) widget is removed.
Destructables without being forced by RemoveDestructable just don't "die". Even freaking dests like igloo with normal destroy animation, disapprears but only theoretically, practically they never do. CreateDeadDest function is used indead by some hardcoded system within wc3.

Thats why if you want to index dead destructables (what you should do) you either hook RemoveDestructable or provide ur own ;/

Additionaly there is room for DestCleanUp ^^
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Hello, below is updated ItemIndexer library which uses Vex's GetItemIndex() approach and features event clean up when given threshold is reached. Take a look, and post your opinions - thoughts regarding "monster drop" are posted in "Resources That Have Yet To Be Coded". Still provides "NewItem" function as a replacement for CreateItem, yet one doesn't have to replace anything within his code.
I guess hooking GetItemUserData is not an option, thus you need to use GetItemIndex or NewItem before GetItemUserData or such item won't give you a proper data index.
WorldBounds and RPUE can be made optional, Table too, though we still need hashtable to retrieve item from widget (there is no direct conversion).
JASS:
library ItemIndexer uses Event RegisterPlayerUnitEvent Table WorldBounds RestoreTrigger

    globals
        private Trigger trig
        private integer eventIndex = 0
        private item lastCreatedItem = null
        private item array itemArray
        private constant integer TRIG_RESTORE_THRESHOLD = 50
        private integer emptyEvents = 0
    endglobals

    // function API
    function NewItem takes integer itemid, real x, real y returns item
        set lastCreatedItem = CreateItem(itemid, x, y)
        call ItemIndexer.indexItem(lastCreatedItem)
        return lastCreatedItem
    endfunction

    function LastCreatedItem takes nothing returns item
        return lastCreatedItem
    endfunction

    function GetIndexedItemId takes nothing returns integer
        return eventIndex
    endfunction

    function GetIndexedItem takes nothing returns item
        return itemArray[eventIndex]
    endfunction

    function IsItemIndexed takes item it returns boolean
        return ( it == itemArray[GetItemUserData(it)] )
    endfunction

    function GetItemIndex takes item it returns integer
        if not ( IsItemIndexed(it) ) then
           call ItemIndexer.indexItem(it)
        endif
        return GetItemUserData(it)
    endfunction

    function GetItemByIndex takes integer index returns item
        return itemArray[index]
    endfunction

    function RegisterItemIndexEvent takes code c, Event ev returns nothing
        call ev.register(Condition(c))
    endfunction

    function TriggerRegisterItemIndexEvent takes trigger t, Event ev returns nothing
        call ev.registerTrigger(t)
    endfunction

    // module API
    module ItemIndexModule
        static method operator [] takes item it returns thistype
            return GetItemUserData(it)
        endmethod

        method operator item takes nothing returns item
            return itemArray[this]
        endmethod

        static if thistype.filterItem.exists then
            static if thistype.onIndex.exists then

                static if thistype.onDeindex.exists then
                    readonly boolean allocated
                else
                    method operator allocated takes nothing returns boolean
                        return filterItem(item)
                    endmethod
                endif
            else
                method operator allocated takes nothing returns boolean
                    return filterItem(item)
                endmethod
            endif

        elseif ( thistype.onIndex.exists ) then

            static if thistype.onDeindex.exists then
                readonly boolean allocated
            else
                method operator allocated takes nothing returns boolean
                    return ( this == GetItemUserData(item) )
                endmethod
            endif
        else
            method operator allocated takes nothing returns boolean
                return ( this == GetItemUserData(item) )
            endmethod
        endif

        static if thistype.onIndex.exists then

            private static method onIndexEvent takes nothing returns boolean
                static if thistype.filterItem.exists then

                    if ( filterItem( itemArray[eventIndex] ) ) then
                        static if thistype.onDeindex.exists then
                            set thistype(eventIndex).allocated=true
                        endif
                        call thistype(eventIndex).onIndex()
                    endif
                else
                    static if thistype.onDeindex.exists then
                        set thistype(eventIndex).allocated=true
                    endif
                    call thistype(eventIndex).onIndex()
                endif

                return false
            endmethod
        endif

        static if thistype.onDeindex.exists then

            private static method onDeindexEvent takes nothing returns boolean
                static if thistype.filterItem.exists then

                    static if thistype.onIndex.exists then
                        if ( thistype(eventIndex).allocated ) then
                            set thistype(eventIndex).allocated=false
                            call thistype(eventIndex).onDeindex()
                        endif
                    else
                        if ( filterItem( itemArray[eventIndex] ) ) then
                            call thistype(eventIndex).onDeindex()
                        endif
                    endif
                else
                    static if thistype.onIndex.exists then
                        set thistype(eventIndex).allocated=false
                    endif
                    call thistype(eventIndex).onDeindex()
                endif

                return false
            endmethod
        endif

        static if thistype.onIndex.exists then

            static if thistype.onDeindex.exists then

                private static method onInit takes nothing returns nothing
                    call RegisterItemIndexEvent(function thistype.onIndexEvent, ItemIndexer.INDEX)
                    call RegisterItemIndexEvent(function thistype.onDeindexEvent, ItemIndexer.DEINDEX)
                endmethod
            else
                private static method onInit takes nothing returns nothing
                    call RegisterItemIndexEvent(function thistype.onIndexEvent, ItemIndexer.INDEX)
                endmethod
            endif

        elseif thistype.onDeindex.exists then

            private static method onInit takes nothing returns nothing
                call RegisterItemIndexEvent(function thistype.onDeindexEvent, ItemIndexer.DEINDEX)
            endmethod
        endif
    endmodule

    // ItemIndexer initializer
    private module ItemIndexerInit
        private static method onInit takes nothing returns nothing
            local group g = CreateGroup()
            set trig = Trigger.create()
            set INDEX = CreateEvent()
            set DEINDEX = CreateEvent()
            set itemTable = Table.create()

            call EnumItemsInRect(WorldBounds.world, Condition(function thistype.indexPreplaced), null)
            call GroupEnumUnitsInRect(g, WorldBounds.world, Condition(function thistype.indexInventory))
            call trig.addCondition(function thistype.deindexLeaving)

            call TimerStart(CreateTimer(), 0, false, function thistype.refireIndexEvent)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PICKUP_ITEM, function thistype.indexManipulated)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SELL_ITEM, function thistype.indexManipulated)
            call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_PAWN_ITEM, function thistype.deindexSold)

            call DestroyGroup(g)
            set g = null
        endmethod
    endmodule

    // instance related methods
    private module ItemIndexerModule
        static method allocate takes nothing returns thistype
            local thistype this
            if ( 0 == recycle ) then
                set idCount = idCount+1
                set this = idCount
            else
                set this = recycle
                set recycle = recycle.next
            endif

            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set this.next = 0
            set thistype(0).prev = this

            return this
        endmethod

        method deallocate takes nothing returns nothing
            set this.prev.next = this.next
            set this.next.prev = this.prev
            if ( this.refCount == 0 ) then
                set this.next = recycle
                set recycle = this
            endif
        endmethod

        method incRef takes nothing returns nothing
            if (itemArray[this] != null) then
                set this.refCount = this.refCount+1
            endif
        endmethod

        method decRef takes nothing returns nothing
            if ( this.refCount > 0 ) then
                set this.refCount = this.refCount - 1
                if ( this.refCount == 0 and null == itemArray[this] ) then
                    set this.next = recycle
                    set recycle = this
                endif
            endif
        endmethod
    endmodule

    struct ItemIndexer extends array
        readonly static Event INDEX
        readonly static Event DEINDEX
        private static Table itemTable
        static boolean enabled = true

        private static integer idCount = 0
        private static thistype recycle = 0
        private thistype next
        private thistype prev
        private integer refCount

        implement ItemIndexerModule

        private static method refireIndexEvent takes nothing returns nothing
            local thistype this = thistype(0).next
            local integer prevIndex = eventIndex

            loop
                exitwhen 0==this
                set eventIndex = this
                call INDEX.fire()
                set this = this.next
            endloop

            set eventIndex = prevIndex
            call DestroyTimer(GetExpiredTimer())
        endmethod

        static method indexItem takes item it returns boolean
            local thistype this
            local integer prevIndex = eventIndex

            if ( enabled and it != itemArray[GetItemUserData(it)] ) then
                set this = thistype.allocate()
                call SetItemUserData(it, this)
                set itemArray[this] = it
                set itemTable.item[GetHandleId(it)] = it
                call TriggerRegisterDeathEvent(trig.trigger, it)

                set eventIndex = this
                call FireEvent(INDEX)
                set eventIndex = prevIndex
            endif
            return false
        endmethod

        // index helper methods
        private static method indexPreplaced takes nothing returns boolean
            return indexItem(GetFilterItem())
        endmethod

        private static method indexManipulated takes nothing returns boolean
            return indexItem(GetManipulatedItem())
        endmethod

        private static method indexInventory takes nothing returns boolean
            local unit u = GetFilterUnit()
            local integer slot = 0
            local integer last = UnitInventorySize(u)
            loop
                exitwhen slot == last
                if ( UnitItemInSlot(u, slot) != null ) then
                    call indexItem(UnitItemInSlot(u, slot))
                endif
                set slot = slot+1
            endloop
            set u = null
            return false
        endmethod

        private static method deindexItem takes item it returns boolean
            local thistype this = GetItemUserData(it)
            local integer save = eventIndex

            if ( it == itemArray[this] ) then
                set eventIndex = this
                call FireEvent(DEINDEX)
                set eventIndex = save

                call this.deallocate()
                set itemArray[this] = null
                set itemTable.item[GetHandleId(it)] = null

                set emptyEvents = emptyEvents + 1
                if ( emptyEvents >= TRIG_RESTORE_THRESHOLD ) then
                    call trig.restore()
                    set this = thistype(0).next
                    loop
                        exitwhen 0==this
                        call TriggerRegisterDeathEvent(trig.trigger, itemArray[this])
                        set this = this.next
                    endloop
                    set emptyEvents = 0
                endif
            endif
            return false
        endmethod

        // deindex helper methods
        private static method deindexLeaving takes nothing returns boolean
            return deindexItem(itemTable.item[GetHandleId(GetTriggerWidget())])
        endmethod

        private static method deindexSold takes nothing returns boolean
            return deindexItem(GetSoldItem())
        endmethod

        implement ItemIndexerInit
    endstruct

endlibrary
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
No comments..
Updated to 2.0.0.0 anyways. No need for any requirement. Code has been reorganized. No additional methods are generated via jasshelper. Added demo.
Should I replace lastCreatedItem with bj_lastCreatedItem? I guess using the bj might be more intuitive. Once again, awaiting your feedback.
 
I'll give a proper review hopefully some time this week. :)

But for starters, I'm not sure why you need to index items that are picked up or bought. Only index what needs to be indexed. i.e.:
JASS:
function GetItemIndex takes item i returns integer
    if not IsItemIndexed(i) then
        return IndexItem(i)
    endif
    return GetItemUserData(i)
endfunction

// ... rest of system concerns with deindexing and other misc. features
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
At first, thanks for replay.

@Purge: the problem is, that there are no commets anywhere so I don't really know what users expect. When I was recently updating some of my stuff which I've decided to upload from my script collection I've been even posting down code which clearly was not elegant and such (not nulling handles; unappropriate behaviour, bugs etc) just to confirm that truely, none reads those. Big lol.

Anyways, this code features two approaches:
1) tries to auto-index everything possible
2) Index the rest via GetItemIndex

The only thing it can not catch are "monster drops" (issue described after multiple tests in "Things that yet to be coded") and CreateItem with its BJs. Hook solution may be an option, and even somewhere was a thread (Standard smth or Moving forward) were hooks were said to be allowed when there is basically no other way around. Currently, I've resigned from those and instead this uses Vex's method + provides additional function in form of "NewItem" to index created item immidiately.
 
Anyways, this code features two approaches:
1) tries to auto-index everything possible
2) Index the rest via GetItemIndex

The only thing it can not catch are "monster drops" (issue described after multiple tests in "Things that yet to be coded") and CreateItem with its BJs. Hook solution may be an option, and even somewhere was a thread (Standard smth or Moving forward) were hooks were said to be allowed when there is basically no other way around. Currently, I've resigned from those and instead this uses Vex's method + provides additional function in form of "NewItem" to index created item immidiately.

Yes, but what is the advantage of indexing everything possible? It just seems like unnecessary work, unless the user plans on (1) registering when an item is indexed [which tbh, is a glorified "on enter event". And if you can't catch them all, it is pointless to have] (2) iterating through items that are indexed [which doesn't make a whole lot of sense, especially if not all items are represented anyway.

IMO, just keep it simple and use GetItemIndex. It is kinda like selling cheeseburgers. On one hand, you could make 100 cheeseburgers for the day and just hand them out when people buy one. On the other hand, you can just make it when someone asks for it. That way, if it the apocalypse happens, you wouldn't have wasted any effort on making unnecessary cheeseburgers.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Seems fine, will delete the indexPreplaced, indexManipulated and indexInventory methods and code related to them. This is getting super light ^)^

Btw, I don't eat cheeseburgers, basically I avoid fastfoods as much as possible. I know that it was just an example, though does not hurt to tell :D
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
@Purge, however, this creates situation where event INDEX seems pointless.. I know that it wasn't accurate anyways, yet if one was using NewItem instead of CreateItem with kind of item pool (or just simple triggered drop) then issue was fixed ;>
Current code keeps INDEX purpose somehow.

Should INDEX stay as it is (will fire when user calls GetItemIndex for the first time for given item)? For me in such case it has already lost it's meaning. I'd probably stick with DEINDEX only.
 
Spinnaker said:
Seems fine, will delete the indexPreplaced, indexManipulated and indexInventory methods and code related to them. This is getting super light ^)^

That is good. I don't think an item indexer has to be super duper featured. Most of us just want the functionality to use them easily in arrays. Cover the bare minimum for now, and then you can extend the functionality with extensions/other libraries if you'd like.

@Purge, however, this creates situation where event INDEX seems pointless.. I know that it wasn't accurate anyways, yet if one was using NewItem instead of CreateItem with kind of item pool (or just simple triggered drop) then issue was fixed ;>
Current code keeps INDEX purpose somehow.

Should INDEX stay as it is (will fire when user calls GetItemIndex for the first time for given item)? For me in such case it has already lost it's meaning. I'd probably stick with DEINDEX only.

In my opinion, just remove the index event. I can't really think of reason why someone would want to check when an item is indexed. Usually for units, it is just so that we don't have to do the whole--GroupEnumUnitsInRect + TriggerRegisterEnterRegion() combo (i.e. it is a glorified "OnEnter" event), and then you can assign data to the unit--but in this scenario, the user has full control over when and if an item is indexed, so they know exactly when something is indexed (i.e. they don't need an event for it).

P.S. Feel free to disagree with my opinions. If you liked it the way it was, you can always revert it. I'm just thinking from the perspective of when I needed an item indexer--I just needed indexing and deindexing.

Spinnaker said:
Btw, I don't eat cheeseburgers, basically I avoid fastfoods as much as possible. I know that it was just an example, though does not hurt to tell :D

You make me feel unhealthy. :( I ate out like 3 times this week, lol. I really need to buy some groceries but it is difficult when you have a lot of studying to do. Oh well. Kudos to your resilience. I eat out much less often than the average Joe, but I can never say no to a cheeseburger. :)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I seriously fail to see when you would ever need an item indexer.

What could you possibly want to 'attach' to an item?
The only practical thing that comes to my mind is a is-bound-to-unit-state for save/load systems. But in that case you can just add the index of the player/unit as item user data.
Maybe the number of charges of an item in case you use a powerup instead of chargeable to allow picking it up even when the inventory is full? In that case a dedicated system makes more sense imho.
Hmm... maybe randomized stats? But those wouldn't be represented on the tooltip anyway, so it only makes sense on full screen inventories - and then, again, I would probably use a specialized system for that.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
If you are so "specific system" oriented, then probably no UnitIndexer was ever needed since scripts using it could be specialized >.>

You have listed examples I just wanted to list after reading the very 1st sentence of your replay. Truely, if II were in interest of all, or kinda hot topic, I guess the case would be different. However, its really not that important. Ppl used to handle item-related subjects themselves without need of exaternal snippet. One can not claim tho, that getting user data from an item isnt useful afterall. It had its applications, and it always will.

Sorry for lacks of updates guys. Kinda of bussy like most of you.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
Actually i agree with Zwiebel, however there are some maps where items are used in a way that is far away from their originally intended usage. Those maps are rare but i guess its ok to make systems for rare cases (most of the stuff that has been submitted lately will probably never be used).
 
In general, it can make item/inventory systems much "cleaner". You can map items directly to struct instances that contain its data (e.g. for a full screen system, you may contain the x-y position or slot, or the destructable that is displayed).

Apart from that, it could always be used in a bag system (save the slot/charges), which are quite common.

True, you can just use a hashtable, and that applies to unit indexers as well. This allows arrays and convenient syntax. It is mostly just syntactic sugar for the coder, with perhaps a marginal insignificant speed gain (although, I'm not quite sure about this. It is probably about the same).

EDIT: But idk, it is 4:34 A.M. so maybe I'm delusional. But I still see some uses for it. Compared to some other resources that were approved in the past, it is incredibly awesome that I can actually think of a use for a snippet/system.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
EDIT: But idk, it is 4:34 A.M. so maybe I'm delusional. But I still see some uses for it. Compared to some other resources that were approved in the past, it is incredibly awesome that I can actually think of a use for a snippet/system.
I agree. An Item Indexer HAS a practical uses, despite not even close as many as a unit indexer (and to be perfectly honest, I don't even use unit indexers anymore, as most of the time hashtables are more convenient due to the flushing functions). Which is refreshing compared to 90% of the submissions here. ;)

The issue I have with both types of indexers is, that I consider it "dirty coding". When comparing the readability of a code, I feel that hashtables indexed by HandleIDs are just the more "natural" approach, as the handle ID of a unit is perfectly deterministic and directly linked to the game engine. The handle ID has a real internal meaning.
On the contrary, a unit index is just an arbitrary number.
Depending on the scale and impact of map related system, one single call of SetUnitUserData can completely break the game. And let's not even get started about how much dirty programming the abuse of undefend is...
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Spinnaker said:
@Purge: the problem is, that there are no commets anywhere so I don't really know what users expect. When I was recently updating some of my stuff which I've decided to upload from my script collection I've been even posting down code which clearly was not elegant and such (not nulling handles; unappropriate behaviour, bugs etc) just to confirm that truely, none reads those. Big lol.

Really, care to share what you (have) really use(d) if you want, but don't expect people's comments, it's an "old" game after all.
Frankly, the first reaction i had when i saw all these resources popped up was :
Well, Nestharus has left but someone else is taking the place (post a resource just because you can)

Please don't take it wrong, i'm not bitching you, just trying to write my feelings about that. (not this particular resource, after all in some special cases an unit indexer could be used, as it was written)
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Thanks Troll-Brain, yeah Ive probably forgotten during those last 2 years that I should avoid submitting anything; just keep everything for myself and myself only, like bufons do.

I've done this "little experiment" mostly for fun; yet after releasing that multiple scripts with bugs are not commented for several days, it was quite embarrassing. Anyways, glad there are replies afterall.

And btw, Im not Nestharus, and I do not want to repeat any mistakes.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Every item which has to be indexed, has to created via NewItem()! ( bad )
So what is about items created by PlaceRandomItem(),
which is a convinied native when writing an ItemDrop system.

As decription, an item index system requires a detailed disclaimer so users
understand what to expect and what not.

Usage for an Item Indexer is very limited and won't be seen in too many public resources.
No big deal, as this is no criteria for a submission here.
I also use SetItemUserData from time to time, but just to transfer random data like i.e
gold amount, when gold coins are picked up.

I didn't check Vexorians ItemIndexer, but could you point out
pros and cons of yours vs his
difference between the two

Please do not use "we don't have one submitted here" as core argument, because we
also don't have a TimerUtils, GroupUtils, IsTerrainWalkable, ... library.
It's valid a point in your argumentation, but not a strong one.

In my opinion, ItemUserData as struct allocator is just syntax sugar, which doesn't play
an as big role as UnitUserData.
If we could manipulate object tooltips ingame ItemUserData could be very important.
I might miss the meaning of ItemUserData and custom inventory systems.
Tooltips generated here could be based on ItemUserData.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
This resource should be simplified to this:

JASS:
library GetItemIndex requires Alloc

    globals
        private constant integer RECYCLE_AT = 32 //every RECYCLE_AT indices,
        //loop through all indexed items to check for ones which were removed
    endglobals

    private struct Data extends array
        implement Alloc
    endstruct
    
    globals
        private item array items
        private Data array stack
        private integer size = 0
    endglobals
    
    function IsItemIndexed takes item it returns boolean
        return items[GetItemUserData(it)] == it
    endfunction

    function GetItemByIndex takes integer id returns item
        return items[id]
    endfunction
    
    private function Purge takes nothing returns nothing
        local integer i = size
        loop
            set i = i - 1
            exitwhen i == 0
            if GetItemTypeId(items[stack[i]]) == 0 then
                call stack[i].deallocate()
                set stack[i] = stack[size]
                set size = size - 1
            endif
        endloop
    endfunction

    function GetItemIndex takes item it returns integer
        local integer i = GetItemUserData(dat)
        if i == 0 and GetItemTypeId(it) != 0 then //avoid indexing invalid/removed items
            set i = Data.allocate()
            set items[i] = it
            set size = size + 1
            set stack[size] = i //avoid stack index 0 for IsItemIndexed check
            if ModuloInteger(size, RECYCLE_AT) == 0 then
                call ForForce(bj_FORCE_PLAYER[0], function Purge) //avoid op limit risks
            endif
        endif
        return i
    endfunction
    
endlibrary

No events, no auto-indexing nor deindexing, just indexing on-demand.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Since there is no way to detect the handle ID of an item that was just created and would have to hook every single item-getting function in the game even to start a 0 second timer for the resource (which wouldn't work if you need the ID of the item the instant it is created) and this resource doesn't hook the RemoveItem functions, I think it is better to simply make this a GetItemIndex function as I have illustrated above.

Graveyarding.
 
Top