- Joined
- Nov 4, 2007
- Messages
- 337
What is this?
This is a System that created unique integers between 1 and 8190 for
handles. These values can be used as Index for globals.
Thats, why this is called 'HandleIndexing'.
This has the same functionality as PUI, but there are some important differences:
[*] - HandleIndexing works for each Handle type.
[*] - HandleIndexing does not use UnitUserData. That makes the whole thing slower, but it is not that slow that it causes laggs. Actually, this system is simple.
[*] - This is slower than TimerUtils, PUI, DUI, UIU, HSAS ==> whatever.
But the good things about this is, that you do not have to run textmacros.
HSAS for example created for each Usage ID/8190 global variables. I think, that is ... Not good. You HAVE to create a tetxmacro for each HSAS usage (static!), because otherwise you overwrite the stored value.
This is supposed to be combinated with the Recycling textmacro.
If they are used both, they will cover all your needs for handles.
And here is the code:
You might wonder, what Plugin is used for. The Identifier 'Eraselike' has just one parameter: The handle.
So if you want for example that the system nulls Locations automatically, you can write this function into 'Plugin' and give the function as parameter.
Warning: This requires careful handling, because it has not really protections or Debug messages.
Or as peq code:
peq - jass-Code #1488 //********************************************************************
And the two flavours of HandleIndexing.
And the Steel Flavour:
I created this, to add more comfort to HandleIndexing.
Why is this more comfort?
Ok. Let's imagine following situation:
I have 2 systems, both use HandleIndexing. I want to create an Index for a unit in system 2,
but it was previously created in system 1. (Were talking about the same handle).
So if you release the Index from system 2 now, the Index from system 1 is recycled, too.
===> This is only possible, if the handle is used in more than one system, if it's reachable from each trigger. So if you use for example a Recycling System, this is not problem.
But it's always a problem, if you have for example units.
If you use HandleIndexing correctly, you will not experience bugs.
But remember: If you have units as Handle, there are 2 possibilites to prevent that bug:
A: Use HandleIndexing Steel
B: Flush the Index of a unit, when it decays.
I prefer solution B.
Because then you have other comfort functions from A.
However, heres the steel code:
Ok. However.
I said, that HandleIndexing is slower. Well. I did some tests with 500000 executions:
TEST with StopWatchMark
=== TESTED WITH WAR3ERR DISABLED===
With 'Safety':
HandleIndexing: 90% [Called 500k times Index and FlushIndex]
PUI: 100% [Called 500k times PeriodicRecycler and GetUnitIndex]
Without 'Safety':
HandleIndexing: 100% [Called 500k times Index and FlushIndex]
PUI: 94% [Called 500k times PeriodicRecycler and GetUnitIndex]
==> This is, what the systems actually do. A bit code was removed (when Indexes got higher than 8190).
So actually, this can be faster than PUI.
PUI is faster, when you call FlushIndex() more than three times a second. However, if you use this for units,
This can be faster, too. If you Flush the Index when a unit decays:
EDIT: New results. Updated. HandleIndexing is as fast as PUI. I think they're both almost identically fast.
http://peeeq.de/gui.php?id=773
Thats dynamic. Thats faster than PUI. Ok.
This system is faster than PUI in most usages, but you can't do anything wrong with PUI,
because it recycles automatically. You have to be more careful with HandleIndexing.
===> I was suprised, that the debug messages slower my system by ~35%!
However, if you use the map finally, you should remove the debug messages.
Note, that PeriodicRecycler isn't called as often as GetUnitIndex(), but if you play for very long, the Test might fit. Maybe PUI uses even more performance.
I didn't test UnitIndexingUtils, because I think, that it is the same as PUI, but PUI is safer, because it recycles Indexes automatically (Do you remember the possible bug I explained?)
And the testcode:
And a manual, how to improve coding with HandleIndexing:
This is a System that created unique integers between 1 and 8190 for
handles. These values can be used as Index for globals.
Thats, why this is called 'HandleIndexing'.
This has the same functionality as PUI, but there are some important differences:
[*] - HandleIndexing works for each Handle type.
[*] - HandleIndexing does not use UnitUserData. That makes the whole thing slower, but it is not that slow that it causes laggs. Actually, this system is simple.
[*] - This is slower than TimerUtils, PUI, DUI, UIU, HSAS ==> whatever.
But the good things about this is, that you do not have to run textmacros.
HSAS for example created for each Usage ID/8190 global variables. I think, that is ... Not good. You HAVE to create a tetxmacro for each HSAS usage (static!), because otherwise you overwrite the stored value.
This is supposed to be combinated with the Recycling textmacro.
If they are used both, they will cover all your needs for handles.
And here is the code:
JASS:
//********************************************************************
// textmacro Recycling takes NAME, TYPE, IDENTIFIER, AMOUNT, ERASELIKE
// NAME = Enter the name you want to Recycle with. Example: Enter Bottle to create NewBottle() and ReleaseBottle().
// TYPE = Enter the handle type you want to recycle. timer will create timers to recycle.
// IDENTIFIER = How is that handle created? Preloaded? If TYPE is timer, this has to be NewTimer().
// AMOUNT = How big shall the stack be? The bigger the safer. But this preloads AMOUNT types of the Handle
// You want at the map Init, so it lacks all the time some memory.
// ERASELIKE = How is the handle simulated to be dead? For Timers Write PauseTimer.
// Dont add the parameters here. They are added automatically. If you don't want simulate death, or it is impossible,
// set this to Foo.
// ======= Examples =======
// Are found below
// ======= Functions =======
// New$NAME$() : Recycles an old timer, to keep the HandleID size small.
// Release$NAME$($TYPE$) : Timers created with NewTimer() must be deleten like this.
library Plugin
function Foo takes handle h returns nothing
set h = null
endfunction
function P_NewUnitA takes nothing returns unit
local unit u = CreateUnit(Player(15),'e003',0.,0.,0.)
call ShowUnit(u, false)
return u
endfunction
function P_ReleaseUnit takes unit u returns nothing
call SetUnitAnimation(u,"death")
call ShowUnit(u, false)
//call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl",GetUnitX(u),GetUnitY(u)))
endfunction
endlibrary
//! textmacro Recycling takes NAME, TYPE, IDENTIFIER, ERASELIKE
library $NAME$Recycling initializer $NAME$Preload requires Plugin
globals
integer $NAME$Wanted = 5
$TYPE$ array $NAME$Handle
endglobals
function New$NAME$ takes nothing returns $TYPE$
if ( $NAME$Wanted<1 ) then
[I] set $NAME$Wanted = $NAME$Wanted + 1
set $NAME$Handle[$NAME$Wanted] = $IDENTIFIER$[/I] ==> [U]CUT THIS OUT![/U]
return $IDENTIFIER$
endif
set $NAME$Wanted = $NAME$Wanted - 1
return $NAME$Handle[$NAME$Wanted]
endfunction
function Release$NAME$ takes $TYPE$ which returns nothing
set $NAME$Handle[$NAME$Wanted] = which
set $NAME$Wanted = $NAME$Wanted + 1
call $ERASELIKE$(which)
endfunction
private function $NAME$Preload takes nothing returns nothing
local integer i = 0
loop
set i = i + 1
exitwhen i > $NAME$Wanted
set $NAME$Handle[i] = $IDENTIFIER$
endloop
endfunction
endlibrary
//! endtextmacro
//! runtextmacro Recycling( "Group", "group", "CreateGroup()" , "GroupClear")
// runtextmacro Recycling( "Rect", "rect", "Rect(0.,0.,0.,0.)" , "Foo")
// runtextmacro Recycling( "Location", "location", "Location(0.,0.)", "Foo")
// runtextmacro Recycling( "Force", "force", "CreateForce()", "ForceClear")
// runtextmacro Recycling( "Timer", "timer", "CreateTimer()", "PauseTimer")
// runtextmacro Recycling( "Dummy", "unit", "P_NewUnitA()", "P_ReleaseUnit)
You might wonder, what Plugin is used for. The Identifier 'Eraselike' has just one parameter: The handle.
So if you want for example that the system nulls Locations automatically, you can write this function into 'Plugin' and give the function as parameter.
JASS:
function ClearLocation takes location l returns nothing
call SetLocationX(l,0.)
call SetLocationY(l,0.)
endfunction
Warning: This requires careful handling, because it has not really protections or Debug messages.
Or as peq code:
peq - jass-Code #1488 //********************************************************************
And the two flavours of HandleIndexing.
JASS:
// ***************************************
//! S T A R T O F H A N D L E I N D E X I N G
// ***************************************
//*********************************************************************
//* HandleIndexing
//* ----------
//*
//* To implement it , create a custom text trigger called HandleIndexing
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your needs:
//* * Get unique integers between 1 and 8190 for handles.
//*
//* set index = Index(handle) : Gets a unique number that fits into arrays for a handle.
//* FlushIndex(handle) : Tells the recycler that this Index can be recycles. Call this, when youre don with the Index.
//* FlushIndexSafe(handle) : Flushs an Index using the system itself;
//* PassIndex(handle,handle) : Passes ALL the data attached with this system from a handle to another handle.
//* Features
//* - Doesn't use UnitUserData
//* - You Don't have to run textmacros
//* - Attaching 15 Data to a unit is almost as fast as attaching 1 Date to a unit.
//* - This is safe! If you Release an Index from a unit, that isn't create, the index will instantly be moved to the RecycleBin
//* - This is fast! HSAS for example or PUI is faster, but; HSAS needs to run a textmacro for each usage. And it uses extended arrays. That are about 30 globals per macro. PUI uses UnitUserData and I don't like PUIs way to recycle Indexes.
//* - If you combinate this with Recycling textmacros, it is perfect to recycle indexes and add Data to them.
//*
//********************************************************************
//! I M P O R T A N T
//* If it is possible, that your handle is used in more than one system (like units), Flush the Handle
//* Only, when the Handle is destroyed!
//================================================================
library HandleIndexing
globals
private constant integer MAX_HANDLE_ID = 100000
private constant integer tosub = 0x100000
private integer array StoredIDs[MAX_HANDLE_ID]
private integer IDs = 0
private integer gaps = 0
private integer array Gaps
endglobals
private function h2i takes handle h returns integer
return h
return 0
endfunction
function ReleaseIndex takes integer index returns nothing
//! Tell the recycler which gap can be recycled. This will not remove the fitting Index from a unit. ==> lacks memory
set gaps = gaps + 1
set Gaps[gaps] = index
endfunction
function FlushIndex takes handle h returns integer
local integer num = h2i(h)-tosub
local integer re = 0
if ( StoredIDs[num] != 0 ) then
call ReleaseIndex(StoredIDs[num])
set re = StoredIDs[num]
set StoredIDs[num] = 0
endif
return re
endfunction
function CreateIndex takes integer num returns integer
//! Recycle Indexes
if ( gaps > 0 ) then
set StoredIDs[num] = Gaps[gaps]
set gaps = gaps - 1
return StoredIDs[num]
else
//! Create new Index
set IDs = IDs + 1
set StoredIDs[num] = IDs
return IDs
endif
endfunction
function Index takes handle h returns integer
local integer hashnum = h2i(h)-tosub
if ( StoredIDs[hashnum] == 0 ) then
return CreateIndex(hashnum)
else
return StoredIDs[hashnum]
endif
endfunction
function PassIndex takes handle a, handle b returns nothing
local integer index = Index(a)
local integer num = h2i(b)-tosub
set StoredIDs[num] = index
call FlushIndex(a)
endfunction
endlibrary
And the Steel Flavour:
I created this, to add more comfort to HandleIndexing.
Why is this more comfort?
Ok. Let's imagine following situation:
I have 2 systems, both use HandleIndexing. I want to create an Index for a unit in system 2,
but it was previously created in system 1. (Were talking about the same handle).
So if you release the Index from system 2 now, the Index from system 1 is recycled, too.
===> This is only possible, if the handle is used in more than one system, if it's reachable from each trigger. So if you use for example a Recycling System, this is not problem.
But it's always a problem, if you have for example units.
If you use HandleIndexing correctly, you will not experience bugs.
But remember: If you have units as Handle, there are 2 possibilites to prevent that bug:
A: Use HandleIndexing Steel
B: Flush the Index of a unit, when it decays.
I prefer solution B.
Because then you have other comfort functions from A.
However, heres the steel code:
JASS:
//| --------------------------------------------- |
//| | ########################### |
//| HandleIndexing | #### [ Steel Flavour ] #### |
//| | ########################### |
//| --------------------------------------------- |
//
// Actually, normal HandleIndexing is the better choice.
// But this is suited for noobs and safer.
// Differences:
// ________________
// | Differences |
// |________________|
// - You dont have to be careful when you Flush an Index with this Flavour.
// - Instead of calling Index(handle) to create AND get an Index, you have to
// call GetIndex(handle) to return the Index of the unit and
// call NewIndex(handle) to create an Index for the unit.
// - This is slower than the Normal Handle Indexing.
// - If you use HandleIndexing Steel Flavour, it's veeeeery important that you call ReleaseIndex()!!!!
// - If you created an Index, you have to release one.
// ________________________
// | How does this work? |
// |________________________|
// - If you call NewIndex(handle), an integer for the Index is increased by 1.
// If you call ReleaseIndex(handle) then, the integer is decreased by 1.
// If the Integer is 0, the Index is released.
library SystemAffector
globals
integer array Affeted
endglobals
function Affect takes integer index returns nothing
set Affeted[index] = Affeted[index] + 1
endfunction
function DeAffect takes integer index returns boolean
set Affeted[index] = Affeted[index] - 1
if (Affeted[index] <= 0 ) then
return true
endif
return false
endfunction
endlibrary
library HandleIndexingSteel requires SystemAffector
globals
private constant integer MAX_HANDLE_ID = 150000
private constant integer tosub = 0x100000
private integer array StoredIDs[MAX_HANDLE_ID]
private integer IDs = 0
private integer gaps = 0
private integer array Gaps
endglobals
private function h2i takes handle h returns integer
return h
return 0
endfunction
function ReleaseIndex takes integer index returns nothing
set gaps = gaps + 1
set Gaps[gaps] = index
endfunction
function CreateIndex takes integer num returns integer
//! Recycle Indexes
if ( gaps > 0 ) then
set StoredIDs[num] = Gaps[gaps]
set gaps = gaps - 1
call Affect(StoredIDs[num])
return StoredIDs[num]
else
//! Create new Index
set IDs = IDs + 1
set StoredIDs[num] = IDs
if IDs > 8190 then
debug call BJDebugMsg("Handle Indexing; Created too many Indexes! Don't forget to call ReleaseIndex(handle). Trying to fix Issue. May cause Errors or bugs!")
return GetRandomInt(1,8190)
endif
call Affect(IDs)
return IDs
endif
endfunction
function GetIndex takes handle h returns integer
return StoredIDs[h2i(h)-tosub]
endfunction
function NewIndex takes handle h returns integer
local integer hashnum = h2i(h)-tosub
if ( StoredIDs[hashnum] == 0 ) then
return CreateIndex(hashnum)
else
call Affect(StoredIDs[hashnum])
return StoredIDs[hashnum]
endif
endfunction
function FlushIndex takes handle h returns integer
local integer num = h2i(h)-tosub
local integer re = 0
if ( DeAffect(StoredIDs[num]) ) then
call ReleaseIndex(StoredIDs[num])
set re = StoredIDs[num]
set StoredIDs[num] = 0
endif
return re
endfunction
endlibrary
Ok. However.
I said, that HandleIndexing is slower. Well. I did some tests with 500000 executions:
TEST with StopWatchMark
=== TESTED WITH WAR3ERR DISABLED===
With 'Safety':
HandleIndexing: 90% [Called 500k times Index and FlushIndex]
PUI: 100% [Called 500k times PeriodicRecycler and GetUnitIndex]
Without 'Safety':
HandleIndexing: 100% [Called 500k times Index and FlushIndex]
PUI: 94% [Called 500k times PeriodicRecycler and GetUnitIndex]
==> This is, what the systems actually do. A bit code was removed (when Indexes got higher than 8190).
So actually, this can be faster than PUI.
PUI is faster, when you call FlushIndex() more than three times a second. However, if you use this for units,
This can be faster, too. If you Flush the Index when a unit decays:
EDIT: New results. Updated. HandleIndexing is as fast as PUI. I think they're both almost identically fast.
http://peeeq.de/gui.php?id=773
Thats dynamic. Thats faster than PUI. Ok.
This system is faster than PUI in most usages, but you can't do anything wrong with PUI,
because it recycles automatically. You have to be more careful with HandleIndexing.
===> I was suprised, that the debug messages slower my system by ~35%!
However, if you use the map finally, you should remove the debug messages.
Note, that PeriodicRecycler isn't called as often as GetUnitIndex(), but if you play for very long, the Test might fit. Maybe PUI uses even more performance.
I didn't test UnitIndexingUtils, because I think, that it is the same as PUI, but PUI is safer, because it recycles Indexes automatically (Do you remember the possible bug I explained?)
And the testcode:
JASS:
library Bench requires PUI, HandleIndexing
globals
unit Temp = null
endglobals
function IND takes nothing returns nothing
local integer i = 0
call RemoveUnit(Temp)
set Temp = CreateUnit(Player(0),'hpea',0.,0.,0.)
loop
set i = i + 1
exitwhen i > 200
//CODE FOR THE SYSTEM. IF A SYSTEM IS TESTED ==> RESTART MAP.
endloop
endfunction
function Benchmark takes nothing returns nothing
local integer sw = StopWatchCreate()
local real settime1 = StopWatchMark(sw)
local real settime2 = StopWatchMark(sw)
local real t1
local real t0 = StopWatchMark(sw)
local integer i = 0
loop
set i = i + 1
exitwhen i > 2500
call TimerStart(CreateTimer(),0.,false,function IND)
endloop
// Insert code to be benchmarked here
set t1 = StopWatchMark(sw)
call BJDebugMsg("HandleIndexing: "+R2S(t1-t0-(settime2-settime1)))
endfunction
endlibrary
And a manual, how to improve coding with HandleIndexing:
JASS:
How do I use this perfectly?
If You want attach Data to many handles at the same time,
dont create a new Index for each handle. Create one index
for a handle and use this one for the others, too.
Use this in combination with Recycling. Then you can attach
the data to a recycled handle.
If you have private globals, you should better use CCComfort.
CCComfort converts a string into a unique integer.
You can use that int as index.
Note, that FlusIndex(handle) returns the index of the handle.
So, if you want perfect performance when you Flush Data, you
should do this:
local integer index = FlushIndex(handle)
set YourData[index] = 0
set YourString[index] = ""
set YourUnit[index] = null
===> This is for a better performance, because if you dont
clear globals you use with HandleIndexing, they suck
memory all the time.
I dont know why, but this:
debug if ( hashnum > MAX_HANDLE_ID_COUNT ) then
debug call BJDebugMsg("Handle Indexes; Index of handle too big. Increae 'MAX_HANDLE_ID_COUNT'!")
debug endif
costs a lot of performance. If you know, that HandleIndexing works, remove this.
Last edited: