Cohadar
master of fugue
- Reaction score
- 209
New version: v6.2 (fully backwards compatible)
Changes:
* You can now get unit from an index GetIndexUnit(index) -> unit
* Added PUI module that does the same stuff as PUI textmacro
Changes:
* You can now get unit from an index GetIndexUnit(index) -> unit
* Added PUI module that does the same stuff as PUI textmacro
JASS:
//===========================================================================
// PUI -- Perfect Unit Indexing by Cohadar -- v6.2
//===========================================================================
//
// 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
//
// THANKS TO:
// * Vexorian - for his help with PUI textmacro
// * Builder Bob - for testing and bugfinding
// * Joker(Div) - bugfinding
//
// 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
//***************************************************************************
//
// PUI TEXTMACROS
//
//***************************************************************************
//===========================================================================
// Allowed PUI_PROPERTY TYPES are: unit, integer, real, boolean, string
// Do NOT put handles that need to be destroyed here (timer, trigger, ...)
// Instead put them in a struct and use PUI textmacro
//===========================================================================
//! textmacro PUI_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
private static unit array pui_unit
private static $TYPE$ array pui_data
//-----------------------------------------------------------------------
// Returns default value when first time used
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns $TYPE$
local integer pui = GetUnitIndex(whichUnit)
if .pui_unit[pui] != whichUnit then
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = $DEFAULT$
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, $TYPE$ whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
endmethod
endstruct
//! endtextmacro
//===========================================================================
// Never destroy PUI structs directly.
// Use .release() instead, will call .destroy()
// The best option is never to release or destroy PUI structs manually
// because they will be recycled automatically with unit handle.
//===========================================================================
//! textmacro PUI
private static unit array pui_unit
private static integer array pui_data
private static integer array pui_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to unit
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns integer
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
if .pui_unit[pui] != whichUnit then
// recycled handle detected
call .destroy(.pui_data[pui])
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endif
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, integer whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
call .destroy(.pui_data[pui])
endif
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
set .pui_id[whichData] = pui
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when unit handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pui= .pui_id[integer(this)]
call .destroy()
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endmethod
//! endtextmacro
//***************************************************************************
//
// PUI MODULE (does the same stuff as PUI textmacro)
//
//***************************************************************************
//===========================================================================
// Never destroy PUI structs directly.
// Use .release() instead, will call .destroy()
// The best option is never to release or destroy PUI structs manually
// because they will be recycled automatically with unit handle.
//===========================================================================
module PUI
private static unit array pui_unit
private static integer array pui_data
private static integer array pui_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to unit
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns integer
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
if .pui_unit[pui] != whichUnit then
// recycled handle detected
call .destroy(.pui_data[pui])
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endif
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, integer whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
call .destroy(.pui_data[pui])
endif
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
set .pui_id[whichData] = pui
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when unit handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pui= .pui_id[integer(this)]
call .destroy()
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endmethod
endmodule
JASS:
//==============================================================================
// PUI -- Perfect Unit Indexing by Cohadar -- v5.3
//==============================================================================
//
// PURPOUSE:
// * Extending UnitUserData()
// * This is basically perfect hashing algorithm for units
//
// HOW TO USE:
// * You have only one function at your disposal GetUnitIndex(unit)
// It will return a unique index for each unit 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 units
// PUI for structs
// PUI_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 UnitUserData()
//
// * You can also use this to attach spell data structs to casters
//
// * There are no SetUnitIndex() or ClearUnitIndex() functions here
// Each unit gets assigned one index that cannot be changed
// That index will be automatically recycled when unit is removed from the game.
//
// CONS:
// * This system uses UnitUserData() itself
// That means that if you want to use PUI you must recode
// any other system that uses UnitUserData() to use GetUnitIndex() instead
//
// * If you use UnitIndex 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 GetUnitIndex() assigns a recycled index (index of some dead and removed unit)
// to the newly created unit 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 unit array to keep track of all units with an index.
// Array is periodically checked for removed units,
// when removed unit is found, index is recycled.
// Indexes have "decay time" to prevent bugs
// caused by attaching to "Can't Raise, Does not decay" type units,
// or by using RemoveUnit() function
//
// SYSTEM COMMANDS: (debug mode only, red player only)
//
// * type -pui to display indexes of currently selected units
// * type -puistats to display some stats
// * type -puitest to verify that all indexes are valid and unique
//
// THANKS TO:
// * Vexorian - for his help with PUI textmacro
// * Builder Bob - for testing and bugfinding
// * Joker(Div) - bugfinding
//
// 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
//-----------------------------------------------
private constant real INDEX_DECAY_TIME = 5. // seconds
//-----------------------------------------------
private constant real PERIOD = 0.03125 // 32 fps
//-----------------------------------------------
private constant integer DECAY_TICKS = R2I(INDEX_DECAY_TIME/PERIOD)
//-----------------------------------------------
private integer maxIndex = 0
private integer usedIndexCount = 0
private integer checker = 0
private integer array Indexz
private unit array Unitz
//-----------------------------------------------
private integer freeIndexCount = 0
private integer array Freez
//-----------------------------------------------
private integer decayIndexCount = 0
private integer decayer = 0
private integer array Decayz
private integer array TimeEndz
private integer tick = 0
endglobals
//==============================================================================
private function PeriodicRecycler takes nothing returns boolean
set tick = tick + 1
// unit recycler
if usedIndexCount > 0 then
set checker = checker + 1
if checker > usedIndexCount then
set checker = 1
endif
if (GetUnitUserData(Unitz[checker]) == 0) then
set decayIndexCount = decayIndexCount + 1
set Decayz[decayIndexCount] = Indexz[checker]
set TimeEndz[decayIndexCount] = tick + DECAY_TICKS
set Indexz[checker] = Indexz[usedIndexCount]
set Unitz[checker] = Unitz[usedIndexCount]
set usedIndexCount = usedIndexCount - 1
endif
endif
// index recycler
if decayIndexCount > 0 then
set decayer = decayer + 1
if decayer > decayIndexCount then
set decayer = 1
endif
if TimeEndz[decayer] <= tick then
set freeIndexCount = freeIndexCount + 1
set Freez[freeIndexCount] = Decayz[decayer]
set Decayz[decayer] = Decayz[decayIndexCount]
set TimeEndz[decayer] = TimeEndz[decayIndexCount]
set decayIndexCount = decayIndexCount - 1
endif
endif
// for debugging
//if ModuloInteger(tick, 8) == 0 then
// call ClearTextMessages()
// call BJDebugMsg("Used/Free/Decaying: " + I2S(usedIndexCount) + "/" + I2S(freeIndexCount) + "/" + I2S(decayIndexCount))
//endif
return false
endfunction
//==============================================================================
// Main and only function exported by this library
//==============================================================================
function GetUnitIndex takes unit whichUnit returns integer
local integer index
debug if whichUnit == null then
debug call BJDebugMsg("|c00FF0000ERROR: PUI - Index requested for null unit")
debug return 0
debug endif
set index = GetUnitUserData(whichUnit)
if index == 0 then
set usedIndexCount = usedIndexCount + 1
if freeIndexCount > 0 then
set Indexz[usedIndexCount] = Freez[freeIndexCount]
set freeIndexCount = freeIndexCount - 1
else
set maxIndex = maxIndex + 1
set Indexz[usedIndexCount] = maxIndex
endif
set Unitz[usedIndexCount] = whichUnit
call SetUnitUserData(whichUnit, Indexz[usedIndexCount])
set index = GetUnitUserData(whichUnit)
// this happens when requesting unit index for removed unit
debug if index == 0 then
debug call BJDebugMsg("|c00FFCC00WARNING: PUI - Bad unit handle")
debug endif
endif
return index
endfunction
//==============================================================================
private function DisplayStats takes nothing returns nothing
call BJDebugMsg("=============================================")
call BJDebugMsg("Biggest index ever = " + I2S(maxIndex))
call BJDebugMsg("Indexes in use = " + I2S(usedIndexCount))
call BJDebugMsg("Decaying indexes = " + I2S(decayIndexCount))
call BJDebugMsg("Released indexes = " + I2S(freeIndexCount))
call BJDebugMsg("=============================================")
endfunction
//===========================================================================
private function DisplaySelectedEnum takes nothing returns nothing
call BJDebugMsg( "PUI(" + ( GetUnitName(GetEnumUnit()) + ( ") = " + I2S(GetUnitUserData(GetEnumUnit())) ) ) )
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
//==============================================================================
globals
private integer testCounter = 0
private integer array TestCountz
endglobals
//==============================================================================
private function Test takes nothing returns nothing
local integer i
set testCounter = testCounter + 1
set i = 1
loop
exitwhen i > usedIndexCount
// happens when you abuse UnitUserData outside PUI
if GetUnitTypeId(Unitz<i>) != 0 and GetUnitUserData(Unitz<i>) != Indexz<i> then
call BJDebugMsg("|c00FF0000ERROR: PUI - Invalid index detected")
return
endif
// if this error happens it means PUI is bugged
if TestCountz[Indexz<i>] != testCounter then
set TestCountz[Indexz<i>] = testCounter
else
call BJDebugMsg("|c00FF0000ERROR: PUI - Double index detected")
return
endif
set i = i + 1
endloop
call BJDebugMsg("|c0000ff00OK: PUI - All indexes were valid and unique")
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) )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-pui", true )
debug call TriggerAddAction( trig, function DisplaySelected )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-puistats", true )
debug call TriggerAddAction( trig, function DisplayStats )
debug set trig = CreateTrigger()
debug call TriggerRegisterPlayerChatEvent( trig, Player(0), "-puitest", true )
debug call TriggerAddAction( trig, function Test )
endfunction
endlibrary
//===========================================================================
// Allowed PUI_PROPERTY TYPES are: unit, integer, real, boolean, string
// Do NOT put handles that need to be destroyed here (timer, trigger, ...)
// Instead put them in a struct and use PUI textmacro
//===========================================================================
//! textmacro PUI_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
private static unit array pui_unit
private static $TYPE$ array pui_data
//-----------------------------------------------------------------------
// Returns default value when first time used
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns $TYPE$
local integer pui = GetUnitIndex(whichUnit)
if .pui_unit[pui] != whichUnit then
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = $DEFAULT$
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, $TYPE$ whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
endmethod
endstruct
//! endtextmacro
//===========================================================================
// Never destroy PUI structs directly.
// Use .release() instead, will call .destroy()
//===========================================================================
//! textmacro PUI
private static unit array pui_unit
private static integer array pui_data
private static integer array pui_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to unit
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns integer
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
if .pui_unit[pui] != whichUnit then
// recycled handle detected
call .destroy(.pui_data[pui])
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endif
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, integer whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
call .destroy(.pui_data[pui])
endif
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
set .pui_id[whichData] = pui
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when unit handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pui= .pui_id[integer(this)]
call .destroy()
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endmethod
//! endtextmacro
</i></i></i></i></i>