Snippet Table

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.

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&#039;s instance
            call tb.remove(i) // Remove the old TableArray&#039;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) &gt;= 0) then
                call BJDebugMsg(&quot;TableArray Error: &quot; + I2S(this) + &quot; is not a valid TableArray instance&quot;)
                return 0
            endif
            if (key &lt; 0 or key &gt;= this.size) then
                call BJDebugMsg(&quot;TableArray Error: Tried to lookup key [&quot; + I2S(key) + &quot;] which is outside array bounds [&quot; + I2S(this.size) + &quot;]&quot;)
                return 0
            endif
        endif
        return this + key
    endmethod
    
    // Destroys a TableArray without flushing it; assumed you&#039;d call .flush()
    // if you want it flushed too. This is public so that if you are flushing
    // instances the whole time you don&#039;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) &gt;= 0 or this.size &lt;= 0) then
                call BJDebugMsg(&quot;TableArray Error: Tried to destroy an invalid instance (&quot; + I2S(this) + &quot;)&quot;)
                return
            elseif (Table(2).boolean[this]) then
                call BJDebugMsg(&quot;TableArray Error: Tried to double-free instance: &quot; + 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 &quot;recycle count&quot; node
        endif
        set tb[0] = i    // Save recycle count
        set tb<i> = this // Save this under recycle count&#039;s index
    endmethod
    
    // All you need to know about this one is that it won&#039;t hit the op limit.
    private static method clean takes Table tb, integer end returns nothing
        local integer i = tb + 4096
        if (i &lt; 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&#039;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) &lt; end) then
            call clean.evaluate(this, end)
            call this.destroy()
        debug else
            debug call BJDebugMsg(&quot;TableArray Error: Tried to flush an invalid instance (&quot; + I2S(this) + &quot;)&quot;)
        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;A&#039;                 // Unlock its power
set tb[1] = &#039;B&#039;
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&#039;s Table.
    
    Disclaimer:
    
    The following error does not occur with HandleTables &amp; 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&#039;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 &amp; HandleTable are wrappers for StringHash &amp; 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
 

tooltiperror

Super Moderator
Reaction score
231
You should make it possible to use this with the same exact syntax as the original Table for compatibility.
 

Bribe

vJass errors are legion
Reaction score
67
The .flush() method is different in this version, .exists() I can make a wrapper for, HandleTable and StringTable yeah I could make as optional library add-ons. Thanks for the tip ;)
 

tooltiperror

Super Moderator
Reaction score
231
Yeah, I tend to like compatibility as just about everything lacks it (even NewGen)!

Wrapping is always nice, yes. Vexorian was such a pioneer, made all these interesting libraries that inspired other JASSers to keep moving. Very emotional, JASSing is.

Also, I find it better to do things like this.

JASS:
globals
        private constant boolean DEBUG = DEBUG_MODE
endglobals

static if (DEBUG) then
        //
endif


Because I've heard of people using debug for funky things, like debug for beta testing but it's not a complete debug stage weirdness.
 

Bribe

vJass errors are legion
Reaction score
67
This needs no benchmark. .create and .destroy are obviously faster and the method calls are the exact same speed as Table 3.0.

I have released this thing with optional backwards-compatibility, all you need to do is include the TableDelegate library in the map. I have to check to make sure the .flush() method won't cause a syntax error with the backwards-compatibility part, but otherwise the Table library is completely stable.
 

Weep

Godspeed to the sound of the pounding
Reaction score
400
Seems useful; I've wanted a Table for reals before. Which parts of this don't follow Vex's Table's syntax by default, again?
 

Bribe

vJass errors are legion
Reaction score
67
Vexorian's Table:

.exists() method for HaveSavedInteger
.flush() method for RemoveSavedInteger
.reset() method for FlushChildHashtable

Bribe's Table (API changed to fit more with the new hashtabe language)

.has() method for HaveSavedInteger/Handle/Real/etc.
.remove() method for RemoveSavedInteger/Handle/Real/etc.
.flush() method for FlushChildHashtable
 

Bribe

vJass errors are legion
Reaction score
67
Updated. The extension is now as optimal as possible but added a disclaimer for people who want to add the TableBC library.

In 99% of cases, backwards compatibility can be granted just by including the TableBC library in the map.

If anyone was using the .flush(integer key) method from regular tables, that will give a syntax error and has to be replaced with .remove(integer key). There is no way to make that parse without #define syntax.
 

Sim

Forum Administrator
Staff member
Reaction score
534
Approved.

Edit: I assume that JassHelper error doesn't happen anymore?
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top