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:
    I read in the PC Gamer review that it was not much of an update to the game like just a re-release
  • Ghan Ghan:
    It's a good remaster. They did not want to change the game much because there are still many die-hard players out there that wouldn't like it to be a new game.
  • Ghan Ghan:
    They've said they are open to adding new content in the future, but wanted to get what's there correct first. As it is, for what you get the $40 asking price is a bit steep.
  • The Helper The Helper:
    Yeah that is a high price think I am going to wait a minute and see if it goes down some.
  • The Helper The Helper:
    Looking forward to doing some dungeon grinding again though.
  • The Helper The Helper:
    still 101 bots on the site constantly
  • midnight8 midnight8:
    my bots got out of their cage? oh no
  • The Helper The Helper:
    these bots are from russia, china and asia they are less than they were but more than they need to be I have no idea why they hover here
  • tom_mai78101 tom_mai78101:
    Probably because of my Headline News
  • The Helper The Helper:
    Facebook is getting fucked right now lol
  • The Helper The Helper:
    I learned one thing from the Facebook outage - BGP - I am sure Ghan is an expert in it but I did not know what the name of it was I knew there was something like that there though
  • The Helper The Helper:
    What is up Tom?
  • jonas jonas:
    We once considered doing a project on BGP looking for problems in the implementations. But we didn't get sufficient interest and Tony Li was mean to us so we said whatever
  • Varine Varine:
    hello, peeps. I got most of my computer back up to Idaho so I have passwords again after that nightmare of like 4 months
  • Varine Varine:
    We're all trying to get time off now, but holy shit. I did not expect it to be that busy. Every day was a historical record, and every week was a total sales record for like all of summer. My pay is going to almost double next summer, which makes cooking finally a viable career, I might actually afford to change industries soon!
  • Varine Varine:
    We had people ask if there was anything we could do about the fucking sun. There's like 30 inside tables and every outside table has a sunbrella thing.
  • Varine Varine:
    No, I'm sorry, I cannot move the sun and the umbrella doesn't go 90 degrees sideways. They unfortunately had to deal with a sunset across mountains and a lake, we can only imagine the horror they had to undergo by visiting our restaurant
  • Varine Varine:
    Well, they do, but they're big and blow away and a 10 foot runaway umbrella is a much bigger problem than some cunt with the sun sorta in her eyes
  • The Helper The Helper:
    What is up Varine? Good to see you buddy! Too bad you are not in Houston I have a friend that needs Cooking staff like desperately remember if you ever want to get a different landscape, not sunsets over mountains and a lake Houston has opportunity!
  • tom_mai78101 tom_mai78101:
    Nice to see Varine back in action.
  • The Helper The Helper:
    Welcome back Varine!
  • The Helper The Helper:
    can someone answer a question in the World Editor Help forum?
  • jonas jonas:
    ok
  • The Helper The Helper:
    thank you jonas!
  • Varine Varine:
    I'll probably be here through next summer at least, I get paid decent at the moment and I expect it to go up significantly next year. Plus I'm on contract through the end of the year
    +1

    Members online

    No members online now.

    Affiliates

    Hive Workshop NUON Dome
    Top