HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

PUI 6.0

12-05-2011, 08:37 PM#1
cohadar
Collapse PUI:
//==============================================================================
//  PUI -- Perfect Unit Indexing by Cohadar -- v6.1
//==============================================================================
//
//  PURPOUSE:
//       * Extending UnitUserData()
//       * This is basically perfect hashing algorithm for units
//
//  HOW TO USE:
//       * You have two functions at your disposal: 
//           GetUnitIndex(unit) -> index
//           GetIndexUnit(index) -> unit
//
//       * Put you custom unit properties in struct that extends arrays.
//         Use unit index as id of that struct.
//         There is an example of this in demo map.
//         
//  PROS:
//       * Unit indexes are assigned only to units that actually need them.
//       * Unit index will be automatically recycled when unit is removed from the game.
//       * Automatically detects null unit handles and removed unit handles (in debug mode)
//
//  CONS:
//       * This system needs exclusive access to UnitUserData
//
//  DETAILS:
//       * Uses internal vJass struct index allocation/deallocation algorithm.
//       * Periodically checks and recycles unit indexes
//
//  HOW TO IMPORT:
//       * Just create a trigger named PUI
//       * convert it to text and replace the whole trigger text with this one
//
//==============================================================================


library PUI initializer Init

//==============================================================================
globals    
    // maximum number of indexed units on your map at a single moment of time
    private constant integer MAX_INDEXES = 1024  // up to 8192
    
    // period of recycling, 32 indexes per second
    private constant real PERIOD = 0.03125   
    
    // current check index
    private integer C = 0 
endglobals

//==============================================================================
// Using internal struct algorithm for index allocation/dealocation
// This way I don't have to write messy code of my own
//==============================================================================
private struct UnitIndex
    unit u
    
    //----------------------------------------------------------
    static method create takes unit whichUnit returns UnitIndex
        local UnitIndex index 
        
        // check for null unit handle
        debug if whichUnit == null then
        debug     call BJDebugMsg("|c00FF0000ERROR: PUI - Index requested for null unit")
        debug     return 0
        debug endif
        
        set index = UnitIndex.allocate()
        set index.u = whichUnit
        call SetUnitUserData(whichUnit, index)
        
        // check for removed unit handle
        debug if GetUnitUserData(whichUnit) == 0 then
        debug     call BJDebugMsg("|c00FFCC00WARNING: PUI - Bad unit handle")
        debug     return 0
        debug endif        
        
        return index
    endmethod
    
    //----------------------------------------------------------
    static method Recycler takes nothing returns boolean
        local UnitIndex index = C
        set C = C + 1
        if C == MAX_INDEXES then
            set C = 0
        endif
        if index.u != null then
            if (GetUnitUserData(index.u) == 0) then
                set index.u = null
                call index.destroy()
            endif
        endif
        return false
    endmethod
endstruct

//==============================================================================
//  Returns index of some unit, if unit has no index it gets a new one.
//==============================================================================
function GetUnitIndex takes unit whichUnit returns integer
    local UnitIndex index = GetUnitUserData(whichUnit)
    if index == 0 then
        set index = UnitIndex.create(whichUnit)
    endif
    return index
endfunction

//==============================================================================
//  Return unit that has specified index, or null in no such unit exists.
//==============================================================================
function GetIndexUnit takes integer index returns unit
    local UnitIndex i = index
    if i.u != null then
        if GetUnitUserData(i.u) == index then
            return i.u
        endif
    endif
    return null
endfunction

//==============================================================================
private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterTimerEvent(trig, PERIOD, true)
    call TriggerAddCondition(trig, Condition(function UnitIndex.Recycler))
endfunction

endlibrary

Collapse example:
//===========================================================================
//  We define 2 integer properties for any unit,
//  number of deaths and number of kills.
//===========================================================================
scope Example initializer Init

//===========================================================================
private struct Data extends array
    unit whichUnit
    integer deaths
    integer kills
    
    // reset instead of create because array structs are never created
    method reset takes unit whichUnit returns nothing
        set .whichUnit = whichUnit
        set .deaths = 0
        set .kills = 0
    endmethod
    
    // In case unit handle gets recycled we need to reset the data
    static method Get takes unit whichUnit returns Data
        local Data data = GetUnitIndex(whichUnit)
        if data.whichUnit != whichUnit then
            call data.reset(whichUnit)
        endif
        return data
    endmethod
endstruct

