[Lua][vJass] New Table


Lua's tables make both Vexorian and my vJass Table libraries obsolete. However, for ease of converting vJass scripts to Lua, I've provided the below.
Lua:
Table       = {}
HandleTable = Table
StringTable = Table

--One map, no hashtables. Welcome to Lua Table version 1.1.1.0
--Made by Bribe, special thanks to Vexorian

function Table.create()
    return setmetatable({}, Table)
end

function Table:has(key)             --Bribe's API
    return rawget(self, key)~=nil
end
Table.exists=Table.has              --Vexorian's API

--Vexorian's API:
function Table:reset()
    for key in pairs(self) do
        self[key]=nil
    end
end
Table.destroy=Table.reset --Destruction only exists for backwards-compatibility. Lua's GC will handle this.

--Bribe's API:
function Table:remove(key)
    self[key]=nil
end

function Table:flush(key)
    if key then
        self[key]=nil   --Vexorian's version of flush
    else
        self:reset()    --Bribe's version of flush
    end
end

do
    local repeater
    local function makeRepeater(parent, key)
        local new=rawget(TableArray, key)
        if not new then
            new=Table.create()
            rawset(parent, key, new)
        end
        return new
    end
    local function create2D(_, size)
        if not repeater then
            repeater={__index=makeRepeater}
        end
        return setmetatable({size=size}, repeater)
    end
    HashTable={
        create=create2D,
        flush=Table.reset,
        destroy=Table.destroy,
        remove=Table.remove,
        has=Table.has
    }
    TableArray=setmetatable(HashTable, {__index=create2D})

    --Create a table just to store the types that will be ignored
    local dump={
        handle=true,          agent=true,       real=true,              boolean=true,
        string=true,          integer=true,     player=true,            widget=true,
        destructable=true,    item=true,        unit=true,              ability=true,
        timer=true,           trigger=true,     triggercondition=true,  triggeraction=true,
        event=true,           force=true,       group=true,             location=true,
        rect=true,            boolexpr=true,    sound=true,             effect=true,
        unitpool=true,        itempool=true,    quest=true,             questitem=true,
        defeatcondition=true, timerdialog=true, leaderboard=true,       multiboard=true,
        multiboarditem=true,  trackable=true,   dialog=true,            button=true,
        texttag=true,         lightning=true,   image=true,             ubersplat=true,
        region=true,          fogstate=true,    fogmodifier=true,       hashtable=true
    }
    --Create a table that handles Vexorian's static 2D Table syntax.
    local indexer2D={}
    function Table.flush2D(index)
        indexer2D[index]=nil
    end
  
    function Table:__index(index)
        local get
        if self==Table then
            --static method operator (for supporting Vexorian's static 2D Table syntax):
            get=indexer2D[index]
            if get then
                return get
            end
            get=Table.create()
            self=indexer2D
        else
            --regular method operator (but only called when the value wasn't found, or if nothing was assigned yet):
            get=dump[index] and self
            if not get then
                return
            end
        end
        rawset(self, index, get) --cache for better performance on subsequent calls
        return get
    end
end
setmetatable(Table, Table)


Table is based on the philosophy that you can use one hashtable for your whole map. What it does is divide one hashtable into many different components, and each system in the map can have its own share of the hashtable. Taking advantage of parent keys and child keys to their fullest extent, the ability to have 1 hashtable for the whole map can be realized.

I came up with the idea for this project after using Vexorian's Table and hitting the limits a number of times. All of those limitations have been fulfilled by this project:
  1. You have access to all the hashtable API, so you can now save handles, booleans, reals, strings and integers, instead of just integers.
  2. You can have up to 2 ^ 31 - 1 Table instances. Previously, you could have 400,000 if you set the constant appropriately, but that generated hundreds of lines of code. Not basing Tables on array indices also means you don't ever have to worry about creating too many Tables.
  3. Multi-dimensional array syntax is perfected and allows you to create Tables with unlimited depth and complexity.
  4. Table instances can save/load data from within module initializers. This is a stark improvement over Vexorian's Table, as that hashtable was initialized from a struct instead of from the globals block.
While you can only have 256 hashtables at a time, Table provides 2 ^ 31 - 1 instances at your disposal and (that's right, I said "and", not "or") roughly 2 ^ 18 fork-Tables with 8192 parallel instances, you will likely find yourself with more storage options than you know what to do with.

If you take advantage of this system to its fullest, you will never need to call InitHashtable() again.


The basis of how it works is this: you often run into situations where you have no use for two keys (only a single key). Usually you just waste a key as 0 and run everything else 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 an Table instance via Table.create().

But sometimes you need more than that, and both keys actually mean something to you. Usually the parent-key would be "this" from a struct and the child-keys are various other bits. You have many ways to branch your Table into any number of dimensions through table.link, table.join, table.bind, Table.createFork, table.forkLink, table.forkJoin and table.forkBind.


JASS:
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 6

    One map, one hashtable, one Table struct.

    Version 6 combines multi-dimensional Tables, TableArray, HandleTable and StringTable into...

        ...Table.

    What's changed? What does the API look like now? Read on to find out:

    Universal Table operations:
    | static method create takes nothing returns Table
    |     create a new Table
    |
    | method destroy takes nothing returns nothing
    |     flush and destroy the Table.
    |
    |     NOTE: this method incorporates the former TableArray.flush() method.
    |
    | method flush takes nothing returns nothing
    |     Erase all saved values inside of the Table
    |
    |     NOTE: This does not flush all parallel instances in a forked Table.
    |           It only flushes the current Table instance.
    |
    | static method createFork takes integer width returns Table
    |     creates a Table that is a negative integer rather than positive one.
    |
    |     NOTE: formerly, this was `static method operator []` on TableArray.
    |
    | method switch takes integer offset returns Table
    |     switches from the current Table to any offset between 0 and 'width - 1'
    |
    |     NOTE: formerly, this was `method operator []` on TableArray.
    |
    |     NOTE: Only works with Tables created as forks.
    |
    | method measureFork takes nothing returns integer
    |     returns the number passed to `createFork`.
    |     formerly, this was `method size` on TableArray.
    |
    | method getKeys takes nothing returns Table
    |     returns a readonly Table containing:
    |       [0] - the number of tracked keys in the Table
    |       [1->number of keys] - contiguous list of keys found inside of the Table.
    |           Effectively combines Object.keys(obj) from JavaScript with 1-based arrays of Lua.
    |
    | -> How to iterate to access values:
    |
    |     local Table keys = myTable.getKeys()
    |     local integer i = keys[0]
    |     loop
    |         exitwhen i == 0
    |         call BJDebugMsg("My value is: " + I2S(myTable[keys[i]]))
    |         set i = i - 1
    |     endloop
    |

    Standard Table operations (store Tables or integers against integer keys):
    | method operator [] takes integer key returns Table
    |     load the value at index `key`
    |
    | method operator []= takes integer key, Table value returns nothing
    |     assign "value" to index `key` without tracking it. Like `rawset` in Lua.
    |
    | method remove takes integer key returns nothing
    |     remove the value at index `key`
    |
    | method has takes integer key returns boolean
    |     whether or not `key` has been assigned
    |
    | method save takes integer key, Table value returns Table
    |     a new method to not just add the value to the Table, but to also track it.
    |     Returns `this` Table, so `save` calls can be daisy-chained.
    |

    Multi-Dimensional Table operations (store nested Tables against integer keys):
    | method link takes integer key returns Table
    |     Checks if a Table already exists at the key, and creates one if not.
    |
    |     Note: Formerly, you needed to create a Table2/3/4/5D/T to access this method.
    |     Note: Can be substituted for `[]` if you are certain that the key is set.
    |
    |     Note: Links are all tracked by default. To avoid the extra tracking behavior,
    |           you can either use a static Table (`struct.typeid` or `key`) or you can
    |           use Table.createFork(1) instead of Table.create() or
    |           myTable.forkLink(1, key) instead of myTable.link(key)
    |
    | method forkLink takes integer width, integer key returns Table
    |       Checks if a Fork already exists at the key, and creates one with the chosen size if not.

    HandleTable operations (store Tables against handle keys):
    | method operator get takes handle key returns Tables
    |     Alias for `[]`
    |
    | method operator store takes handle key, Table value returns Table
    |     Alias for `save`
    |
    | method forget takes handle key returns nothing
    |     Alias for `remove`
    |
    | method stores takes handle key returns boolean
    |     Alias for `has`
    |
    | method bind takes handle key returns Table
    |     Alias for `link`
    |
    | method forkBind takes handle key returns Table
    |     Alias for `forkLink`

    StringTable operations (store Tables against string keys):
    | method operator read takes string key returns Table
    |     Alias for `[]`
    |
    | method operator write takes string key, Table value returns Table
    |     Alias for `save`
    |
    | method delete takes string key returns nothing
    |     Alias for `remove`
    |
    | method written takes string key returns boolean
    |     Alias for `has`
    |
    | method join takes string key returns Table
    |     Alias for `link`
    |
    | method forkJoin takes string key returns Table
    |     Alias for `forkLink`

    Tables that store non-integer/Table values can access the `standard` Table API just by using .type syntax:
    | myTable.unit[5] = GetTriggerUnit()
    | myTable.unit.save(10, GetTriggerUnit())
    | myTable.unit.store(GetAttacker(), GetTriggerUnit())
    | myTable.unit.write("whatever you want", GetTriggerUnit())
    |
    | myTable.handle.remove(10)
    | myTable.handle.forget(GetAttacker())
    | myTable.handle.delete("whatever you want")
    |
    | local string s = myTable.string[15]
    | local string t = myTable.string.get(GetSpellTargetUnit())
    | local string u = myTable.string.read("something you want")
    |
    | local boolean b = myTable.string.has(15)
    | local boolean c = myTable.string.stores(GetSpellTargetUnit())
    | local boolean d = myTable.string.written("something you want")

*/ requires optional Table5BC, optional TableVBC

globals
    private integer tableKeyGen = 8190  //Index generation for Tables starts from here. Configure it if your map contains more than this many structs or 'key' objects.

    private hashtable hashTable = InitHashtable() // The last hashtable.

    private constant boolean TEST = false      // set to `true` to enable error messages and `print`/`toString` API.
    private constant boolean DEEP_TEST = false // set to `true` to enable informational messages.

    private keyword addKey
    private keyword addTypedKey

    private keyword IntegerModule
    private keyword RealModule
    private keyword BooleanModule
    private keyword StringModule
    private keyword PlayerModule
    private keyword WidgetModule
    private keyword DestructableModule
    private keyword ItemModule
    private keyword UnitModule
    private keyword AbilityModule
    private keyword TimerModule
    private keyword TriggerModule
    private keyword TriggerConditionModule
    private keyword TriggerActionModule
    private keyword TriggerEventModule
    private keyword ForceModule
    private keyword GroupModule
    private keyword LocationModule
    private keyword RectModule
    private keyword BooleanExprModule
    private keyword SoundModule
    private keyword EffectModule
    private keyword UnitPoolModule
    private keyword ItemPoolModule
    private keyword QuestModule
    private keyword QuestItemModule
    private keyword DefeatConditionModule
    private keyword TimerDialogModule
    private keyword LeaderboardModule
    private keyword MultiboardModule
    private keyword MultiboardItemModule
    private keyword TrackableModule
    private keyword DialogModule
    private keyword ButtonModule
    private keyword TextTagModule
    private keyword LightningModule
    private keyword ImageModule
    private keyword UbersplatModule
    private keyword RegionModule
    private keyword FogStateModule
    private keyword FogModifierModule
    private keyword HashtableModule
    private keyword FrameModule

    private keyword AgentStruct
    private keyword HandleStruct
    private keyword IntegerStruct
    private keyword RealStruct
    private keyword BooleanStruct
    private keyword StringStruct
    private keyword PlayerStruct
    private keyword WidgetStruct
    private keyword DestructableStruct
    private keyword ItemStruct
    private keyword UnitStruct
    private keyword AbilityStruct
    private keyword TimerStruct
    private keyword TriggerStruct
    private keyword TriggerConditionStruct
    private keyword TriggerActionStruct
    private keyword TriggerEventStruct
    private keyword ForceStruct
    private keyword GroupStruct
    private keyword LocationStruct
    private keyword RectStruct
    private keyword BooleanExprStruct
    private keyword SoundStruct
    private keyword EffectStruct
    private keyword UnitPoolStruct
    private keyword ItemPoolStruct
    private keyword QuestStruct
    private keyword QuestItemStruct
    private keyword DefeatConditionStruct
    private keyword TimerDialogStruct
    private keyword LeaderboardStruct
    private keyword MultiboardStruct
    private keyword MultiboardItemStruct
    private keyword TrackableStruct
    private keyword DialogStruct
    private keyword ButtonStruct
    private keyword TextTagStruct
    private keyword LightningStruct
    private keyword ImageStruct
    private keyword UbersplatStruct
    private keyword RegionStruct
    private keyword FogStateStruct
    private keyword FogModifierStruct
    private keyword HashtableStruct
    private keyword FrameStruct
endglobals

struct Table extends array
    private static method operator shadowTable takes nothing returns Table
        // Table:Table:integer|real|string
        return HashtableStruct.typeid
    endmethod
    private static method operator parentTable takes nothing returns Table
        // Table:Table
        return RealStruct.typeid
    endmethod
    private static method operator hasChildTables takes nothing returns Table
        // Table:boolean
        return BooleanStruct.typeid
    endmethod
    private static method operator seenTables takes nothing returns Table
        // Table:boolean
        return StringStruct.typeid
    endmethod
    private static method operator instanceData takes nothing returns Table
        // Table:integer
        return Table.typeid
    endmethod
    private static method operator widths takes nothing returns Table
        // Table:integer
        return HandleStruct.typeid
    endmethod
    private static method operator recycledArrays takes nothing returns Table
        // The same table, but with a better name for its purpose.
        return HandleStruct.typeid
    endmethod
    private static integer forkKeyGen = 0
    private static boolean isShadow = false
    private static integer cleanUntil
    private static Table tableToClean

    method operator [] takes integer key returns Table
        return LoadInteger(hashTable, this, key)
    endmethod
    method read takes string key returns Table
        return this[StringHash(key)]
    endmethod
    method get takes handle key returns Table
        return this[GetHandleId(key)]
    endmethod

    method operator []= takes integer key, Table value returns nothing
        call SaveInteger(hashTable, this, key, value)
    endmethod

    method has takes integer key returns boolean
        return HaveSavedInteger(hashTable, this, key)
    endmethod
    method written takes string key returns boolean
        return this.has(StringHash(key))
    endmethod
    method stores takes handle key returns boolean
        return this.has(GetHandleId(key))
    endmethod

    // Remove all keys and values from a Table instance
    method flush takes nothing returns nothing
        local Table shadow = shadowTable[this]
        call FlushChildHashtable(hashTable, this)
        if this > 0 and shadow > 0 then
            call FlushChildHashtable(hashTable, shadow)
        endif
        call RemoveSavedBoolean(hashTable, hasChildTables, this)
    endmethod

    // This method enables quick table[parentIndex][childIndex].
    //
    // local Table table = Table.createFork(3)
    // set table[15] = 40 // index 0 remains on the same table, so there is no need to switch to one of the parallel tables.
    // set table.switch(1).unit[5] = GetTriggerUnit()
    // set table.switch(2)[10] = 20
    //
    // Inline-friendly when not running in `TEST` mode
    //
    method switch takes integer key returns Table
        static if TEST then
            local integer i = widths[this]
            if i == 0 then
                call BJDebugMsg("Table.switch Error: Tried to invoke 'switch' method on invalid Table: " + I2S(this))
            elseif key < 0 or key >= i then
                call BJDebugMsg("Table.switch Error: Tried to get key [" + I2S(key) + "] from outside bounds: " + I2S(i))
            endif
        endif
        return this + key
    endmethod

    // Returns a new Table instance that can save/load any hashtable-compatible data type.
    static method create takes nothing returns Table
        local Table table = instanceData[0]

        if table == 0 then
            set table = tableKeyGen + 1
            set tableKeyGen = table
            static if DEEP_TEST then
                call BJDebugMsg("Table.create getting new index: " + I2S(table))
            endif
        else
            set instanceData[0] = instanceData[table]
            static if DEEP_TEST then
                call BJDebugMsg("Table.create recycling index: " + I2S(table))
            endif
        endif

        set instanceData[table] = -1

        if isShadow then
            set isShadow = false
        else
            set isShadow = true
            set shadowTable[table] = Table.create()
        endif

        return table
    endmethod

    private method recycle takes nothing returns nothing
        call this.flush()
        if instanceData[this] != -1 then
            static if TEST then
                call BJDebugMsg("Table.recycle Error: " + I2S(this) + " is already inactive!")
            endif
            return
        endif
        set instanceData[this] = instanceData[0]
        set instanceData[0] = this
    endmethod

    private method setInactive takes nothing returns nothing
        local Table shadow = shadowTable[this]
        call this.recycle()
        if shadow > 0 then
            static if DEEP_TEST then
                call BJDebugMsg("Setting " + I2S(this) + " and its shadow " + I2S(shadow) + " to inactive state.")
            endif
            call shadow.recycle()
            call RemoveSavedInteger(hashTable, shadowTable, this)
            call RemoveSavedInteger(hashTable, parentTable, this)
        else
            static if DEEP_TEST then
                call BJDebugMsg("Setting Table: " + I2S(this) + " to inactive state.")
            endif
        endif
    endmethod

    // Returns:
    // -1: invalid Table for this operation (key/typeid/fork Table, or simply a destroyed/incorrect reference to a Table).
    //  0: key does not exist yet in the Table
    // >0: key exists in the Table.
    method getKeyIndex takes integer key returns integer
        local Table shadow = shadowTable[this]
        if this <= 0 or shadow == 0 then
            return -1
        endif
        return R2I(LoadReal(hashTable, shadow, key))
    endmethod

    method addKey takes integer key returns nothing
        local Table shadow
        local integer i = this.getKeyIndex(key)
        if i == 0 then
            set shadow = shadowTable[this]
            set i = shadow[0] + 1
            set shadow[0] = i
            set shadow[i] = key
            call SaveReal(hashTable, shadow, key, i)
            static if DEEP_TEST then
                call BJDebugMsg("Increasing table " + I2S(this) + "'s' key size to " + I2S(i))
            endif
        endif
    endmethod

    static if TEST then
        method addTypedKey takes integer key, string whichType returns nothing
            local Table shadow = shadowTable[this]
            local string oldType = LoadStr(hashTable, shadow, key)
            if oldType != null and oldType != whichType then
                call BJDebugMsg("Table.addKey Error: Type " + whichType + " and " + oldType + " saved at key: " + I2S(key))
            endif
            call SaveStr(hashTable, shadow, key, whichType)
            call this.addKey(key)
        endmethod
    endif

    private method nestTable takes integer key, Table table returns nothing
        set this[key] = table
        static if TEST then
            call this.addTypedKey(key, "Table")
        else
            call this.addKey(key)
        endif
        set parentTable[table] = this
        call SaveBoolean(hashTable, hasChildTables, this, true)
    endmethod

    method link takes integer key returns Table
        local Table table = this[key]
        if table == 0 then
            set table = Table.create()
            static if DEEP_TEST then
                call BJDebugMsg("Table(" + I2S(this) + ")[" + I2S(key) + "] => Table(" + I2S(table) + ")")
            endif
        elseif instanceData[table] != -1 then
            static if TEST then
                call BJDebugMsg("Table.link Error: Invalid Table " + I2S(table) + " found at key " + I2S(key))
            endif
            return 0
        endif
        call this.nestTable(key, table)
        return table
    endmethod

    method save takes integer key, Table value returns Table
        static if TEST then
            call Table(this).addTypedKey(key, "Table")
        else
            call Table(this).addKey(key)
        endif
        set this[key] = value
        return this
    endmethod
    method write takes string key, Table value returns Table
        return this.save(StringHash(key), value)
    endmethod
    method store takes handle key, Table value returns Table
        return this.save(GetHandleId(key), value)
    endmethod

    method join takes string key returns Table
        return this.link(StringHash(key))
    endmethod

    method bind takes handle key returns Table
        return this.link(GetHandleId(key))
    endmethod

    private static method cleanFork takes nothing returns nothing
        local Table table = tableToClean
        local integer exitWhen = table + 0x1000
        if exitWhen < cleanUntil then
            set tableToClean = exitWhen
            //Avoids hitting the op limit
            call ForForce(bj_FORCE_PLAYER[0], function Table.cleanFork)
        else
            set exitWhen = cleanUntil
        endif
        loop
            exitwhen table == exitWhen
            call table.flush()
            set table = table + 1
        endloop
    endmethod

    private method destroyFork takes nothing returns boolean
        local integer width = widths[this]
        local Table recycled
        local integer i = width

        if this >= 0 or width == 0 then
            return false
        endif

        set tableToClean = this
        set cleanUntil = this + widths[this]
        call Table.cleanFork()

        call RemoveSavedInteger(hashTable, widths, this) //Clear the array size from hash memory

        set recycled = recycledArrays.link(width)
        set recycled[this] = recycled[0]
        set recycled[0] = this

        return true
    endmethod

    // Returns a special type of Table with `width` parallel indices.
    //
    // local Table fork = Table.fork(width)
    //
    static method createFork takes integer width returns Table
        local Table recycled = recycledArrays.link(width) //Get the unique recycle list for this array size
        local Table fork = recycled[0]                         //The last-destroyed fork that had this array size

        if width <= 0 then
            static if TEST then
                call BJDebugMsg("Table.createFork Error: Invalid specified width: " + I2S(width))
            endif
            return 0
        endif

        if fork == 0 then
            set fork = forkKeyGen - width // If we start with 8190, the first fork index will be -8190
            set forkKeyGen = fork
        else
            set recycled[0] = recycled[fork]  //Set the last destroyed to the last-last destroyed
            call RemoveSavedInteger(hashTable, recycled, fork)
        endif

        set widths[fork] = width
        return fork
    endmethod

    method forkLink takes integer width, integer key returns Table
        local Table table = this[key]
        if table == 0 then
            set table = Table.createFork(width)
        elseif widths[this] == 0 then
            static if TEST then
                call BJDebugMsg("Table.forkLink Error: Invalid Table " + I2S(table) + " found at key " + I2S(key))
            endif
            return 0
        endif
        call this.nestTable(key, table)
        return table
    endmethod

    method forkJoin takes integer width, string key returns Table
        return this.forkLink(width, StringHash(key))
    endmethod

    method forkBind takes integer width, handle key returns Table
        return this.forkLink(width, GetHandleId(key))
    endmethod

    method getKeys takes nothing returns integer
        local Table shadow = shadowTable[this]
        if this <= 0 or shadow == 0 then
            static if TEST then
                call BJDebugMsg("Table.getKeys Error: Called on invalid Table " + I2S(this))
            endif
            return 0
        endif
        return shadow
    endmethod

    method measureFork takes nothing returns integer
        return widths[this]
    endmethod

    private method destroyDeep takes nothing returns nothing
        local Table shadow = shadowTable[this]
        local integer i = shadow[0] //get the number of tracked indices
        local Table table

        static if DEEP_TEST then
            call BJDebugMsg("Destroying Table: " + I2S(this) + " and all of its child tables.")
        endif

        // Mark this table as seen to avoid potentially-infinite recursion
        call SaveBoolean(hashTable, seenTables, this, true)

        loop
            exitwhen i == 0
            // Get the actual table using the index from shadow
            set table = this[shadow[i]]
            if table > 0 then
                if instanceData[table] == -1 and /*
                    */ parentTable[table] == this and /*
                    */ not LoadBoolean(hashTable, seenTables, table) /*
                */ then
                    if LoadBoolean(hashTable, hasChildTables, table) then
                        call table.destroyDeep()
                    else
                        call table.setInactive()
                    endif
                endif
            elseif table < 0 then
                call this.destroyFork()
            endif
            set i = i - 1
        endloop
        call this.setInactive()
    endmethod

    // Removes all data from a Table instance and recycles its index.
    method destroy takes nothing returns nothing
        if instanceData[this] != -1 then
            if not this.destroyFork() then
                static if TEST then
                    call BJDebugMsg("Table.destroy Error: Inactive Table: " + I2S(this))
                endif
            endif
        else
            if LoadBoolean(hashTable, hasChildTables, this) then
                call this.destroyDeep()
                call FlushChildHashtable(hashTable, seenTables)
            else
                call this.setInactive()
            endif
        endif
    endmethod

    method removeKey takes integer key returns nothing
        local Table shadow
        local Table child = this[key]
        local integer i = this.getKeyIndex(key)
        local integer top
        if i > 0 then
            set shadow = shadowTable[this]
            static if TEST then
                call RemoveSavedString(hashTable, shadow, key)
            endif
            set top = shadow[0]
            if top == 1 then
                call FlushChildHashtable(hashTable, shadow)
            else
                call RemoveSavedReal(hashTable, shadow, key)
                set shadow[0] = top - 1
                if top != i then
                    set key = shadow[top]
                    set shadow[i] = key
                    call SaveReal(hashTable, shadow, key, i)
                endif
                call RemoveSavedInteger(hashTable, shadow, top)
            endif
        endif
        if child > 0 and /*
            */ instanceData[child] == -1 and /*
            */ parentTable[child] == this /*
        */ then
            call child.destroy()
        endif
    endmethod

    method remove takes integer key returns nothing
        call this.removeKey(key)
        call RemoveSavedInteger(hashTable, this, key)
    endmethod
    method delete takes string key returns nothing
        call this.remove(StringHash(key))
    endmethod
    method forget takes handle key returns nothing
        call this.remove(GetHandleId(key))
    endmethod

    method operator handle takes nothing returns HandleStruct
        return this
    endmethod

    method operator agent takes nothing returns AgentStruct
        return this
    endmethod

    // Implement modules for handle/agent/integer/real/boolean/string/etc syntax.
    implement IntegerModule
    implement RealModule
    implement BooleanModule
    implement StringModule
    implement PlayerModule
    implement WidgetModule
    implement DestructableModule
    implement ItemModule
    implement UnitModule
    implement AbilityModule
    implement TimerModule
    implement TriggerModule
    implement TriggerConditionModule
    implement TriggerActionModule
    implement TriggerEventModule
    implement ForceModule
    implement GroupModule
    implement LocationModule
    implement RectModule
    implement BooleanExprModule
    implement SoundModule
    implement EffectModule
    implement UnitPoolModule
    implement ItemPoolModule
    implement QuestModule
    implement QuestItemModule
    implement DefeatConditionModule
    implement TimerDialogModule
    implement LeaderboardModule
    implement MultiboardModule
    implement MultiboardItemModule
    implement TrackableModule
    implement DialogModule
    implement ButtonModule
    implement TextTagModule
    implement LightningModule
    implement ImageModule
    implement UbersplatModule
    implement RegionModule
    implement FogStateModule
    implement FogModifierModule
    implement HashtableModule
    implement FrameModule

    static if TEST then
        private method toStringFn takes integer depth returns string
            local Table shadow = shadowTable[this]
            local integer i = shadow[0]
            local string indent = ""
            local integer k = 0
            local string output
            local Table table
            local string typeOf
            local string value
            local integer keyOf
            local string parsedKey

            // Determine if this is a tracked table and if it's already been seen
            if this > 0 and shadow > 0 then
                if HaveSavedBoolean(hashTable, seenTables, this) then
                    // Show already-referenced Table:
                    return "Seen Table(" + I2S(this) + ")"
                endif
                call SaveBoolean(hashTable, seenTables, this, true)
                if i == 0 then
                    // Show empty Table:
                    return "Table(" + I2S(this) + ")[]"
                endif
                set output = "Table(" + I2S(this) + ")["
            elseif instanceData[this] > 0 then
                return "Destroyed Table(" + I2S(this) + ")"
            elseif widths[this] > 0 then
                return "Tables " + I2S(this) + " through " + I2S(this + widths[this] - 1)
            elseif instanceData[this] == 0 then
                return "Invalid Table(" + I2S(this) + ")"
            endif

            loop
                exitwhen k == depth
                set indent = indent + "  "
                set k = k + 1
            endloop

            loop
                exitwhen i == 0
                set keyOf = shadow[i]
                set typeOf = LoadStr(hashTable, shadow, keyOf)
                set parsedKey = I2S(keyOf)
                if typeOf == "Table" then
                    set table = this[keyOf]
                    set typeOf = ""
                    if instanceData[keyOf] == -1 or widths[keyOf] > 0 then
                        set parsedKey = Table(keyOf).toStringFn(depth)
                    endif
                    if instanceData[table] == -1 or widths[table] > 0 then
                        set value = table.toStringFn(depth + 1)
                    else
                        set value = I2S(table) // simple integer
                    endif
                elseif typeOf == "integer" then
                    set typeOf = ""
                    set value = I2S(this[keyOf])
                elseif typeOf == "string" then
                    set typeOf = ""
                    set value = "\"" + LoadStr(hashTable, this, keyOf) + "\""
                elseif typeOf == "real" then
                    set typeOf = ""
                    set value = R2S(LoadReal(hashTable, this, keyOf))
                elseif typeOf == "boolean" then
                    set typeOf = ""
                    if LoadBoolean(hashTable, this, keyOf) then
                        set value = "true"
                    else
                        set value = "false"
                    endif
                elseif typeOf == null then
                    set typeOf = ""
                    set value = "untracked value"
                else
                    set value = ""
                endif
                set output = output + "\n" + indent + "  [" + parsedKey + "] = " + typeOf + value
                set i = i - 1
            endloop

            return output + "\n" + indent + "]"
        endmethod

        method toString takes nothing returns string
            local string result = this.toStringFn(0)
            call seenTables.flush()
            return result
        endmethod

        method print takes nothing returns nothing
            call BJDebugMsg(toString())
        endmethod
    endif

    //! runtextmacro optional TABLE_VBC_METHODS()
endstruct

/*
    Create API for stuff like:
        set table.unit[key] = GetTriggerUnit()
        local boolean b = table.handle.has(key)
        local unit u = table.unit[key]
        set table.handle.remove(key)

    These structs include the entire hashtable API as wrappers.

    Feel free to remove any types that you don't use.
*/

struct HandleStruct extends array
    method remove takes integer key returns nothing
        call Table(this).removeKey(key)
        call RemoveSavedHandle(hashTable, this, key)
    endmethod
    method delete takes string key returns nothing
        call this.remove(StringHash(key))
    endmethod
    method forget takes handle key returns nothing
        call this.remove(GetHandleId(key))
    endmethod

    method operator []= takes integer key, handle h returns nothing
        if h != null then
            // "But I need hashtables to typecast generic handles into ..." - say no more. I got u fam.
            call SaveFogStateHandle(hashTable, this, key, ConvertFogState(GetHandleId(h)))
        else
            call this.remove(key)
        endif
    endmethod
    method save takes integer key, agent value returns Table
        static if TEST then
            call Table(this).addTypedKey(key, "handle")
        else
            call Table(this).addKey(key)
        endif
        set this[key] = value
        return this
    endmethod
    method write takes string key, agent value returns Table
        return this.save(StringHash(key), value)
    endmethod
    method store takes handle key, agent value returns Table
        return this.save(GetHandleId(key), value)
    endmethod

    method has takes integer key returns boolean
        return HaveSavedHandle(hashTable, this, key)
    endmethod
    method written takes string key returns boolean
        return this.has(StringHash(key))
    endmethod
    method stores takes handle key returns boolean
        return this.has(GetHandleId(key))
    endmethod
endstruct

struct AgentStruct extends array
    method operator []= takes integer key, agent value returns nothing
        call SaveAgentHandle(hashTable, this, key, value)
    endmethod
    method save takes integer key, agent value returns Table
        static if TEST then
            call Table(this).addTypedKey(key, "agent")
        else
            call Table(this).addKey(key)
        endif
        set this[key] = value
        return this
    endmethod
    method write takes string key, agent value returns Table
        return this.save(StringHash(key), value)
    endmethod
    method store takes handle key, agent value returns Table
        return this.save(GetHandleId(key), value)
    endmethod
endstruct

//! textmacro BASIC_VALUE_TABLE takes SUPER, FUNC, TYPE
struct $SUPER$Struct extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$(hashTable, this, key)
    endmethod
    method get takes handle key returns $TYPE$
        return this[GetHandleId(key)]
    endmethod
    method read takes string key returns $TYPE$
        return this[StringHash(key)]
    endmethod

    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$(hashTable, this, key, value)
    endmethod
    method save takes integer key, $TYPE$ value returns Table
        static if TEST then
            call Table(this).addTypedKey(key, "$TYPE$")
        else
            call Table(this).addKey(key)
        endif
        set this[key] = value
        return this
    endmethod
    method store takes handle key, $TYPE$ value returns Table
        return this.save(GetHandleId(key), value)
    endmethod
    method write takes string key, $TYPE$ value returns Table
        return this.save(StringHash(key), value)
    endmethod

    method has takes integer key returns boolean
        return HaveSaved$SUPER$(hashTable, this, key)
    endmethod
    method written takes string key returns boolean
        return this.has(StringHash(key))
    endmethod
    method stores takes handle key returns boolean
        return this.has(GetHandleId(key))
    endmethod

    method remove takes integer key returns nothing
        call Table(this).removeKey(key)
        call RemoveSaved$SUPER$(hashTable, this, key)
    endmethod
    method delete takes string key returns nothing
        call this.remove(StringHash(key))
    endmethod
    method forget takes handle key returns nothing
        call this.remove(GetHandleId(key))
    endmethod
endstruct
module $SUPER$Module
    method operator $TYPE$ takes nothing returns $SUPER$Struct
        return this
    endmethod
endmodule
//! endtextmacro
//! runtextmacro BASIC_VALUE_TABLE("Real", "Real", "real")
//! runtextmacro BASIC_VALUE_TABLE("Boolean", "Boolean", "boolean")
//! runtextmacro BASIC_VALUE_TABLE("String", "Str", "string")
//! runtextmacro BASIC_VALUE_TABLE("Integer", "Integer", "integer")

//! textmacro HANDLE_VALUE_TABLE takes FUNC, TYPE
struct $FUNC$Struct extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$Handle(hashTable, this, key)
    endmethod
    method get takes handle key returns $TYPE$
        return this[GetHandleId(key)]
    endmethod
    method read takes string key returns $TYPE$
        return this[StringHash(key)]
    endmethod

    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$Handle(hashTable, this, key, value)
    endmethod
    method save takes integer key, $TYPE$ value returns Table
        static if TEST then
            call Table(this).addTypedKey(key, "$TYPE$")
        else
            call Table(this).addKey(key)
        endif
        set this[key] = value
        return this
    endmethod
    method store takes handle key, $TYPE$ value returns Table
        return this.save(GetHandleId(key), value)
    endmethod
    method write takes string key, $TYPE$ value returns Table
        return this.save(StringHash(key), value)
    endmethod

    // deprecated; use handle.has/stores/written
    method has takes integer key returns boolean
        return HaveSavedHandle(hashTable, this, key)
    endmethod

    // deprecated; use handle.remove/forget/delete
    method remove takes integer key returns nothing
        call HandleStruct(this).remove(key)
    endmethod
endstruct
module $FUNC$Module
    method operator $TYPE$ takes nothing returns $FUNC$Struct
        return this
    endmethod
endmodule
//! endtextmacro
//! runtextmacro HANDLE_VALUE_TABLE("Player", "player")
//! runtextmacro HANDLE_VALUE_TABLE("Widget", "widget")
//! runtextmacro HANDLE_VALUE_TABLE("Destructable", "destructable")
//! runtextmacro HANDLE_VALUE_TABLE("Item", "item")
//! runtextmacro HANDLE_VALUE_TABLE("Unit", "unit")
//! runtextmacro HANDLE_VALUE_TABLE("Ability", "ability")
//! runtextmacro HANDLE_VALUE_TABLE("Timer", "timer")
//! runtextmacro HANDLE_VALUE_TABLE("Trigger", "trigger")
//! runtextmacro HANDLE_VALUE_TABLE("TriggerCondition", "triggercondition")
//! runtextmacro HANDLE_VALUE_TABLE("TriggerAction", "triggeraction")
//! runtextmacro HANDLE_VALUE_TABLE("TriggerEvent", "event")
//! runtextmacro HANDLE_VALUE_TABLE("Force", "force")
//! runtextmacro HANDLE_VALUE_TABLE("Group", "group")
//! runtextmacro HANDLE_VALUE_TABLE("Location", "location")
//! runtextmacro HANDLE_VALUE_TABLE("Rect", "rect")
//! runtextmacro HANDLE_VALUE_TABLE("BooleanExpr", "boolexpr")
//! runtextmacro HANDLE_VALUE_TABLE("Sound", "sound")
//! runtextmacro HANDLE_VALUE_TABLE("Effect", "effect")
//! runtextmacro HANDLE_VALUE_TABLE("UnitPool", "unitpool")
//! runtextmacro HANDLE_VALUE_TABLE("ItemPool", "itempool")
//! runtextmacro HANDLE_VALUE_TABLE("Quest", "quest")
//! runtextmacro HANDLE_VALUE_TABLE("QuestItem", "questitem")
//! runtextmacro HANDLE_VALUE_TABLE("DefeatCondition", "defeatcondition")
//! runtextmacro HANDLE_VALUE_TABLE("TimerDialog", "timerdialog")
//! runtextmacro HANDLE_VALUE_TABLE("Leaderboard", "leaderboard")
//! runtextmacro HANDLE_VALUE_TABLE("Multiboard", "multiboard")
//! runtextmacro HANDLE_VALUE_TABLE("MultiboardItem", "multiboarditem")
//! runtextmacro HANDLE_VALUE_TABLE("Trackable", "trackable")
//! runtextmacro HANDLE_VALUE_TABLE("Dialog", "dialog")
//! runtextmacro HANDLE_VALUE_TABLE("Button", "button")
//! runtextmacro HANDLE_VALUE_TABLE("TextTag", "texttag")
//! runtextmacro HANDLE_VALUE_TABLE("Lightning", "lightning")
//! runtextmacro HANDLE_VALUE_TABLE("Image", "image")
//! runtextmacro HANDLE_VALUE_TABLE("Ubersplat", "ubersplat")
//! runtextmacro HANDLE_VALUE_TABLE("Region", "region")
//! runtextmacro HANDLE_VALUE_TABLE("FogState", "fogstate")
//! runtextmacro HANDLE_VALUE_TABLE("FogModifier", "fogmodifier")
//! runtextmacro HANDLE_VALUE_TABLE("Hashtable", "hashtable")
//! runtextmacro HANDLE_VALUE_TABLE("Frame", "framehandle")

//! runtextmacro optional TABLE_VBC_STRUCTS()

// Run these only to support backwards-compatibility.
// If you want to use them, include the Table5BC library in your script.
//! runtextmacro optional TABLE_ARRAY_BC()
//! runtextmacro optional TableXD("Table2D", "Table", "createFork(1)")
//! runtextmacro optional TableXD("Table3D", "Table2D", "createFork(1)")
//! runtextmacro optional TableXD("Table4D", "Table3D", "createFork(1)")
//! runtextmacro optional TableXD("Table5D", "Table4D", "createFork(1)")
//! runtextmacro optional TableXD("Table2DT", "Table", "create()")
//! runtextmacro optional TableXD("Table3DT", "Table2DT", "create()")
//! runtextmacro optional TableXD("Table4DT", "Table3DT", "create()")
//! runtextmacro optional TableXD("Table5DT", "Table4DT", "create()")
//! runtextmacro optional TableXD("HashTable", "Table", "createFork(1)")
//! runtextmacro optional TableXD("HashTableEx", "Table", "create()")

endlibrary

Backwards-compatibility with TableArray, HashTable/Ex, Table2/3/4/5D/T syntax:

Backwards-compatibility with Vexorian's Table syntax:

JASS:
local Table table = Table.create() //create it
set table.join("footman").write("joke", 69) //access arbitrary parent and child keys as needed
set table.read("footman").unit[99999] = GetTriggerUnit() //still works with multiple-type syntax so you still have the full hashtable API.
call table.delete("footman") //This destroys the bridge table created by "join".
call table.destroy() //Flush and destroy the Table and any of its bridge tables.

JASS:
struct table_demo extends array
    private static method demo takes nothing returns nothing
        //Create it:
        local Table a = Table.create()
       
        //Use it:
        local boolean b = a.has(69)
        set a[654321] = 'A'
        set a[54321] = 'B'
        set a.unit[12345] = GetTriggerUnit()
        set a.unit.store(a.unit[12345], GetSpellTargetUnit())
        set a.real['ABCD'] = 3.14159
        set a.integer[133] = 21

        //remove entries
        call a.handle.remove('ABCD')
        call a.remove(54321)

        //Flush/destroy it:
        call a.destroy()

        //Or, only flush it:
        call a.flush()
    endmethod
endstruct

JASS:
//Create it:
local Table fork = Table.createFork(3)
fork.write("Hello, world!", 7) //Unlike TableArrays, the fork is actually accessible as a Table directly.
fork.switch(1).write("Hello there!", 8) //To switch to the next parallel index, use the switch method.
fork.switch(2).write("General Kenobi!", 9) // `2` is the max index, because the fork count starts at 0.


Changes from version 5.1: Comparing 95e9b82..953d972 · BribeFromTheHive/NewTable
Changes from version 5: Update to 5.1 · BribeFromTheHive/NewTable@95e9b82
 
Last edited:
Honestly... I'd just do something like
JASS:
library Table
    globals
        private integer c = 0
        private integer array r
    endglobals
    struct Table extends array
        readonly static hashtable get = InitHashtable()
        method operator [] takes integer index returns integer
            return LoadInteger(get, this, index)
        endmethod
        method operator []= takes integer index, integer newVal returns nothing
            call SaveInteger(get, this, index, newVal)
        endmethod
        method has takes integer index returns boolean
            return HaveSavedInteger(get, this, index)
        endmethod
        method remove takes integer index returns nothing
            call RemoveSavedInteger(get, this, index)
        endmethod
        method clear takes nothing returns nothing
            call FlushChildHashtable(get, this)
        endmethod
        static method create takes nothing returns thistype
            local integer i
            if (r[0] == 0) then
                set c = c + 1
                return c
            endif
            set i = r[0]
            set r[0] = r[i]
            return i
        endmethod
        method destroy takes nothing returns nothing
            set r[this] = r[0]
            set r[0] = this
            call FlushChildHashtable(get, this)
        endmethod
    endstruct
endlibrary

Simple and straight to the point. Mass methods for all the different types is ehh ; |. At that point, you might as well use the table directly.

This is a table script I'd use : \.
 
->I think the normal Table lib does it well enough, since it has a textmacro for any other tables that need to be implemented.

it has onDestroy

Also, you can use the get field to retrieve the hashtable directly ;|, meaning you can work with anything w/o having to spam methods.

edit
Interesting idea...

table.unit[15]

JASS:
private struct UnitTable extends array
//...
endstruct

struct Table extends array

//...
method operator unit takes nothing returns UnitTable
    return this
endmethod

endstruct



Making textmacros for all of the types
JASS:
library Table
    //! textmacro CREATE_TABLE takes SUPER_TYPE, FUNC_TYPE, TYPE
        private struct $FUNCT_TYPE$Type extends array
            method operator [] takes integer index returns $TYPE$
                return Load$FUNC_TYPE$(t, this, index)
            endmethod
            method operator []= takes integer index, $TYPE$ newVal returns nothing
                call Save$FUNC_TYPE$(t, this, index, newVal)
            endmethod
            method has takes integer index returns boolean
                return HaveSaved$SUPER_TYPE$(t, this, index)
            endmethod
            method remove takes integer index returns nothing
                call RemoveSaved$SUPER_TYPE$(t, this, index)
            endmethod
        endstruct
        private module $FUNCT_TYPE$Mod
            method operator $TYPE$ takes nothing returns $FUNCT_TYPE$Type
                return this
            endmethod
        endmodule
    //! endtextmacro

    globals
        private hashtable t = InitHashtable()
        private integer c = 0
        private integer p = 0
        private integer array a
        private integer array q
        private trigger e = CreateTrigger()
        private integer z = 0
    endglobals

    //! runtextmacro CREATE_TABLE("Integer", "Integer", "integer")
    //! runtextmacro CREATE_TABLE("Real", "Real", "real")
    //! runtextmacro CREATE_TABLE("Boolean", "Boolean", "boolean")
    //! runtextmacro CREATE_TABLE("String", "Str", "string")
    //! runtextmacro CREATE_TABLE("Handle", "PlayerHandle", "player")
    //! runtextmacro CREATE_TABLE("Handle", "WidgetHandle", "widget")
    //! runtextmacro CREATE_TABLE("Handle", "DestructableHandle", "destructable")
    //! runtextmacro CREATE_TABLE("Handle", "ItemHandle", "item")
    //! runtextmacro CREATE_TABLE("Handle", "UnitHandle", "unit")
    //! runtextmacro CREATE_TABLE("Handle", "AbilityHandle", "ability")
    //! runtextmacro CREATE_TABLE("Handle", "TimerHandle", "timer")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerHandle", "trigger")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerConditionHandle", "triggercondition")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerActionHandle", "triggeraction")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerEventHandle", "event")
    //! runtextmacro CREATE_TABLE("Handle", "ForceHandle", "force")
    //! runtextmacro CREATE_TABLE("Handle", "GroupHandle", "group")
    //! runtextmacro CREATE_TABLE("Handle", "LocationHandle", "location")
    //! runtextmacro CREATE_TABLE("Handle", "RectHandle", "rect")
    //! runtextmacro CREATE_TABLE("Handle", "BooleanExprHandle", "boolexpr")
    //! runtextmacro CREATE_TABLE("Handle", "SoundHandle", "sound")
    //! runtextmacro CREATE_TABLE("Handle", "EffectHandle", "effect")
    //! runtextmacro CREATE_TABLE("Handle", "UnitPoolHandle", "unitpool")
    //! runtextmacro CREATE_TABLE("Handle", "ItemPoolHandle", "itempool")
    //! runtextmacro CREATE_TABLE("Handle", "QuestHandle", "quest")
    //! runtextmacro CREATE_TABLE("Handle", "QuestItemHandle", "questitem")
    //! runtextmacro CREATE_TABLE("Handle", "DefeatConditionHandle", "defeatcondition")
    //! runtextmacro CREATE_TABLE("Handle", "TimerDialogHandle", "timerdialog")
    //! runtextmacro CREATE_TABLE("Handle", "LeaderboardHandle", "leaderboard")
    //! runtextmacro CREATE_TABLE("Handle", "MultiboardHandle", "multiboard")
    //! runtextmacro CREATE_TABLE("Handle", "MultiboardItemHandle", "multiboarditem")
    //! runtextmacro CREATE_TABLE("Handle", "TrackableHandle", "trackable")
    //! runtextmacro CREATE_TABLE("Handle", "DialogHandle", "dialog")
    //! runtextmacro CREATE_TABLE("Handle", "ButtonHandle", "button")
    //! runtextmacro CREATE_TABLE("Handle", "TextTagHandle", "texttag")
    //! runtextmacro CREATE_TABLE("Handle", "LightningHandle", "lightning")
    //! runtextmacro CREATE_TABLE("Handle", "ImageHandle", "image")
    //! runtextmacro CREATE_TABLE("Handle", "UbersplatHandle", "ubersplat")
    //! runtextmacro CREATE_TABLE("Handle", "RegionHandle", "region")
    //! runtextmacro CREATE_TABLE("Handle", "FogStateHandle", "fogstate")
    //! runtextmacro CREATE_TABLE("Handle", "FogModifierHandle", "fogmodifier")
    //! runtextmacro CREATE_TABLE("Handle", "AgentHandle", "agent")
    //! runtextmacro CREATE_TABLE("Handle", "HashtableHandle", "hashtable")
    struct Table extends array
        implement IntegerMod
        implement RealMod
        implement BooleanMod
        implement StrMod
        implement PlayerHandleMod
        implement WidgetHandleMod
        implement DestructableHandleMod
        implement ItemHandleMod
        implement UnitHandleMod
        implement AbilityHandleMod
        implement TimerHandleMod
        implement TriggerHandleMod
        implement TriggerConditionHandleMod
        implement TriggerActionHandleMod
        implement TriggerEventHandleMod
        implement ForceHandleMod
        implement GroupHandleMod
        implement LocationHandleMod
        implement RectHandleMod
        implement BooleanExprHandleMod
        implement SoundHandleMod
        implement EffectHandleMod
        implement UnitPoolHandleMod
        implement ItemPoolHandleMod
        implement QuestHandleMod
        implement QuestItemHandleMod
        implement DefeatConditionHandleMod
        implement TimerDialogHandleMod
        implement LeaderboardHandleMod
        implement MultiboardHandleMod
        implement MultiboardItemHandleMod
        implement TrackableHandleMod
        implement DialogHandleMod
        implement ButtonHandleMod
        implement TextTagHandleMod
        implement LightningHandleMod
        implement ImageHandleMod
        implement UbersplatHandleMod
        implement RegionHandleMod
        implement FogStateHandleMod
        implement FogModifierHandleMod
        implement AgentHandleMod
        implement HashtableHandleMod

        method clear takes nothing returns nothing
            call FlushChildHashtable(t, this)
        endmethod
        debug private static boolean array a
        debug private static integer overflow = 0
        static method create takes nothing returns thistype
            local integer i
            if (r[0] == 0) then
                debug if (c < 8191) then
                    set c = c + 1
                    debug set a[c] = true
                    return c
                debug else
                    debug set overflow = overflow + 1
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Error: Instance Overflow " + I2S(overflow) + " times")
                    debug return 0
                debug endif
            endif
            set i = r[0]
            set r[0] = r[i]
            debug set a[i] = true
            return i
        endmethod
        method destroy takes nothing returns nothing
            debug if (a[this]) then
                set r[this] = r[0]
                set r[0] = this
                debug set a[this] = false
                call FlushChildHashtable(t, this)
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Error: Multiply Destroyed Instance " + I2S(this))
            debug endif
        endmethod
    endstruct
    private module TableArrayMod
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(e, function thistype.doClear)
        endmethod
    endmethod
    struct TableArray extends array
        debug private static boolean array a
        debug private static integer overflow = 0
        method operator [] takes integer index returns Table
            debug if (a[this] then
            debug if index < 8192 then
                return (this*8192)+index
            debug else
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Index Out Of Bounds " + I2S(this) + "[" + I2S(index) + "]")
            debug endif
            debug else
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Null Table Array " + I2S(this))
            debug endif
        endmethod
        static method create takes nothing returns thistype
            local integer i
            if (q[0] == 0) then
                debug if (p < 8191) then
                    set p = p + 1
                    debug set a = true
                    return p
                debug else
                    debug set overflow = overflow + 1
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Instance Overflow " + I2S(overflow) + " times")
                    debug return 0
                debug endif
            endif
            set i = q[0]
            set q[0] = q[i]
            debug set a[i] = true
            return i
        endmethod
        private static method doClear takes nothing returns boolean
            local integer i = z + 8191
            local integer m = z
            loop
                call FlushChildHashtable(t, i)
                exitwhen i == m
                set i = i - 1
            endloop
            return false
        endmethod
        method destroy takes nothing returns nothing
            debug if (a[this]) then
                set q[this] = q[0]
                set q[0] = this
                debug set a[this] = false
                set z = this*8192
                call TriggerEvaluate(e)
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Multiply Destroyed Instance " + I2S(this))
            debug endif
        endmethod
        method clear takes nothing returns nothing
            set z = this*8192
            call TriggerEvaluate(e)
        endmethod
    endstruct
endlibrary

Usage
JASS:
struct Example extends array
    
    static TableArray tableArray
    
    static method onInit takes nothing returns nothing
        set tableArray = TableArray.create() // New TableArray of size 8192, can be destroyed
    endmethod

    static method loopAll takes nothing returns nothing
        local integer i = 15
        loop
            set tableArray[i].integer[0] = 'A'
            set tableArray[i].integer[1] = 'B'
            set tableArray[i].real[2] = 3.14159
            set tableArray[i].real[3] = 90
            exitwhen i == 0
            set i = i - 1
        endloop
        call tableArray.clear()
    endmethod
endstruct
 
Last edited:
Nestharus, having access to a public hashtable makes it unreliable for systems to access keys without risking overwriting by conflicting key handling. I originally had the ability to destroy Table instances but removed it in favor of not limiting the number of destroyed instances to 8191. I can add it back if this limit doesn't matter in practical cases.

Some pros and cons between this and Vexorian's Table:

Vex: Hides the syntax of GetHandleId and StringHash with HandleTable and StringTable.
This: Doesn't generate wasted methods when the user can easily type GetHandleId or StringHash

Vex: Can only save/load integers
This: Can save/load integers and reals

Vex: 2-D Parent Key access is by strings, which are incredibly slow and can conflict unless the string keys are properly name-spaced.
This: Can instantiate a ParentTable to do a 2-D array of arbitrary size for each system that wishes to use it, partitioning the parent keys to each system without conflicting key usage. ParentTable[8190] reserves a Table for each instance in your structs.

JASS:
set pt = ParentTable[8190]
set pt[this] = someInt

It may take some analyzation to get the hang of it, but this has the potential for extremely dynamic use. My next release of Retro will be using this, to show off its capability to be a very useable 4-dimensional array.
 
If you didn't notice, I updated the code on the second last reply (you did notice though).

It includes a list of all of the hashtable natives converted into a struct type format to be used with 1 parent hashtable. Useful if you only need 1 index and all of the stuff inlines except for create/destroy.

With support of entire Hashtable API, there's no reason for a person not to use this script if they only need one index for their hashtable ;P.
 
Last edited:
Updated to 1.0.0.0, the version I am satisfied with and the only API that will change would be additions at this point, but nothing will be done at this point to break backwards compatibility.

Four new things:

1. All types are now able to be saved
2. All types have beautiful syntax as Nestharus provided:
JASS:
tb.unit[this] = GetTriggerUnit()
tb.real[that] = 3.14159
3. Table instances can now be destroyed
4. ParentTable instances can now be flushed
 
Last edited:
Updated to 1.1.0.0, ParentTable instances can now be destroyed. Calling .flush() on a ParentTable will also destroy it, I just separated the two methods so that if you were flushing instances the whole way around you can still just deallocate the ParentTable's index point.

With this version it is now very easy to use a dynamic ParentTable of any specified size to do your bidding, and is incredibly, incredibly efficient.
 
This is bugged with ParentTable, don't have time to go into detail atm. This has to do with the fact that the sizes can be anything, so you can't just add to the end. What you'd end up having to do is have different sets of recycles for different sizes ;|.

This is silly
JASS:
    private integer last = 0
    private integer array list
.

You only need the array, not the last. Just treat the array like a stack...

I also suggest that you use different instances for ParentArrays and TableArrays, otherwise you are seriously going to screw up your tables. I had it right in my code >.>.


edit
Since you are determined to have dynamic sizes for array tables, this is an update that'll do it properly ; |.

Fixed all of the syntax errors too ;P.

edit
nvm, our libs do like the same thing ;P, but yours will have a syntax error with agents as there is no LoadAgentHandle.

Also mine includes tons of debug messages ;o.
JASS:
library Table
    //! textmacro CREATE_TABLE takes SUPER_TYPE, FUNC_TYPE, TYPE, LOAD
        private struct $FUNC_TYPE$Type extends array
            static if $LOAD$ then
                method operator [] takes integer index returns $TYPE$
                    return Load$FUNC_TYPE$(t, this, index)
                endmethod
            endif
            method operator []= takes integer index, $TYPE$ newVal returns nothing
                call Save$FUNC_TYPE$(t, this, index, newVal)
            endmethod
            method has takes integer index returns boolean
                return HaveSaved$SUPER_TYPE$(t, this, index)
            endmethod
            method remove takes integer index returns nothing
                call RemoveSaved$SUPER_TYPE$(t, this, index)
            endmethod
        endstruct
        private module $FUNC_TYPE$Mod
            method operator $TYPE$ takes nothing returns $FUNC_TYPE$Type
                return this
            endmethod
        endmodule
    //! endtextmacro

    globals
        private hashtable t = InitHashtable()
        private integer c = 0
        private integer p = 0
        private integer array r
        private trigger e = CreateTrigger()
        private integer z = 0
        private integer h = 0
    endglobals

    //! runtextmacro CREATE_TABLE("Integer", "Integer", "integer", "true")
    //! runtextmacro CREATE_TABLE("Real", "Real", "real", "true")
    //! runtextmacro CREATE_TABLE("Boolean", "Boolean", "boolean", "true")
    //! runtextmacro CREATE_TABLE("String", "Str", "string", "true")
    //! runtextmacro CREATE_TABLE("Handle", "PlayerHandle", "player", "true")
    //! runtextmacro CREATE_TABLE("Handle", "WidgetHandle", "widget", "true")
    //! runtextmacro CREATE_TABLE("Handle", "DestructableHandle", "destructable", "true")
    //! runtextmacro CREATE_TABLE("Handle", "ItemHandle", "item", "true")
    //! runtextmacro CREATE_TABLE("Handle", "UnitHandle", "unit", "true")
    //! runtextmacro CREATE_TABLE("Handle", "AbilityHandle", "ability", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TimerHandle", "timer", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerHandle", "trigger", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerConditionHandle", "triggercondition", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerActionHandle", "triggeraction", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TriggerEventHandle", "event", "true")
    //! runtextmacro CREATE_TABLE("Handle", "ForceHandle", "force", "true")
    //! runtextmacro CREATE_TABLE("Handle", "GroupHandle", "group", "true")
    //! runtextmacro CREATE_TABLE("Handle", "LocationHandle", "location", "true")
    //! runtextmacro CREATE_TABLE("Handle", "RectHandle", "rect", "true")
    //! runtextmacro CREATE_TABLE("Handle", "BooleanExprHandle", "boolexpr", "true")
    //! runtextmacro CREATE_TABLE("Handle", "SoundHandle", "sound", "true")
    //! runtextmacro CREATE_TABLE("Handle", "EffectHandle", "effect", "true")
    //! runtextmacro CREATE_TABLE("Handle", "UnitPoolHandle", "unitpool", "true")
    //! runtextmacro CREATE_TABLE("Handle", "ItemPoolHandle", "itempool", "true")
    //! runtextmacro CREATE_TABLE("Handle", "QuestHandle", "quest", "true")
    //! runtextmacro CREATE_TABLE("Handle", "QuestItemHandle", "questitem", "true")
    //! runtextmacro CREATE_TABLE("Handle", "DefeatConditionHandle", "defeatcondition", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TimerDialogHandle", "timerdialog", "true")
    //! runtextmacro CREATE_TABLE("Handle", "LeaderboardHandle", "leaderboard", "true")
    //! runtextmacro CREATE_TABLE("Handle", "MultiboardHandle", "multiboard", "true")
    //! runtextmacro CREATE_TABLE("Handle", "MultiboardItemHandle", "multiboarditem", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TrackableHandle", "trackable", "true")
    //! runtextmacro CREATE_TABLE("Handle", "DialogHandle", "dialog", "true")
    //! runtextmacro CREATE_TABLE("Handle", "ButtonHandle", "button", "true")
    //! runtextmacro CREATE_TABLE("Handle", "TextTagHandle", "texttag", "true")
    //! runtextmacro CREATE_TABLE("Handle", "LightningHandle", "lightning", "true")
    //! runtextmacro CREATE_TABLE("Handle", "ImageHandle", "image", "true")
    //! runtextmacro CREATE_TABLE("Handle", "UbersplatHandle", "ubersplat", "true")
    //! runtextmacro CREATE_TABLE("Handle", "RegionHandle", "region", "true")
    //! runtextmacro CREATE_TABLE("Handle", "FogStateHandle", "fogstate", "true")
    //! runtextmacro CREATE_TABLE("Handle", "FogModifierHandle", "fogmodifier", "true")
    //! runtextmacro CREATE_TABLE("Handle", "AgentHandle", "agent", "false")
    //! runtextmacro CREATE_TABLE("Handle", "HashtableHandle", "hashtable", "true")
    struct Table extends array
        implement IntegerMod
        implement RealMod
        implement BooleanMod
        implement StrMod
        implement PlayerHandleMod
        implement WidgetHandleMod
        implement DestructableHandleMod
        implement ItemHandleMod
        implement UnitHandleMod
        implement AbilityHandleMod
        implement TimerHandleMod
        implement TriggerHandleMod
        implement TriggerConditionHandleMod
        implement TriggerActionHandleMod
        implement TriggerEventHandleMod
        implement ForceHandleMod
        implement GroupHandleMod
        implement LocationHandleMod
        implement RectHandleMod
        implement BooleanExprHandleMod
        implement SoundHandleMod
        implement EffectHandleMod
        implement UnitPoolHandleMod
        implement ItemPoolHandleMod
        implement QuestHandleMod
        implement QuestItemHandleMod
        implement DefeatConditionHandleMod
        implement TimerDialogHandleMod
        implement LeaderboardHandleMod
        implement MultiboardHandleMod
        implement MultiboardItemHandleMod
        implement TrackableHandleMod
        implement DialogHandleMod
        implement ButtonHandleMod
        implement TextTagHandleMod
        implement LightningHandleMod
        implement ImageHandleMod
        implement UbersplatHandleMod
        implement RegionHandleMod
        implement FogStateHandleMod
        implement FogModifierHandleMod
        implement AgentHandleMod
        implement HashtableHandleMod

        method clear takes nothing returns nothing
            call FlushChildHashtable(t, this)
        endmethod
        debug private static boolean array a
        debug private static integer overflow = 0
        static method create takes nothing returns thistype
            local integer i
            if (r[0] == 0) then
                debug if (c < 8190) then
                    set c = c + 1
                    debug set a[c] = true
                    return c
                debug else
                    debug set overflow = overflow + 1
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Error: Instance Overflow " + I2S(overflow) + " times")
                    debug return 0
                debug endif
            endif
            set i = r[0]
            set r[0] = r[i]
            debug set a[i] = true
            return i
        endmethod
        method destroy takes nothing returns nothing
            debug if (a[this]) then
                set r[this] = r[0]
                set r[0] = this
                debug set a[this] = false
                call FlushChildHashtable(t, this)
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Error: Multiply Destroyed Instance " + I2S(this))
            debug endif
        endmethod
    endstruct
    private module TableArrayMod
        private static method onInit takes nothing returns nothing
            call TriggerAddCondition(e, function thistype.doClear)
        endmethod
    endmodule
    struct TableArray extends array
        debug private static boolean array a
        debug private static integer overflow = 0
        readonly thistype size
        method operator [] takes integer index returns Table
            debug if (a[this]) then
            debug if index < integer(size) then
                return (this+8191)+index
            debug else
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Index Out Of Bounds " + I2S(this) + "[" + I2S(index) + "]")
            debug endif
            debug else
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Null Table Array " + I2S(this))
            debug endif
            debug return 0
        endmethod
        static method operator [] takes integer size returns thistype
            local integer i = LoadInteger(t, -size, 0)
            debug if (size > 0) then
                if (i == 0) then
                    debug if (p < 8190) then
                        set p = p + 1
                        debug set a = true
                        return p
                    debug else
                        debug set overflow = overflow + 1
                        debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Instance Overflow " + I2S(overflow) + " times")
                        debug return 0
                    debug endif
                endif
                call SaveInteger(t, -size, 0, i-1)
                set i = LoadInteger(t, -size, i-1)
                set thistype(i).size = size
                debug set a[i] = true
                return i
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Invalid Size " + I2S(size))
            debug endif
            debug return 0
        endmethod
        private static method doClear takes nothing returns boolean
            local integer i = h
            local integer m = h-5000
            if (m < z) then
                set m = z
            endif
            loop
                call FlushChildHashtable(t, i)
                exitwhen i == m
                set i = i - 1
            endloop
            if (i != z) then
                set h = i
                call TriggerEvaluate(e)
            endif
            return false
        endmethod
        method destroy takes nothing returns nothing
            local integer i
            debug if (a[this]) then
                set i = LoadInteger(t, -size, 0)+1
                call SaveInteger(t, -size, i, this)
                call SaveInteger(t, -size, 0, i)
                debug set a[this] = false
                set z = this+8191
                set h = size+z-1
                call TriggerEvaluate(e)
                set size = 0
            debug else
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Table Array Error: Multiply Destroyed Instance " + I2S(this))
            debug endif
        endmethod
        method clear takes nothing returns nothing
            set z = this+8191
            set h = size+z-1
            call TriggerEvaluate(e)
        endmethod
    endstruct
endlibrary

Demo
JASS:
function Test takes nothing returns nothing
    local TableArray table = TableArray[16] //0-15
    set table[0].integer[1] = 5
    set table[15].real[6] = 1.1
    call table.destroy()
endfunction
 
Last edited:
Yesterday I made a silent update to correct the syntax error (didn't have enough time to post an update message).

Previously typecasting would be required to save a Table within a Table. Now, the original Table syntax is supported as well as storing Tables within Tables. One day I might remove the .integer syntax if no one is using it.

Something like set pt[this][10]['A']['G'].unit[1] = GetTriggerUnit() is now supported (obviously the example is an exaggeration).

For those reading this in the future, Nestharus and I were chatting about the mechanics of this library for a while behind the scenes and he gave some good feedback, just disregard the bits about ParentTable bugging because that was never the scenario.
 
integer(this) is silly... you only need to do that for comparisons like > or == or < or w/e.

This should be a debug statement (all safety should be in debug) if (this > 0 and lpos < 8191) then.


You should also be clearing data on destroy rather than on create to save on memory and to keep hashtables running as fast as possible on the lookups.


Also, it'd be nice to have more debug messages (debug on every possible error please). This really can help a lot of people.


Also, I think that TableArray is a better name that ParrentTable personally, but eh... JASS kinda of breaks how hashtables are normally done. Hashtables have 1 index, not 2, lol...

Example from c#
JASS:
        // Add some elements to the hash table. There are no 
        // duplicate keys, but some of the values are duplicates.
        openWith.Add("txt", "notepad.exe");
        openWith.Add("bmp", "paint.exe");
        openWith.Add("dib", "paint.exe");
        openWith.Add("rtf", "wordpad.exe");

But anyways, if we want to follow JASS convention, Table would be a bad name for the first struct. In fact, it should be something like Hashtable for the second and I don't know what for the first, lol. That's why I just stuck with Table and TableArray (if you're going to break it, might as well go all the way ;P).

Table->??
ParentTable->Hashtable

But then again, you are defining a size, so it wouldn't actually be a hashtable would it? (sneaky sneaky). It's more like an array actually. TableArray or HashtableArray is a much better name than ParentTable.

Now, if you really want to keep ParentTable, there is a way (you have a hashtable on top of the hashtable, which would be ugly, but oh well, lol).


So my suggestion is Table and TableArray or ???? and ????HashtableArray. As it stands, neither name reflects what the object actually is.

Maybe MiniHashtable and MiniHashtableArray? Who knows.

Also, if we really want to stick with the JASS style, you shouldn't be able to set the array size ;p, but array size is a cool feature that JASS should have probably had, so keep it in there =).


Really need to come up with a specific style and specific objects for JASS since we have all of this chaos with JASS and vJASS : |. Procedural + OO programming = a spaghetti code, not that good JASS isn't spaghetti code (oddly enough -.-), lol.
 
integer(this) is just the format Vex was using and I thought it looked trendy.

Clearing data on destroy? I'm clearing data on destroy all the time.

this > 0 and lpos < 8191 should not be on debug because it doesn't matter if the array can't recycle instances, I just throw out ones if the stack is full (integers can go into the trillions, who cares?)
 
Table and TableArray would certainly represent the naming convention without problems, it's just that Blizzard called the first key parentKeys and the second key childKeys, and FlushChildHashtable/FlushParentHashtable, so I stuck with the naming convention ParentTable. At this point no one has mentioned they will be working with this library so breaking backwards compatibility isn't going to affect anyone I know of.

I'm about to break backwards compatibility with this next update, and removing that .integer syntax that is now so useless.
 
This script is loaded with syntax errors and I don't trust it to be logically correct >.>.

This is w/o syntax errors
JASS:
library Table // made by Bribe, special thanks to Nestharus, v2.0.0.0
    
globals
    private hashtable ht = InitHashtable() // The last hashtable you need
    private integer size = 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 NewTable 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 NewTable("Real", "Real", "real", "true")
//! runtextmacro NewTable("Boolean", "Boolean", "boolean", "true")
//! runtextmacro NewTable("String", "Str", "string", "true")
//! runtextmacro NewTable("Handle", "PlayerHandle", "player", "true")
//! runtextmacro NewTable("Handle", "WidgetHandle", "widget", "true")
//! runtextmacro NewTable("Handle", "DestructableHandle", "destructable", "true")
//! runtextmacro NewTable("Handle", "ItemHandle", "item", "true")
//! runtextmacro NewTable("Handle", "UnitHandle", "unit", "true")
//! runtextmacro NewTable("Handle", "AbilityHandle", "ability", "true")
//! runtextmacro NewTable("Handle", "TimerHandle", "timer", "true")
//! runtextmacro NewTable("Handle", "TriggerHandle", "trigger", "true")
//! runtextmacro NewTable("Handle", "TriggerConditionHandle", "triggercondition", "true")
//! runtextmacro NewTable("Handle", "TriggerActionHandle", "triggeraction", "true")
//! runtextmacro NewTable("Handle", "TriggerEventHandle", "event", "true")
//! runtextmacro NewTable("Handle", "ForceHandle", "force", "true")
//! runtextmacro NewTable("Handle", "GroupHandle", "group", "true")
//! runtextmacro NewTable("Handle", "LocationHandle", "location", "true")
//! runtextmacro NewTable("Handle", "RectHandle", "rect", "true")
//! runtextmacro NewTable("Handle", "BooleanExprHandle", "boolexpr", "true")
//! runtextmacro NewTable("Handle", "SoundHandle", "sound", "true")
//! runtextmacro NewTable("Handle", "EffectHandle", "effect", "true")
//! runtextmacro NewTable("Handle", "UnitPoolHandle", "unitpool", "true")
//! runtextmacro NewTable("Handle", "ItemPoolHandle", "itempool", "true")
//! runtextmacro NewTable("Handle", "QuestHandle", "quest", "true")
//! runtextmacro NewTable("Handle", "QuestItemHandle", "questitem", "true")
//! runtextmacro NewTable("Handle", "DefeatConditionHandle", "defeatcondition", "true")
//! runtextmacro NewTable("Handle", "TimerDialogHandle", "timerdialog", "true")
//! runtextmacro NewTable("Handle", "LeaderboardHandle", "leaderboard", "true")
//! runtextmacro NewTable("Handle", "MultiboardHandle", "multiboard", "true")
//! runtextmacro NewTable("Handle", "MultiboardItemHandle", "multiboarditem", "true")
//! runtextmacro NewTable("Handle", "TrackableHandle", "trackable", "true")
//! runtextmacro NewTable("Handle", "DialogHandle", "dialog", "true")
//! runtextmacro NewTable("Handle", "ButtonHandle", "button", "true")
//! runtextmacro NewTable("Handle", "TextTagHandle", "texttag", "true")
//! runtextmacro NewTable("Handle", "LightningHandle", "lightning", "true")
//! runtextmacro NewTable("Handle", "ImageHandle", "image", "true")
//! runtextmacro NewTable("Handle", "UbersplatHandle", "ubersplat", "true")
//! runtextmacro NewTable("Handle", "RegionHandle", "region", "true")
//! runtextmacro NewTable("Handle", "FogStateHandle", "fogstate", "true")
//! runtextmacro NewTable("Handle", "FogModifierHandle", "fogmodifier", "true")
//! runtextmacro NewTable("Handle", "AgentHandle", "agent", "false")
//! runtextmacro NewTable("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 size = size + 1
            return size
        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
    
endstruct
    
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 (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 (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 (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 (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
    
    // 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 TableArray.clean(this, end)
            call this.destroy()
        debug else
            debug call BJDebugMsg("TableArray Error: Tried to flush an invalid instance (" + I2S(this) + ")")
        endif
    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 tb == end
        endloop
    endmethod
    
endstruct
    
endlibrary

I was about to use this script, but the syntax errors made me stop cuz that tells me it's 100% untested ^_^.


When you make sure that all of its features work (no leaks on destroy, etc), I'll be sure to use it ;D. Was going to update Base script to use it.
 
Corrected for the syntax errors. Like I said, can't get NewGen to install on this computer, so how can I test?

The logic is there and works fine. The syntax errors were easy to make, you just corrected a couple of typos in the macros and wrapped "this" in integer format.

Very easy to lose my trust ;P.

Even if I think the logic is right on a script, I test it anyways. That base script i wrote worked 100% on the very first write with no syntax or logical errors, but I tested it anyways to make sure. Even when logic is easy or the script is simple, you always test it ;D, always.
 
This is approved with a notice that I will be finding a solution for a problem I found (another impossibly-stupid JassHelper error).

When loading/saving reals with this system, JassHelper generates a USELESS *1.0 with the real value being saved/loaded. !!!?!?!?!??

EPIC FAIL.

I will try to resolve this later somehow. But it's good to go.
 
Updated with backwards-support for Vexorian's Table. All you have to do is include the TableBC library in your map (you do not even have to require it). This supports HandleTable, StringTable, the .exists() and .reset() methods from the original Table and supports .flush(key) for StringTable and HandleTable. .flush(key) will throw a syntax error for anyone using the old, standard integer-Table, but this is a rarely-used method and can be easily fixed going through the script and changing any .flush(key) to .remove(key).
 
Level 14
Joined
Nov 18, 2007
Messages
816
Congratulations, you just wasted a few hours of your life in a futile endeavour to improve Vex's Table library.

Why would anyone ever need attaching anything other than integers (ie. structs)?
What about people who need more than 8190 tables?
Why did you see the necessity to improve on something that doesnt need improving?
Why did something like this get approved?
 
1. Vex's library is one-dimensional and limited to 40,000 instances if configured that way
2. Reals often need to be stored in hashtables, see my Retro library and you'll realize that magnitude of indexing cannot be done with normal arrays.
3. You can't have more than 8190 destroyed Tables, but you can have as many active ones as you want.
4. With TableArrays you don't even need to destroy Tables in most cases
5. Judging by your comments, you haven't even looked at this library nor came across a situation where you need a multi-dimensional array. Again, you should look at the Retro library. Even take a look at AutoIndex's AutoData module which uses a far less efficient method to simulate a 3-D array.
6. Vexorian's Table was in serious need of update... bugs with module initializers, uses onDestroy for no reason, can only store integers, has a really lacking approach to 2-D Tables.
 
Level 14
Joined
Nov 18, 2007
Messages
816
1. Vex's library is one-dimensional and limited to 40,000 instances if configured that way
Vex's Table does support 2D Syntax, albeit with strings for the first dimension, which is a leftover from gamecache days and probably also there to protect idiots from shooting themselves in the foot. You also seem to confuse 400k with 40k.
2. Reals often need to be stored in hashtables, see my Retro library and you'll realize that magnitude of indexing cannot be done with normal arrays.
Store the reals in structs and then store the structs in tables.
3. You can't have more than 8190 destroyed Tables, but you can have as many active ones as you want.
Why would one need more than 400k active tables?
6. Vexorian's Table was in serious need of update... bugs with module initializers, uses onDestroy for no reason, can only store integers, has a really lacking approach to 2-D Tables.
Yes to the first, no to the rest. And fixing the bug you mentioned takes all of 2 seconds.
 
You can always remove what you don't need. IMO a library that has everything you need is better than a library that supports only one thing, because it is easier to remove than it is to add.

Anyway, this library is made as an extension. If Table supports everything you need, then you can go ahead and use it. Because this has a backwards compatibility library, you can easily replace Table if you need the other features. Some of the features may seem a bit redundant in systems/spells, but you also have to take into consideration map making, where its usefulness is a bit more prominent.
 
albeit with strings for the first dimension

Relying on StringHash is far less reliable than using a TableArray. And a TableArray can be easily used to grant a Table instance to every struct member - such a thing with Vexorian's method would involve a nastily inefficient StringHash("ArrayPrefix" + I2S(this)) lookup instead of simple (ParentTableIndex + this) math.

Store the reals in structs and then store the structs in tables.

Again you miss the point. There are far too many real values to store them all in JASS arrays. At any given time in the Retro library, one unit alone will have a minimum of 1,280 real values associated with it.

Also, for something like Vexorian's "GetUnitCollisionSize", if his Table could store reals it would eliminate all the useless R2I calls he does.

Why would one need more than 400k active tables?

If you really spread that thing to such a great size your performance slowdown will be extraordinary, not only is needless limitation.

no to the rest

You haven't given enough thought to the necessary dimensions required in some systems if you really think like that.

And fixing the bug you mentioned takes all of 2 seconds.

Make sure you give all your libraries that use Tables in modules a disclaimer: fix Vexorian's Table before implementing this. Since he has repeatedly refused to update that thing, this library fits that need as well as many others.
 
Level 14
Joined
Nov 18, 2007
Messages
816
Vex's Table already has everything you need. Thats the point im trying to make.
Its interesting to see how you seem to think writing anything other than integers into hashtables is necessary. Name one case where you need the extra space of Tables and extended arrays dont cut it (ie. >400k indices). Two things are going to happen: I'm going to call you insane and immediately suggest a way to reduce the amount of data you need to store.

To be fair, ive used the 2D syntax of Table once or so. If I needed a table array, either I really DO use an array of Tables or I use a Table of Tables. Works for me and is only slightly more inconvenient than what you did.
At any given time in the Retro library, one unit alone will have a minimum of 1,280 real values associated with it.
This is you missing the point. 1280 values, i assume, come from X/Y coordinates. Those are two different values, so youd use separate arrays, which brings the amount down to 640. Now, storing data with an accuracy of 1/32nd of a second is pointless. You can save that data twice per second and players wont complain about inaccuracy. So i think saving that data 8 times per second is far more than enough, which brings us down to 160. 160 fits 51 times into 8190, and 2500 times into 400k. And id venture that storing data for 2500 units is going to result in a thread crash regardless of how you do it.
if his Table could store reals it would eliminate all the useless R2I calls he does.
Yes, its a workaround. Another workaround wouldve involved storing the actual data in a struct instance.
You haven't given enough thought to the necessary dimensions required in some systems if you really think like that.
What does that have to do with anything. I can just claim i have anyway, regardless of what ive actually done.
And what do you mean with your "necessary dimensions"? 3D/4D arrays? Sorry, been there, done that. Extra large arrays? Sorry, if you need that much space, youre doing something wrong.
Make sure you give all your libraries that use Tables in modules a disclaimer
You know what i do? I dont use module initializers for everything. I use them to initialize modules, not structs or libraries.
 
Last edited:
Vex's Table already has everything you need. Thats the point im trying to make.

Vex's Table may have everything you need, but it doesn't have a lot of things many other people need.

Its interesting to see how you seem to think writing anything other than integers into hashtables is necessary.

It would be a lot less interesting if you looked at the Retro library and found out why such dynamism is required.

Name one case where you need the extra space of Tables and extended arrays dont cut it (ie. >400k indices).

I've named it a few times but you seem to be missing the point.

Two things are going to happen: I'm going to call you insane and immediately suggest a way to reduce the amount of data you need to store.

Two things are going to happen: you're going to waste your time and you're going to give a horribly-less efficient alternative.
 
I see you edited your post since I wrote my reply.

One hashtable versus piles and piles of arrays is the inefficient alternative I was telling you would be an overall terrible idea.

Yes, its a workaround. Another workaround wouldve involved storing the actual data in a struct instance.

Pointlessly adding an array search instead of storing the real directly. Wasted efficiency.

3D/4D arrays? Sorry, been there, done that.

Looks like you went there, got lost and came back afraid to go back.

I realize you've taken the role of devil's advocate and I am no longer going to "play along" until you start taking the features a bit more seriously. When you honestly take the breakdown of functionality, efficiency and dynamics, this outscores Table in every way that it doesn't already match it in performance, and with that in mind, you're supporting a standard "just because it's a standard", and I can't respect such redundancy.
 
Level 11
Joined
Sep 30, 2009
Messages
697
The library looks very good and works well :) You did a nice job there. One thing I would like to mention is that this seems to break the cJass parser somehow with this:

JASS:
    static if ($LOAD$) then
        method operator [] takes integer key returns $TYPE$
            return Load$FUNC$(ht, this, key)
        endmethod
    endif

I do neither know why this happens nor how to fix it. Looks like an error in the cJass parser for me.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Since i hate innacurated things and Bribe didn't care to edit it, i just wanted to say the limit of hashtables is 256 not 255.
Yeah most people have probably guess for themselves and even don't care about it, but still.

A short script which prove it :

JASS:
function Trig_test_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_test takes nothing returns nothing
    local integer i = 0
    local hashtable hash
    
    loop
        set hash = InitHashtable()
        call SaveBoolean(hash,0,0,true)
        if LoadBoolean(hash,0,0) then
            set i = i+1
        else
            exitwhen true
        endif
    endloop
    call BJDebugMsg("number max of hashtable == " + I2S(i))
endfunction
 
I'm going to use this library to create large data structures ^^
Unit Data
DataU[GetHandleId(unit)][StringHash("damage")][StringHash("average")]
DataU[GetHandleId(unit)][StringHash("armor")]

This would look much better than a giant hashtable x) (I'm not actually going to use StringHash ofcourse :p)

Well done Bribe =)
You are brilliant! (as Nestharus said =P)
 

BBQ

BBQ

Level 4
Joined
Jun 7, 2011
Messages
97
Since you probably aren't reading messages at Wc3C, let me quote myself.
BBQ said:
Well, speaking about 2D-syntax, the one provided by your library is not perfectly safe either.
JASS:
//! zinc
library DualArrayTest requires Array {
    
    DualArray firstArray, secondArray;
    constant integer ARRAY_SIZE = 0x2000;
    constant key KEY;
    
    function print(string message) {
        DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 50.0, message);
    }
    
    function onInit() {
        firstArray = DualArray[ARRAY_SIZE];
        secondArray = DualArray[ARRAY_SIZE];
        
        firstArray[ARRAY_SIZE][KEY] = 0x7FFFFFFF;
        secondArray[2*ARRAY_SIZE][KEY] = 0xF;
        
        print(I2S(firstArray[ARRAY_SIZE][KEY]));
        /* Prints "15" instead of "2147483647". */
    }
}
//! endzinc
 
Last edited:
1. You are exceeding the array bounds when you try to reference ARRAY_SIZE. 8192 size means that you can access slots 0-8191.

2. You are super-exceeding it by using 2*ARRAY_SIZE. That shows a complete disregard for the functionality of the thing. If you had tested this, it would have thrown plenty of errors.

3. This is nothing I haven't though of before, that's why the checks in the getindex method make sure that the key has to be equal or greater than 0 and less than the array size.
 
Top