System PII - Perfect Item Indexing

Cohadar

master of fugue
Reaction score
209
JASS:

//==============================================================================
//  PII -- Perfect Item Indexing by Cohadar -- v1.1
//==============================================================================
//
//  PURPOUSE:
//       * Extending ItemUserData()
//       * This is basically perfect hashing algorithm for items
//
//  HOW TO USE:
//       * You have only one function at your disposal GetItemIndex(item)
//         It will return a unique index for each item in range 1..8190
//
//       * What you will do with that index is all up to you
//         Of course using global arrays is the most obvious choice
//         Advanced jassers will think of a couple of more clever ones ofc.
//
//       * There are also 2 textmacros available at the end of library code
//         They can be used for easier attaching to items
//         PII for structs 
//         PII_PROPERTY for unit, integer, real, boolean and string variables
//
//  PROS: 
//       * You can use any number of systems that previously could not work together
//         because they all required exclusive access of ItemUserData()
//
//       * You can also use this to add bonus mod data structs to items
//
//       * System removes dead items after they decay
//         (unbelievably but this is not done automatically by the game)
//
//       * There are no SetItemIndex() or ClearItemIndex() functions here
//         Each item gets assigned one index that cannot be changed
//         That index will be automatically recycled some time after item death.
//
//  CONS:
//       * This system uses ItemUserData() itself
//         That means that if you want to use PII you must recode 
//         any other system that uses ItemUserData() to use GetItemIndex() instead
//
//       * If you use ItemIndex for arrays of non-native types (timers, effects and similar)
//         you must check if timer on that index already exists before you create a new one.
//         This can happen if GetItemIndex() assigns a recycled index (index of some dead and removed item)
//         to the newly created item for which you intended to use timer for
//
//       * All in all this is not a sys for newbies, it gives great power,
//         but it requires knowledge and carefull handling
//
//  DETAILS:
//       * System is using item array to keep track of all items with an index.
//         Array is periodically checked for dead items, when dead item is found, index is recycled.
//
//       * Indexes have "decay time" to prevent bugs from using RemoveItem() function
//         or from selling items to shop (game instantly removes sold items)
//         When item decays it will be removed by PII in case it was not removed by game!
//
//       * PII automatically registers any picked item, this ensures
//         all runes, tomes and charged items are properly removed from the game
//
//  SYSTEM COMMANDS: (debug mode only, red player only)
//
//       * type -pii to display indexes of items in inventory of currently selected unit
//       * type -piistats to display some stats
//
//  THANKS TO:
//       * Vexorian - for his help with PUI textmacro
//
//  HOW TO IMPORT:
//       * Just create a trigger named PII
//       * convert it to text and replace the whole trigger text with this one
//
//==============================================================================


library PII initializer Init

//==============================================================================
globals    
    //-----------------------------------------------
    private constant real INDEX_DECAY_TIME = 5.  // seconds
    
    //-----------------------------------------------    
    private constant real PERIOD = 1.0  // check one item every second
        
    //-----------------------------------------------
    private constant integer DECAY_TICKS = R2I(INDEX_DECAY_TIME/PERIOD)
    
    //-----------------------------------------------
    private integer array Indexz
    private item    array Itemz
    private integer array Decayz
    private integer array Tickz

    private integer maxindex = 0
    private integer topindex = 0
    private integer decayindex = 0
    private integer checker  = 0
    private integer decayer  = 0
    private integer tick = 0
endglobals


//==============================================================================
private function PeriodicRecycler takes nothing returns boolean
    local integer temp
    
    set tick = tick + 1
    
    if topindex > decayindex then
        set checker = checker + 1
        if checker > topindex then
            set checker = decayindex + 1
        endif
        if (GetWidgetLife(Itemz[checker])<0.405) then 
            set decayindex = decayindex + 1
            
            // Itemz[0] is temp, index 0 is never used by system
            set Itemz[0] = Itemz[checker]      
            set Itemz[checker] = Itemz[decayindex]
            set Itemz[decayindex] = Itemz[0]   
            
            // swap(checker, decayindex)
            set temp = Indexz[checker]
            set Indexz[checker] = Indexz[decayindex]
            set Indexz[decayindex] = temp
            
            set Decayz[decayindex] = DECAY_TICKS
            set Tickz[decayindex] = tick
        endif
    endif

    if decayindex > 0 then
        set decayer = decayer + 1
        if decayer > decayindex then
            set decayer = 1
        endif
        set Decayz[decayer] = Decayz[decayer] - (tick-Tickz[decayer])
        if Decayz[decayer] > 0 then
            set Tickz[decayer] = tick
        else
            // swap(decayer, decayindex)
            set temp = Indexz[decayer]
            set Indexz[decayer] = Indexz[decayindex]
            set Indexz[decayindex] = temp
            
            // if item is not removed by the game after it decays, we do it.
            if GetItemUserData(Itemz[decayindex]) != 0 then
                //debug call BJDebugMsg("PII_Removing: "+GetItemName(Itemz[decayindex]))
                call RemoveItem(Itemz[decayindex])
            endif
            
            set Itemz[decayindex] = Itemz[topindex]
            
            // swap(decayindex, topindex)
            set temp = Indexz[decayindex]
            set Indexz[decayindex] = Indexz[topindex]
            set Indexz[topindex] = temp
            
            set decayindex = decayindex - 1
            set topindex = topindex - 1
        endif    
    endif
    
    return false
endfunction

//==============================================================================
//  Main and only function exported by this library
//==============================================================================
function GetItemIndex takes item whichItem returns integer
    local integer index
    
    debug if whichItem == null then
    debug   call BJDebugMsg("|c00FF0000ERROR: PII - Index requested for null item")
    debug   return 0
    debug endif
    
    set index = GetItemUserData(whichItem)

    if index == 0 then
        set topindex = topindex + 1
        if topindex > maxindex then
            set maxindex = topindex
            set Indexz[topindex] = topindex
        endif
        set index = Indexz[topindex]
        set Itemz[topindex] = whichItem
       
        call SetItemUserData(whichItem, index)
        set index = GetItemUserData(whichItem)
       
        // this happens when requesting item index for removed item
        debug if index == 0 then
        debug     call BJDebugMsg("|c00FFCC00WARNING: PII - Bad item handle")
        debug endif
        
        //debug call BJDebugMsg("|c00FFCC00PII: Index assigned #" + I2S(index))
    endif
    
    return index
endfunction

//==============================================================================
//  Registers all picked items, this ensures runes tomes and charged items
//  are properly removed after they decay
//==============================================================================
private function RegisterConditions takes nothing returns boolean
    call GetItemIndex(GetManipulatedItem())
    return false
endfunction

//==============================================================================
private function DisplayStats takes nothing returns nothing
    call BJDebugMsg("=============================================")    
    call BJDebugMsg("Biggest index ever = " + I2S(maxindex))    
    call BJDebugMsg("Indexes in use = " + I2S(topindex-decayindex))
    call BJDebugMsg("Decaying indexes = " + I2S(decayindex))
    call BJDebugMsg("Released indexes = " + I2S(maxindex-topindex))
    call BJDebugMsg("=============================================")    
endfunction

//===========================================================================
private function DisplaySelectedEnum takes nothing returns nothing
    local integer i
    local item m
    call BJDebugMsg("Unit: "+GetUnitName(GetEnumUnit()))
    
    set i = 0
    loop
        exitwhen i>=6
        set m = UnitItemInSlot(GetEnumUnit(), i)
        if m != null then
            call BJDebugMsg( "PII(" + ( GetItemName(m) + ( ") = " + I2S(GetItemUserData(m)) ) ) )
        endif
        set i = i + 1
    endloop
endfunction

//===========================================================================
private function DisplaySelected takes nothing returns nothing
    local group g = CreateGroup()
    call SyncSelections()
    call GroupEnumUnitsSelected(g, Player(0), null)
    call ForGroup(g, function DisplaySelectedEnum)
    call DestroyGroup(g)
    set  g = null
endfunction

//==============================================================================
private function Init takes nothing returns nothing
    local trigger trig 
    
    set trig = CreateTrigger()
    call TriggerRegisterTimerEvent( trig, PERIOD, true )
    call TriggerAddCondition( trig, Condition(function PeriodicRecycler) )
    
    set trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_PICKUP_ITEM )
    call TriggerAddCondition( trig, Condition( function RegisterConditions ) )    

    debug set trig = CreateTrigger()
    debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-pii", true )
    debug call TriggerAddAction( trig, function DisplaySelected )      
    
    debug set trig = CreateTrigger()
    debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-piistats", true )
    debug call TriggerAddAction( trig, function DisplayStats )
endfunction

endlibrary


//===========================================================================
//  Allowed PII_PROPERTY TYPES are: item, integer, real, boolean, string
//  Do NOT put handles that need to be destroyed here (timer, trigger, ...)
//  Instead put them in a struct and use PII textmacro
//===========================================================================
//! textmacro PII_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
    private static item   array pii_item
    private static $TYPE$ array pii_data
    
    //-----------------------------------------------------------------------
    //  Returns default value when first time used
    //-----------------------------------------------------------------------
    static method operator[] takes item whichItem returns $TYPE$
        local integer pii = GetItemIndex(whichItem)
        if .pii_item[pii] != whichItem then
            set .pii_item[pii] = whichItem
            set .pii_data[pii] = $DEFAULT$
        endif
        return .pii_data[pii]
    endmethod
    
    //-----------------------------------------------------------------------
    static method operator[]= takes item whichItem, $TYPE$ whichData returns nothing
        local integer pii = GetItemIndex(whichItem)
        set .pii_item[pii] = whichItem
        set .pii_data[pii] = whichData
    endmethod
endstruct
//! endtextmacro

//===========================================================================
//  Never destroy PII structs directly.
//  Use .release() instead, will call .destroy()
//===========================================================================
//! textmacro PII
    private static item    array pii_item
    private static integer array pii_data
    private static integer array pii_id
    
    //-----------------------------------------------------------------------
    //  Returns zero if no struct is attached to item
    //-----------------------------------------------------------------------
    static method operator[] takes item whichItem returns integer
        local integer pii = GetItemIndex(whichItem)
        if .pii_data[pii] != 0 then
            if .pii_item[pii] != whichItem then
                // recycled handle detected
                call .destroy(.pii_data[pii])
                set .pii_item[pii] = null
                set .pii_data[pii] = 0            
            endif
        endif
        return .pii_data[pii]
    endmethod
    
    //-----------------------------------------------------------------------
    //  This will overwrite already attached struct if any
    //-----------------------------------------------------------------------
    static method operator[]= takes item whichItem, integer whichData returns nothing
        local integer pii = GetItemIndex(whichItem)
        if .pii_data[pii] != 0 then
            call .destroy(.pii_data[pii])
        endif
        set .pii_item[pii] = whichItem
        set .pii_data[pii] = whichData
        set .pii_id[whichData] = pii
    endmethod

    //-----------------------------------------------------------------------
    //  If you do not call release struct will be destroyed when item handle gets recycled
    //-----------------------------------------------------------------------
    method release takes nothing returns nothing
        local integer pii= .pii_id[integer(this)]
        call .destroy()
        set .pii_item[pii] = null
        set .pii_data[pii] = 0
    endmethod
//! endtextmacro
 

Attachments

  • PII 1.1.w3x
    30.1 KB · Views: 255

Jesus4Lyf

Good Idea™
Reaction score
397
Hmmm.
JASS:
    local integer index
    
    debug if whichItem == null then
    debug   call BJDebugMsg("|c00FF0000ERROR: PII - Index requested for null item")
    debug   return 0
    debug endif
    
    set index = GetItemUserData(whichItem)

-->
JASS:
    local integer index = GetItemUserData(whichItem)
    
    debug if whichItem == null then
    debug   call BJDebugMsg("|c00FF0000ERROR: PII - Index requested for null item")
    debug   return 0
    debug endif

?
 

saw792

Is known to say things. That is all.
Reaction score
280
// THANKS TO:
// * Vexorian - for his help with PUI textmacro

PUI -> PII

Missed that one in your copy/paste I guess.
 

Jesus4Lyf

Good Idea™
Reaction score
397

Jesus4Lyf

Good Idea™
Reaction score
397
*Facepalms*
Your brevity can be frustrating for people who want to understand why you do things, like myself.
JASS:
private function PeriodicRecycler takes nothing returns nothing
    local integer temp
    
    set tick = tick + 1
    
    if topindex > decayindex then
        ...
    endif
endfunction
...
private function Init takes nothing returns nothing
    call TimerStart(CreateTimer(),PERIOD,true,function PeriodicRecycler)
...

So how doesn't using a timer work?

And is there any particular reason you don't inline your first "set var="s into their declaration?
 

Romek

Super Moderator
Reaction score
963
Pii reminds me of Wii.

Still using a textmacro, not a module? :(
 

Cohadar

master of fugue
Reaction score
209
Pii reminds me of Wii.

Still using a textmacro, not a module? :(
You can use modules only for structs, but not for properties.
So I decided to be consistent and use macros all around.
 

Romek

Super Moderator
Reaction score
963
Modules are much nicer to use though.
How about making the macro a module, and adding this:
JASS:
//! textmacro PII
implement PII
//! endtextmacro
 

Builder Bob

Live free or don't
Reaction score
249
Thumbs up for this :)

I've been using (a very similar) PII for a long time now, and can say that it works just as perfectly as PUI.
 

Cohadar

master of fugue
Reaction score
209
I did not get what you were asking the first time.
What are you asking me exactly?

EDIT:
Ah I think I get it now, it is a misunderstanding
your first question actually was: (hate implicit questions)
is using periodic trigger + condition more efficient that simply using a timer?
And the answer is:
I have no IDEA.
 

Jesus4Lyf

Good Idea™
Reaction score
397
*Shrugs*

In answer to that question (just benchmarked it):

I got...
Timers: 46,400 execs/sec
Triggers: 43,800 execs/sec

Timers are 6% faster. (Tested 100 instances on 0.0 period for 10 seconds.)

I'd suggest you switch your trigger with a periodic event to a timer. Less lines of code and free efficiency! :)
 

Cohadar

master of fugue
Reaction score
209
It is a leftover from the old days. (I made PUI long time ago)

And your benchmark is wrong, (or rather to say within the margin of error)
Two methods have exactly the same performance.
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Staff online

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top