//===========================================================================
private function Actions takes nothing returns nothing
    local unit victim = GetDyingUnit()
    local unit killer = GetKillingUnit()
    
    local Data data = Data.Get(victim)
    set data.deaths = data.deaths + 1
    
    if killer != null then
        set data = Data.Get(killer)
        set data.kills = data.kills + 1
        call TextTag_Unit(killer,I2S(data.kills), "|c00FFFFFF")
    endif
    
    set victim = null
    set killer = null
endfunction

//===========================================================================
private function Conditions takes nothing returns boolean
    return true
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( trig, Condition( function Conditions ) )
    call TriggerAddAction( trig, function Actions )
endfunction

endscope

Attached Images
File type: pngpui.png (1.0 MB)
File type: gifclear.gif (43 bytes)
12-06-2011, 08:00 AM#2
Bribe
This should have optional support for hashtable.

The "create" method leaks indices in some cases in DEBUG_MODE. To fix this you can revise it (and shorten it) to:
Collapse JASS:
    static method create takes unit whichUnit returns UnitIndex
        local UnitIndex index 
        
        // check for invalid unit handle
        debug if GetUnitTypeId(whichUnit) == 0 then
        debug     call BJDebugMsg("|c00FF0000ERROR: PUI - Index requested for invalid unit")
        debug     return 0
        debug endif
        
        set index = UnitIndex.allocate()
        set index.u = whichUnit
        call SetUnitUserData(whichUnit, index)
        
        return index
    endmethod

Why using a trigger for the timer, instead of just a normal timer?

PUI_PROPERTY is nice... if you are using just ONE piece of data. I don't even think it has a purpose otherwise.
12-06-2011, 08:07 AM#3
cohadar
Quote:
Originally Posted by Bribe
This should have optional support for hashtable.
I disagree.

Quote:
Originally Posted by Bribe
The "create" method leaks indices in some cases in DEBUG_MODE.
Recycler handles those.
I don't want to shorten anything, I want both null and removed errors to be detected, it is debug mode anyways.

Quote:
Originally Posted by Bribe
Why using a trigger for the timer, instead of just a normal timer?
Because.

Quote:
Originally Posted by Bribe
PUI_PROPERTY is nice... if you are using just ONE piece of data. I don't even think it has a purpose otherwise.
And that one piece of data can be a struct.

EDIT:
I just realized this version of PUI does not require exclusive access to UnitUserData.
If you assign custom values to some UnitUser data that unit will be ignored by PUI automatically.
This way you can have special uses for UserData on dummy casters for example, same goes for heroes, and what is best you need no custom filters for this.
Sometimes I am better coder than myself.
12-06-2011, 07:58 PM#4
BBQ
I don't see why anyone would use this over AutoIndex or any other decent indexer.
  1. GetUnitIndex() is not inline-friendly.
  2. No functionality to detect when a unit enters or leaves the map.
  3. No modules for structs.
  4. Periodic recycling.
  5. The PUI_PROPERTY cannot be used in conjunction with a Zinc script.
Yes, AutoIndex can use some improvements, but this is definitely not the thing anyone would be looking for.
12-06-2011, 08:20 PM#5
cohadar
Quote:
Originally Posted by BBQ
I don't see why anyone would use this over AutoIndex or any other decent indexer.
  1. GetUnitIndex() is .
  2. No modules for structs.
  3. Periodic recycling.
  4. The PUI_PROPERTY cannot be used in conjunction with a Zinc script.
Yes, AutoIndex can use some improvements, but this is definitely not the thing anyone would be looking for.

1. not indexing every unit > not inline-friendly

2. No functionality to detect when a unit enters or leaves the map.
There exists a standard unit-enters-map event no?
And unit-leaves-map is not really a unit event but an index event.

3 No modules for structs.
version 5.3 had support for structs but than I realized I never used it so I removed it.

4. Periodic recycling
How is this worse than adding a defend ability to every unit, and processing all unit-enters-map events and catching all undefend orders?

5. Zinc? What is that? Oh wait, another thing Vexorian made that is used by 0.000001% of mapmakers.

PUI was the first indexing system ever created, the concept did not exist before it.
It is simple,it does the job and has been used in maps. (Still used in EleTD as far as I know)

I don't care if anyone uses it, I want it approved on general principle.
12-06-2011, 08:34 PM#6
BBQ
Quote:
Originally Posted by cohadar
1. not indexing every unit > not inline-friendly
Every indexer should be able to filter out units, so not all of them will end up being indexed. This is especially important for dummy units.

Quote:
Originally Posted by cohadar
There exists a standard unit-enters-map event no?
And unit-leaves-map is not really a unit event but an index event.
The standard event fires for any unit. The events provided by an indexer fire only for units that have been indexed. There is quite a difference.

Quote:
Originally Posted by cohadar
version 5.3 had support for structs but than I realized I never used it so I removed it.
There is a plethora of situations in which you'd use it.

Quote:
Originally Posted by cohadar
How is this worse than adding a defend ability to every unit, and processing all unit-enters-map events and catching all undefend orders?
Not only that abusing the "undefend" order is faster, but it also allows for more functionality and extensibility. Look at AutoEvents.

Quote:
Originally Posted by cohadar
5. Zinc? What is that? Oh wait, another thing Vexorian made that is used by 0.000001% of mapmakers.
I'll refrain from commenting.

Quote:
Originally Posted by cohadar
It is simple,it does the job and has been used in maps. (Still used in EleTD as far as I know)
The last official version of Element TD used AutoIndex. I think that the author abandoned the project in favor of StarCraft II, and some random unofficial version that was posted months ago used a custom-made indexer, that contained only the stuff that was actually needed for a TD.

Quote:
Originally Posted by cohadar
I don't care if anyone uses it, I want it approved on general principle.
I don't think that's how resources get approved.

If I'm not mistaken, Anitarf (or it could have been somebody else) has stated that there is no "space" for different indexers in a single community -- and I can't help but agree with him.
12-06-2011, 08:51 PM#7
cohadar
Quote:
Originally Posted by BBQ
there is no "space" for different indexers in a single community -- and I can't help but agree with him.

Oh I knew chances of approval were in low percents when I posted this.
It is just a matter of principle for me to try.

I have long ago realized that all systems I make are always better than alternatives.
This has something to do with objective nature of human beings,
but still I would feel bad if I did not try to share my code with other people and assure them in the error of their ways.

EDIT:
I was reading this:
http://www.wc3c.net/vexorian/jasshel...html#arrstruct

and realized that not only that unit indexing does not need support for structs (those idiotic module stuff), it also does not need any textmacros (like PUI_PROPERTY)

In the end, the only thing you need is an integer and a struct that extends array.
You don't need any events for this at all.
12-13-2011, 04:21 PM#8
Anitarf
I don't see how this would be better than simply using a HandleTable. The argument of speed is not really convincing when your GetUnitIndex function is not inline friendly.

Evein with inlining, I don't paticularly care for the speed benefits. For me, the index/deindex events are the main selling point of indexing systems, since they allow you to easily do automatic creation and recycling. Without even that, I don't know what this script is supposed to do.
12-15-2011, 06:58 AM#9
cohadar
Quote:
Originally Posted by Anitarf
I don't see how this would be better than simply using a HandleTable. The argument of speed is not really convincing when your GetUnitIndex function is not inline friendly.
HandleTable does not provide array indexes at all, just storage without any unit-removed detection.
Autoindex function can be inline friendly because it uses enter-map event and creates indexes there, PUI creates indexes when you ask for them. It is the same amount of work just done in two different places.


Quote:
Originally Posted by Anitarf
For me, the index/deindex events are the main selling point of indexing systems, since they allow you to easily do automatic creation and recycling. Without even that, I don't know what this script is supposed to do.
Maybe an example will help, please post some spell that uses Autoindex events and I will recode it with PUI so we can compare.
12-15-2011, 07:11 AM#10
Bribe
@ Cohadar, here's a spell I wrote from last year which uses AutoIndex + AutoEvents so that it gets reincarnation events, on-birth events, etc:

http://www.hiveworkshop.com/forums/s...mproved-166353

I'll give it that most spells can use just your GetUnitIndex function, but the more advanced stuff is going to need more advanced indexing. Efficiency aside.
12-15-2011, 12:27 PM#11
Anitarf
Quote:
Originally Posted by cohadar
HandleTable does not provide array indexes at all, just storage without any unit-removed detection.
And what are you going to do with an array index other than store stuff in arrays? Why is the lack of unit removal detection relevant, I was making a comparison with PUI which doesn't have it either, which was my point: in pretty much any case where I could use PUI, I would rather just use Table instead.

