i'm not a fan of unitindexing systems but when i tried to build one, my initialization was wrong. so today i was kinda boring and i decided to give it a try and make it right. here is the result:
as you can see, pretty simple, nothing special but (hopefully) with correct initialization (i tested and works good).
i hope this serves to anyone to understand a little bit more how unit indexers work.
JASS:
//! external ObjectMerger w3a Adef !e@$ anam "UnitIndexer" ansf "(UnitIndexer)" aart "" acat "" arac 0
library UnitIndexer
globals
public constant integer UNIT_INDEXER_ABILITY = '!e@$'
private constant boolean USE_HASHTABLE = false
endglobals
private function GlobalUnitFilter takes unit whichUnit returns boolean
return true
endfunction
static if ( USE_HASHTABLE ) then
globals
private hashtable hashTable = InitHashtable()
endglobals
endif
module UnitIndexerModule
private static method onInit takes nothing returns nothing
static if ( thistype.onIndex.exists ) then
call UnitIndexer.onIndex(function thistype.onIndex)
endif
static if ( thistype.onDeIndex.exists ) then
call UnitIndexer.onDeIndex(function thistype.onDeIndex)
endif
endmethod
endmodule
private module UnitIndexerInit
private static method initialize takes nothing returns nothing
set thistype.initialized = true
loop
exitwhen integer(thistype.lastUnitIndexer) == 0
call thistype.lastUnitIndexer.fireEvent(true)
set thistype.lastUnitIndexer = thistype.lastUnitIndexer - 1
endloop
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onInit takes nothing returns nothing
call thistype.indexInitialUnits()
call TimerStart(CreateTimer(), 0, false, function thistype.initialize)
endmethod
endmodule
struct UnitIndexer
readonly static thistype triggered = 0
private static trigger eventTrigger = CreateTrigger()
private static integer array locked
private static boolean initialized = false
private static thistype lastUnitIndexer = 0
readonly unit unit
static if ( USE_HASHTABLE ) then
readonly integer handleId
endif
method isLocked takes nothing returns boolean
return thistype.locked[this] > 0
endmethod
method lock takes boolean lock returns nothing
if ( lock ) then
set thistype.locked[this] = thistype.locked[this] + 1
else
set thistype.locked[this] = thistype.locked[this] - 1
endif
endmethod
static method onIndex takes code c returns nothing
call TriggerAddCondition(thistype.eventTrigger, Condition(c))
endmethod
static method onDeIndex takes code c returns nothing
call TriggerAddAction(thistype.eventTrigger, c)
endmethod
private method fireEvent takes boolean indexEvent returns nothing
local thistype prevTriggered = thistype.triggered
set thistype.triggered = this
if ( indexEvent ) then
if ( thistype.initialized ) then
call TriggerEvaluate(thistype.eventTrigger)
endif
else
if ( thistype.initialized ) then
call TriggerExecute(thistype.eventTrigger)
else
set thistype.initialized = true
call this.fireEvent(true)
call this.fireEvent(false)
set thistype.initialized = false
endif
endif
set thistype.triggered = prevTriggered
endmethod
method destroy takes nothing returns nothing
call this.fireEvent(false)
call UnitRemoveAbility(this.unit, UNIT_INDEXER_ABILITY)
static if ( USE_HASHTABLE ) then
call FlushChildHashtable(hashTable, this.handleId)
set this.handleId = 0
else
call SetUnitUserData(this.unit, 0)
endif
set this.unit = null
if ( not(this.isLocked()) ) then
call this.deallocate()
endif
endmethod
static method get takes unit whichUnit returns thistype
static if ( USE_HASHTABLE ) then
return LoadInteger(hashTable, GetHandleId(whichUnit), 0)
else
return GetUnitUserData(whichUnit)
endif
endmethod
static method create takes unit whichUnit returns thistype
local thistype this
if ( thistype.get(whichUnit) == 0 and GlobalUnitFilter(whichUnit) ) then
set this = thistype.allocate()
if ( integer(thistype.lastUnitIndexer) < integer(this) ) then
set thistype.lastUnitIndexer = this
endif
static if ( USE_HASHTABLE ) then
set this.handleId = GetHandleId(whichUnit)
call SaveInteger(hashTable, this.handleId, 0, this)
else
call SetUnitUserData(whichUnit, this)
endif
set this.unit = whichUnit
call UnitAddAbility(whichUnit, UNIT_INDEXER_ABILITY)
call UnitMakeAbilityPermanent(whichUnit, true, UNIT_INDEXER_ABILITY)
call this.fireEvent(true)
endif
return this
endmethod
private static method onEnter takes nothing returns boolean
call thistype.create(GetFilterUnit())
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit leavingUnit = GetFilterUnit()
local thistype unitIndexer = thistype.get(leavingUnit)
if ( GetUnitAbilityLevel(leavingUnit, UNIT_INDEXER_ABILITY) == 0 and unitIndexer != 0 ) then
call unitIndexer.destroy()
endif
set leavingUnit = null
return false
endmethod
private static method indexInitialUnits takes nothing returns nothing
local trigger onLeaveTrigger = CreateTrigger()
local boolexpr onEnterCondition = Condition(function thistype.onEnter)
local boolexpr onLeaveCondition = Condition(function thistype.onLeave)
local region worldBoundsRegion = CreateRegion()
local integer maxPlayers = bj_MAX_PLAYERS
local player enumPlayer
loop
exitwhen maxPlayers < 0
set enumPlayer = Player(maxPlayers)
call TriggerRegisterPlayerUnitEvent(onLeaveTrigger, enumPlayer, EVENT_PLAYER_UNIT_ISSUED_ORDER, onLeaveCondition)
call GroupEnumUnitsOfPlayer(bj_lastCreatedGroup, enumPlayer, onEnterCondition)
call SetPlayerAbilityAvailable(enumPlayer, UNIT_INDEXER_ABILITY, false)
set maxPlayers = maxPlayers - 1
endloop
call RegionAddRect(worldBoundsRegion, GetWorldBounds())
call TriggerRegisterEnterRegion(CreateTrigger(), worldBoundsRegion, onEnterCondition)
set onLeaveTrigger = null
set onEnterCondition = null
set onLeaveCondition = null
set worldBoundsRegion = null
set enumPlayer = null
endmethod
implement UnitIndexerInit
endstruct
endlibrary
as you can see, pretty simple, nothing special but (hopefully) with correct initialization (i tested and works good).
i hope this serves to anyone to understand a little bit more how unit indexers work.