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:
    Have been talking to a childhood friend who is an Artist and is interested in doing some game music. He has a studio in his house. Yes!
  • The Helper The Helper:
    Going to try and get my friend into the homebrew game indie market he is real interested
  • Varine Varine:
    Does reforged require the port forwarding? Is that even still a thing?
  • The Helper The Helper:
    I do not think that is a thing anymore
  • The Helper The Helper:
    Battle.net is different now and I do not think they are using the same network code
  • The Helper The Helper:
    I am sure they are not
  • tom_mai78101 tom_mai78101:
    Lately, news aren't as enticing as they seemed. Or maybe I'm getting old and desensitized.
  • jonas jonas:
    I think news are more serious recently
  • tom_mai78101 tom_mai78101:
    More serious doesn't necessarily mean it's more interesting. :(
  • The Helper The Helper:
    I find that news.google.com has lots of interesting science stuff going on and the Space X stuff with Starship is super cool too
  • The Helper The Helper:
    plus the boring company and physics stuff happening now but I am more into the science now but there is still some crazy news I see out there using Reddit as a news source though and aggregate services like news.google and stuff and put AP in there I always read the news and yes sometimes it is not as interesting as others
  • The Helper The Helper:
    At some point I am going to look at Xenforo and see if it integrates with Hubspot at all. Would love to look at forum under a full marketing account in Hubspot since I have access to that stuff with my current gig
  • Ghan Ghan:
    Kind of surprised it wouldn't have its own forum product offering.
  • Ghan Ghan:
    That said, when Blizzard switched forum software, they went with Discourse, not xenForo.
  • jonas jonas:
    I think news.google shows you personalized news
  • midnight8 midnight8:
    any of the peeps in here run youtube channels? Figure of all the peeps in this forum, got to be some other geeks like me with channels
  • Ghan Ghan:
    I can say I've uploaded a YouTube video.
    +1
  • The Helper The Helper:
    I can say I know someone that uploaded a youtube video :)
  • jonas jonas:
    A friend of mine has a channel where he uploads some of the crazy machines he has built. Like when he plugged an electric motor into a 3d printed gearbox, attached it to his bycicle and then drove 50mph with it.
  • The Helper The Helper:
    You should post a link to his channel in General Discussion I would definitely check it out

    Members online

    No members online now.

    Affiliates

    Hive Workshop NUON Dome
    Top