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: 260

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.
  • Varine Varine:
    I ordered like five blocks for 15 dollars. They're just little aluminum blocks with holes drilled into them
  • Varine Varine:
    They are pretty much disposable. I have shitty nozzles though, and I don't think these were designed for how hot I've run them
  • Varine Varine:
    I tried to extract it but the thing is pretty stuck. Idk what else I can use this for
  • Varine Varine:
    I'll throw it into my scrap stuff box, I'm sure can be used for something
  • Varine Varine:
    I have spare parts for like, everything BUT that block lol. Oh well, I'll print this shit next week I guess. Hopefully it fits
  • Varine Varine:
    I see that, despite your insistence to the contrary, we are becoming a recipe website
  • Varine Varine:
    Which is unique I guess.
  • The Helper The Helper:
    Actually I was just playing with having some kind of mention of the food forum and recipes on the main page to test and see if it would engage some of those people to post something. It is just weird to get so much traffic and no engagement
  • The Helper The Helper:
    So what it really is me trying to implement some kind of better site navigation not change the whole theme of the site
  • Varine Varine:
    How can you tell the difference between real traffic and indexing or AI generation bots?
  • The Helper The Helper:
    The bots will show up as users online in the forum software but they do not show up in my stats tracking. I am sure there are bots in the stats but the way alot of the bots treat the site do not show up on the stats
  • Varine Varine:
    I want to build a filtration system for my 3d printer, and that shit is so much more complicated than I thought it would be
  • Varine Varine:
    Apparently ABS emits styrene particulates which can be like .2 micrometers, which idk if the VOC detectors I have can even catch that
  • Varine Varine:
    Anyway I need to get some of those sensors and two air pressure sensors installed before an after the filters, which I need to figure out how to calculate the necessary pressure for and I have yet to find anything that tells me how to actually do that, just the cfm ratings
  • Varine Varine:
    And then I have to set up an arduino board to read those sensors, which I also don't know very much about but I have a whole bunch of crash course things for that
  • Varine Varine:
    These sensors are also a lot more than I thought they would be. Like 5 to 10 each, idk why but I assumed they would be like 2 dollars
  • Varine Varine:
    Another issue I'm learning is that a lot of the air quality sensors don't work at very high ambient temperatures. I'm planning on heating this enclosure to like 60C or so, and that's the upper limit of their functionality
  • Varine Varine:
    Although I don't know if I need to actually actively heat it or just let the plate and hotend bring the ambient temp to whatever it will, but even then I need to figure out an exfiltration for hot air. I think I kind of know what to do but it's still fucking confusing
  • The Helper The Helper:
    Maybe you could find some of that information from AC tech - like how they detect freon and such
  • Varine Varine:
    That's mostly what I've been looking at
  • Varine Varine:
    I don't think I'm dealing with quite the same pressures though, at the very least its a significantly smaller system. For the time being I'm just going to put together a quick scrubby box though and hope it works good enough to not make my house toxic
  • Varine Varine:
    I mean I don't use this enough to pose any significant danger I don't think, but I would still rather not be throwing styrene all over the air

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top