Quote:
Autoindex function can be inline friendly because it uses enter-map event and creates indexes there, PUI creates indexes when you ask for them. It is the same amount of work just done in two different places.
That's a key difference, though. AutoIndex is both faster and has more functionality thanks to it. This isn't even a case of different approaches having different strengths, AutoIndex is absolutely better.
12-15-2011, 04:06 PM#12
cohadar
Quote:
Originally Posted by Bribe
@ Cohadar, here's a spell I wrote from last year which uses AutoIndex + AutoEvents so that it gets reincarnation events, on-birth events, etc:
That is not a spell, that is a whole custom system.
I have no intention of browsing through 10 different triggers and 5000 lines of code just to prove a point.
I am sure there are simpler examples that use both AutoIndex + AutoEvents.

Quote:
Originally Posted by Anitarf
That's a key difference, though. AutoIndex is both faster and has more functionality thanks to it. This isn't even a case of different approaches having different strengths, AutoIndex is absolutely better.
You should know by now that just claiming something to be faster does not automatically makes it so. Benchmarks often prove surprising.
(And I have very strong doubts when comparing code sizes of Autoindex and PUI)

It is AutoEvents that has more functionality, not AutoIndex, you are mixing stuff here.
And I still claim that functionality is just a fluff.
12-15-2011, 05:31 PM#13
Anitarf
Quote:
Originally Posted by cohadar
You should know by now that just claiming something to be faster does not automatically makes it so. Benchmarks often prove surprising.
(And I have very strong doubts when comparing code sizes of Autoindex and PUI)
This is not a question of whether X is faster than Y, but whether X is faster than X+Y. In other words, it is not a question at all, X is faster.

Quote:
It is AutoEvents that has more functionality, not AutoIndex, you are mixing stuff here.
onIndexed and onDeindexed events are a part of AutoIndex.

Quote:
And I still claim that functionality is just a fluff.
And I still claim it's the only good reason to use an indexing system at all instead of using a HandleTable to attach data to units.
12-16-2011, 04:53 AM#14
cohadar
I'll just ignore the speed nonsense, because you just keep arguing without any proof.

Quote:
Originally Posted by Anitarf
onIndexed and onDeindexed events are a part of AutoIndex.
See this right here! Pay special attention to this part!
This events is what I have been claiming to be not needed the whole time.

Since you failed to provide an example of their use, let's try it in reverse.
Collapse JASS:
//===========================================================================
//  We define 2 integer properties for any unit,
//  number of deaths and number of kills.
//===========================================================================
scope Example initializer Init

//===========================================================================
private struct Data extends array
    unit whichUnit
    integer deaths
    integer kills
    
    method reset takes unit whichUnit returns nothing
        set .whichUnit = whichUnit
        set .deaths = 0
        set .kills = 0
    endmethod
    
    static method Get takes unit whichUnit returns Data
        local Data data = GetUnitIndex(whichUnit)
        if data.whichUnit != whichUnit then
            call data.reset(whichUnit)
        endif
        return data
    endmethod
endstruct

//===========================================================================
private function Actions takes nothing returns nothing
    local unit victim = GetDyingUnit()
    local unit killer = GetKillingUnit()
    
    local Data data = Data.Get(victim)
    set data.deaths = data.deaths + 1
    
    if killer != null then
        set data = Data.Get(killer)
        set data.kills = data.kills + 1
        call TextTag_Unit(killer,I2S(data.kills), "|c00FFFFFF")
    endif
endfunction

//===========================================================================
private function Conditions takes nothing returns boolean
    return true
endfunction

//===========================================================================
private function Init takes nothing returns nothing
    local trigger trig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddCondition( trig, Condition( function Conditions ) )
    call TriggerAddAction( trig, function Actions )
endfunction

endscope


Oh look, I created an unit indexed struct without the need for onIndexed and onDeindexed.
Can you please recode this to use those events so we can all be awed by how much better Autoindex version is?
12-16-2011, 10:09 AM#15
Anitarf
Quote:
Originally Posted by cohadar
I'll just ignore the speed nonsense, because you just keep arguing without any proof.
You need proof that a function which inlines to a GetUnitUserData call is faster than a non-inlineable function which contains a GetUnitUserData call in addition to an if statement? Now you're just trolling.

Quote:
See this right here! Pay special attention to this part!
This events is what I have been claiming to be not needed the whole time.

Oh look, I created an unit indexed struct without the need for onIndexed and onDeindexed.
Can you please recode this to use those events so we can all be awed by how much better Autoindex version is?
In this particular example, the code will be quite similar. The main difference is that your Get method incurs extra cost on every read/write operation similarly to how your GetUnitIndex function does. Oh, right, I forgot that you are not convinced that adding extra code to a function actually makes it slower.
Expand JASS: