System PII - Perfect Item Indexing

Cohadar

master of fugue
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

Jesus4Lyf

Good Idea™
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.
// THANKS TO:
// * Vexorian - for his help with PUI textmacro

PUI -> PII

Missed that one in your copy/paste I guess.
 

Jesus4Lyf

Good Idea™

Jesus4Lyf

Good Idea™
*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
Staff member
Pii reminds me of Wii.

Still using a textmacro, not a module? :(
 

Romek

Super Moderator
Staff member
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
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
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.
 

Cohadar

master of fugue
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 The Helper:
    seeing some weird stuff doing some moves to the new Weird News forum please see the bathroom admins
  • The Helper The Helper:
    :) it is weird
  • The Helper The Helper:
    btw it was just lag the weirdness is gone
  • Wizard Wizard:
    I checked!
    +1
  • Blackveiled Blackveiled:
    How is everyone doing?
  • Wizard Wizard:
    Alright!
  • The Helper The Helper:
    Doing well thanks!
  • The Helper The Helper:
    Tom Mai check out the post I just found you made in 2006, from an apparent banned account, I do not remember that happening, the post is in the General Discussion Hall of Fame about record breakers
  • Blackveiled Blackveiled:
    Lmao thats somethin right there.
  • tom_mai78101 tom_mai78101:
    How far back were you looking at?
  • tom_mai78101 tom_mai78101:
    But yes, that was me.
  • The Helper The Helper:
    Do you think I am looking back in the forums for this stuff? LOL, no, I find these posts in the Members Online section or the stats - all these old posts I am bumping and moving around are what people are actually looking at on the website
  • tom_mai78101 tom_mai78101:
    Interesting.
  • The Helper The Helper:
    hey Tom your Burrito news is going viral by TheHelper.net standards LOL
  • The Helper The Helper:
    looks like The Burrito has been shared to facebook lol I am getting the facebook bots in now
  • The Helper The Helper:
    I am going to do one more News Forum like the Weird News but this one is Science News for all the cool science and technology articles in the Archive
  • tom_mai78101 tom_mai78101:
    Hmm, do I need to start posting some of the Science and Technology news to this new section in the future?
  • tom_mai78101 tom_mai78101:
    Or do we need to move the news to their respective sections once the threads are old enough, read to be put into the News Archive?
  • The Helper The Helper:
    The 2nd thing
  • The Helper The Helper:
    I have been moving stuff from the current Headline news like a year old and stuff over when I see it in the logs or members online but mostly I have been moving stuff from the Archives
  • The Helper The Helper:
    It is possible to automate it so that instead of moving to Archive if it has the Science, technology or sci tech tag that it gets archived there instead. I am considering a new Weird News Tag
  • tom_mai78101 tom_mai78101:
    Please do. I'll try to tag the news appropriately when I get the chance.
  • The Helper The Helper:
    The new Discourse forum software we are moving to next does alot with the tags and also hashtags
  • The Helper The Helper:
    OK I found the source of your burrito post stuff it is from Reddit and possibly a facebook post too but definitely reddit

    Staff online

    Members online

    Affiliates

    Hive Workshop NUON Dome World Editor Tutorials

    Network Sponsors

    Apex Steel Pipe - Buys and sells Steel Pipe.
    Top