Bribe
vJass errors are legion
- Reaction score
- 67
Many of us are aware that hashtables have a 255-instance limit. Many of us could also agree the hashtable API is some of the most ugly (albeit arguably the most dynamic) in the whole common.j library.
Table, based on Vexorian's Table, takes one hashtable far beyond what would otherwise be limitations in usability. This library essentially hashes the hashtable many, many, many times over, basically as many times as you need, and allows the complete hashtable API to be used for all existing handle-types.
The way I see it, most people will never hit the 255 limit. But when I think about it philosophically, the further away we can get from that limit, the better.
This library does just that, and in the most efficient way possible. If you use it properly, you will never need to call InitHashtable() again.
The basis of how it works is that you often run into situations where you have no use for the Parent-Key, only the child-key, to store data. Usually you just waste the parent-key as the index of 0 and run everything through child-keys. This system (as well as Vexorian's Table) simply gives you a parent-key to use in a globally-shared hashtable. Just initialize a Table instance via [ljass]Table.create()[/ljass].
But sometimes you need more than that, and the parent-key actually means something to you. Usually the parent-key would be "this" from a struct and the child-keys are various rubbish associated further within. Welcome to the concept of a TableArray - an array of Tables. Instanciate a Table array via [ljass]TableArray[16][/ljass] for an array of Tables sized 16, or [ljass]TableArray[8191][/ljass] for an array of Tables for every valid struct instance. The parameter 16/8191 can really be any array size, but try to keep it under ten or twenty billion if you can (lol?), because integers can only go into the trillions. If you do need a parent-key that high you could just use a Table instead of a parent-key, and store tables within that table (or even store a ParentTable within it). This adds a hashtable lookup but if you need indexing of that magnitude it should be on rare occasions anyway - so it doesn't matter.
Point is, this is the last hashtable you'll ever need, and in comfortable syntax once you get used to it. Enjoy.
Example Usage of Table:
Example Usage of TableArray:
Note the power of TableArray, how it splices the one hashtable into a many-dimensional array. This means you can have a multi-dimensional array (and more) per system.
Backwards-compatible add-on:
Table, based on Vexorian's Table, takes one hashtable far beyond what would otherwise be limitations in usability. This library essentially hashes the hashtable many, many, many times over, basically as many times as you need, and allows the complete hashtable API to be used for all existing handle-types.
The way I see it, most people will never hit the 255 limit. But when I think about it philosophically, the further away we can get from that limit, the better.
This library does just that, and in the most efficient way possible. If you use it properly, you will never need to call InitHashtable() again.
The basis of how it works is that you often run into situations where you have no use for the Parent-Key, only the child-key, to store data. Usually you just waste the parent-key as the index of 0 and run everything through child-keys. This system (as well as Vexorian's Table) simply gives you a parent-key to use in a globally-shared hashtable. Just initialize a Table instance via [ljass]Table.create()[/ljass].
But sometimes you need more than that, and the parent-key actually means something to you. Usually the parent-key would be "this" from a struct and the child-keys are various rubbish associated further within. Welcome to the concept of a TableArray - an array of Tables. Instanciate a Table array via [ljass]TableArray[16][/ljass] for an array of Tables sized 16, or [ljass]TableArray[8191][/ljass] for an array of Tables for every valid struct instance. The parameter 16/8191 can really be any array size, but try to keep it under ten or twenty billion if you can (lol?), because integers can only go into the trillions. If you do need a parent-key that high you could just use a Table instead of a parent-key, and store tables within that table (or even store a ParentTable within it). This adds a hashtable lookup but if you need indexing of that magnitude it should be on rare occasions anyway - so it doesn't matter.
Point is, this is the last hashtable you'll ever need, and in comfortable syntax once you get used to it. Enjoy.
JASS:
library Table // made by Bribe, special thanks to Nestharus, v2.1.0.0
globals
private hashtable ht = InitHashtable() // The last hashtable you need
private integer grow = 2 // Index generation for Tables (above 2)
private integer keys = 0 // Index generation for TableArrays (below 0)
private integer array list
private integer lpos = 0 // These two are used to recycle Tables
endglobals
//! textmacro NEW_TABLE takes SUPER, FUNC, TYPE, LOAD
private struct $TYPE$s extends array
static if ($LOAD$) then
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
endif
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! runtextmacro NEW_TABLE("Real", "Real", "real", "true")
//! runtextmacro NEW_TABLE("Boolean", "Boolean", "boolean", "true")
//! runtextmacro NEW_TABLE("String", "Str", "string", "true")
//! runtextmacro NEW_TABLE("Handle", "PlayerHandle", "player", "true")
//! runtextmacro NEW_TABLE("Handle", "WidgetHandle", "widget", "true")
//! runtextmacro NEW_TABLE("Handle", "DestructableHandle", "destructable", "true")
//! runtextmacro NEW_TABLE("Handle", "ItemHandle", "item", "true")
//! runtextmacro NEW_TABLE("Handle", "UnitHandle", "unit", "true")
//! runtextmacro NEW_TABLE("Handle", "AbilityHandle", "ability", "true")
//! runtextmacro NEW_TABLE("Handle", "TimerHandle", "timer", "true")
//! runtextmacro NEW_TABLE("Handle", "TriggerHandle", "trigger", "true")
//! runtextmacro NEW_TABLE("Handle", "TriggerConditionHandle", "triggercondition", "true")
//! runtextmacro NEW_TABLE("Handle", "TriggerActionHandle", "triggeraction", "true")
//! runtextmacro NEW_TABLE("Handle", "TriggerEventHandle", "event", "true")
//! runtextmacro NEW_TABLE("Handle", "ForceHandle", "force", "true")
//! runtextmacro NEW_TABLE("Handle", "GroupHandle", "group", "true")
//! runtextmacro NEW_TABLE("Handle", "LocationHandle", "location", "true")
//! runtextmacro NEW_TABLE("Handle", "RectHandle", "rect", "true")
//! runtextmacro NEW_TABLE("Handle", "BooleanExprHandle", "boolexpr", "true")
//! runtextmacro NEW_TABLE("Handle", "SoundHandle", "sound", "true")
//! runtextmacro NEW_TABLE("Handle", "EffectHandle", "effect", "true")
//! runtextmacro NEW_TABLE("Handle", "UnitPoolHandle", "unitpool", "true")
//! runtextmacro NEW_TABLE("Handle", "ItemPoolHandle", "itempool", "true")
//! runtextmacro NEW_TABLE("Handle", "QuestHandle", "quest", "true")
//! runtextmacro NEW_TABLE("Handle", "QuestItemHandle", "questitem", "true")
//! runtextmacro NEW_TABLE("Handle", "DefeatConditionHandle", "defeatcondition", "true")
//! runtextmacro NEW_TABLE("Handle", "TimerDialogHandle", "timerdialog", "true")
//! runtextmacro NEW_TABLE("Handle", "LeaderboardHandle", "leaderboard", "true")
//! runtextmacro NEW_TABLE("Handle", "MultiboardHandle", "multiboard", "true")
//! runtextmacro NEW_TABLE("Handle", "MultiboardItemHandle", "multiboarditem", "true")
//! runtextmacro NEW_TABLE("Handle", "TrackableHandle", "trackable", "true")
//! runtextmacro NEW_TABLE("Handle", "DialogHandle", "dialog", "true")
//! runtextmacro NEW_TABLE("Handle", "ButtonHandle", "button", "true")
//! runtextmacro NEW_TABLE("Handle", "TextTagHandle", "texttag", "true")
//! runtextmacro NEW_TABLE("Handle", "LightningHandle", "lightning", "true")
//! runtextmacro NEW_TABLE("Handle", "ImageHandle", "image", "true")
//! runtextmacro NEW_TABLE("Handle", "UbersplatHandle", "ubersplat", "true")
//! runtextmacro NEW_TABLE("Handle", "RegionHandle", "region", "true")
//! runtextmacro NEW_TABLE("Handle", "FogStateHandle", "fogstate", "true")
//! runtextmacro NEW_TABLE("Handle", "FogModifierHandle", "fogmodifier", "true")
//! runtextmacro NEW_TABLE("Handle", "AgentHandle", "agent", "false")
//! runtextmacro NEW_TABLE("Handle", "HashtableHandle", "hashtable", "true")
struct Table extends array
// Implement modules for intuitive type-syntax
implement realm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement agentm
implement hashtablem
debug private static integer debugOverflow = 0
// set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key)
endmethod
// set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb)
endmethod
// set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key)
endmethod
// call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key)
endmethod
// Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
// local Table tb = Table.create()
static method create takes nothing returns Table
if (lpos == 0) then
set grow = grow + 1
return grow
endif
set lpos = lpos - 1
debug call Table(2).boolean.remove(list[lpos])
return list[lpos]
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
call this.flush()
static if (DEBUG_MODE) then
if (integer(this) < 3) then
call BJDebugMsg("Table Error: Tried to destroy an invalid Table instance: " + I2S(this))
return
elseif (Table(2).boolean[this]) then
call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
return
endif
// The reserved Table(2) index detects double-free of instances
// if running in debug mode.
set Table(2).boolean[this] = true
endif
if (lpos < 8191) then
set list[lpos] = this
set lpos = lpos + 1
debug else
debug set debugOverflow = debugOverflow + 1
debug call BJDebugMsg("Table Error: Instance" + I2S(this) + " could not fit in the recycle stack. Overflows: " + I2S(debugOverflow))
endif
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
// Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[arraySize]
//
static method operator [] takes integer arraySize returns TableArray
local Table tb = Table(1)[arraySize] // Table(1) indexes arraySizes
local TableArray ta // and instances.
local integer i
debug if (arraySize <= 0) then
debug call BJDebugMsg("TableArray Error: Invalid specified array size: " + I2S(arraySize))
debug return 0
debug endif
if (integer(tb) == 0 or tb[0] == 0) then
set keys = keys - arraySize // Negative values are reserved...
set Table(1)[keys] = arraySize // This remembers the array size
set ta = keys // All TableArray IDs are below 0
else
set i = tb[0] // Get the last-destroyed TableArray's index
call tb.remove(0) // Clear data as we go along
set tb[0] = i - 1 // Decrease and save the recycle count
set ta = tb<i> // Retrieve the old TableArray's instance
call tb.remove(i) // Remove the old TableArray's node
debug call Table(2).boolean.remove(ta)
endif
return ta
endmethod
// Returns the size of the TableArray (arraySize)
method operator size takes nothing returns integer
return Table(1)[this]
endmethod
// ta[integer a].unit[integer b] = unit u
// ta[integer a][integer c] = integer d
//
// Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if (DEBUG_MODE) then
if (integer(this) >= 0) then
call BJDebugMsg("TableArray Error: " + I2S(this) + " is not a valid TableArray instance")
return 0
endif
if (key < 0 or key >= this.size) then
call BJDebugMsg("TableArray Error: Tried to lookup key [" + I2S(key) + "] which is outside array bounds [" + I2S(this.size) + "]")
return 0
endif
endif
return this + key
endmethod
// Destroys a TableArray without flushing it; assumed you'd call .flush()
// if you want it flushed too. This is public so that if you are flushing
// instances the whole time you don't waste efficiency when disposing the
// TableArray.
//
method destroy takes nothing returns nothing
local integer i
local Table tb = Table(1)[this.size]
static if (DEBUG_MODE) then
if (integer(this) >= 0 or this.size <= 0) then
call BJDebugMsg("TableArray Error: Tried to destroy an invalid instance (" + I2S(this) + ")")
return
elseif (Table(2).boolean[this]) then
call BJDebugMsg("TableArray Error: Tried to double-free instance: " + I2S(this))
return
endif
set Table(2).boolean[this] = true
endif
if (integer(tb) == 0) then
set tb = Table.create() // A Table to remember old indexes
set Table(1)[this.size] = tb // Save it in the reserved key (1)
set i = 1 // The recycle count is initially 1
else
set i = tb[0] + 1 // Increase recycle count
call tb.remove(0) // Remove the "recycle count" node
endif
set tb[0] = i // Save recycle count
set tb<i> = this // Save this under recycle count's index
endmethod
// All you need to know about this one is that it won't hit the op limit.
private static method clean takes Table tb, integer end returns nothing
local integer i = tb + 4096
if (i < end) then
call clean.evaluate(i, end)
set end = i
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen integer(tb) == end
endloop
endmethod
// Flushes the TableArray and also destroys it. Doesn't get any more
// similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
local integer end = this.size + this
if (integer(this) < end) then
call clean.evaluate(this, end)
call this.destroy()
debug else
debug call BJDebugMsg("TableArray Error: Tried to flush an invalid instance (" + I2S(this) + ")")
endif
endmethod
endstruct
endlibrary
</i></i>
Example Usage of Table:
JASS:
private static Table tb // Declare it
...
set tb = Table.create() // Create it
...
local boolean b = tb.has(69)
set tb[0] = 039;A039; // Unlock its power
set tb[1] = 039;B039;
set tb.unit[2] = GetTriggerUnit()
set tb.unit[3] = GetSpellTargetUnit()
set tb.real[4] = 3.14159
...
call tb.destroy() // Flush and destroy it
or...
call tb.flush() // Only flushes it
Example Usage of TableArray:
JASS:
private static TableArray ta // Declare it
...
set ta = TableArray[8191] // Create it
...
local thistype this = 0
loop // Unlock its power
set this = this.next
exitwhen this == 0
set this.save = this.save + 1
set ta[this].real[this.save * 3] = GetUnitX(this.unit)
set ta[this].real[this.save * 3 + 1] = GetUnitY(this.unit)
set ta[this].real[this.save * 3 + 2] = GetUnitFlyHeight(this.unit)
endloop
...
call ta.flush() // Flush and destroy it
or...
call ta.destroy() // Only destroys it (more efficient if you already
// flushed everything manually).
Note the power of TableArray, how it splices the one hashtable into a many-dimensional array. This means you can have a multi-dimensional array (and more) per system.
Backwards-compatible add-on:
JASS:
library TableBC requires Table
/*
Backwards-compatibility add-on for scripts employing Vexorian's Table.
Disclaimer:
The following error does not occur with HandleTables & StringTables, only
with the standard, integer-based Table, so you do not need to make any
changes to StringTable/HandleTable-employing scripts.
The this.flush(key) method from the original Table cannot be parsed with
the new Table. For the scripts that use this method, they need to be up-
dated to use the more fitting this.remove(key) method.
Please don't try using StringTables/HandleTables with features exclusive
to the new Table as they will cause syntax errors. I do not have any plan
to endorse these types of Tables because delegation in JassHelper is not
advanced enough for three types of Tables without copying every single
method over again (as you can see this already generates plenty of code).
StringTable & HandleTable are wrappers for StringHash & GetHandleId, so
just type them out.
*/
//! textmacro TABLE_BC_METHODS
method reset takes nothing returns nothing
call this.flush()
endmethod
method exists takes integer key returns boolean
return this.has(key)
endmethod
//! endtextmacro
//! textmacro TABLE_BC_STRUCTS
struct HandleTable extends array
method operator [] takes handle key returns integer
return Table(this)[GetHandleId(key)]
endmethod
method operator []= takes handle key, integer value returns nothing
set Table(this)[GetHandleId(key)] = value
endmethod
method flush takes handle key returns nothing
call Table(this).remove(GetHandleId(key))
endmethod
method exists takes handle key returns boolean
return Table(this).has(GetHandleId(key))
endmethod
method reset takes nothing returns nothing
call Table(this).flush()
endmethod
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
struct StringTable extends array
method operator [] takes string key returns integer
return Table(this)[StringHash(key)]
endmethod
method operator []= takes string key, integer value returns nothing
set Table(this)[StringHash(key)] = value
endmethod
method flush takes string key returns nothing
call Table(this).remove(StringHash(key))
endmethod
method exists takes string key returns boolean
return Table(this).has(StringHash(key))
endmethod
method reset takes nothing returns nothing
call Table(this).flush()
endmethod
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
//! endtextmacro
endlibrary