[Lua][vJass] New Table

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264

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 now 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 generates hundreds of lines of code. This means you don't ever have to worry about creating too many Tables, because you can never really have too many.
  3. 2-D array syntax is perfected and allows you to create things called TableArrays. The old method used the volatile StringHash, which easily bypasses the integer limit and starts saving into unpredictable places. This is dangerous when using a shared hashtable because you could overwrite someone else's data without even knowing it.
  4. Table instances can save/load data from within module initializers. This didn't work before because the hashtable was initialized from a struct instead of from the globals block.

You can only have 256 hashtables at a time, so with 2 ^ 31 - 1 Table instances at your disposal and (that's right, I said "and", not "or") roughly 2 ^ 18 TableArray instances with array size 8192 (JASS max array size), you will 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. A TableArray - an array of Tables - is a great way to achieve this. Instanciate a TableArray via TableArray[16] for an array of Tables sized 16, or TableArray[0x2000] for an array of Tables with the same size as a normal JASS array. The size can be very large if you want, but to be practical keep it smaller than a few million, because the total accumulated size of all your TableArrays must remain under 2 ** 31 - 1 (a little more than 2 billion) because that's the limit of how high integers can go.

If you're dealing with more randomly-accessed numbers or consistently very large indices in the parent-key field and couldn't previously accomplish it with a pre-sized TableArray, I have (on July 21, 2015) updated this to NewTable 4.0 where you can instantiate HashTables to do just that!

I now imagine this a completed project, so please let me know if there's more you'd like to see in the future and I'll take it into consideration.


JASS:
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
    
    One map, one hashtable. Welcome to NewTable 4.1.0.1
    
    This newest iteration of Table introduces the new HashTable struct.
    You can now instantiate HashTables which enables the use of large
    parent and large child keys, just like a standard hashtable. Previously,
    the user would have to instantiate a Table to do this on their own which -
    while doable - is something the user should not have to do if I can add it
    to this resource myself (especially if they are inexperienced).
    
    This library was originally called NewTable so it didn't conflict with
    the API of Table by Vexorian. However, the damage is done and it's too
    late to change the library name now. To help with damage control, I
    have provided an extension library called TableBC, which bridges all
    the functionality of Vexorian's Table except for 2-D string arrays &
    the ".flush(integer)" method. I use ".flush()" to flush a child hash-
    table, because I wanted the API in NewTable to reflect the API of real
    hashtables (I thought this would be more intuitive).
    
    API
    
    ------------
    struct Table
    | static method create takes nothing returns Table
    |     create a new Table
    |    
    | method destroy takes nothing returns nothing
    |     destroy it
    |    
    | method flush takes nothing returns nothing
    |     flush all stored values inside of it
    |    
    | method remove takes integer key returns nothing
    |     remove the value at index "key"
    |    
    | method operator []= takes integer key, $TYPE$ value returns nothing
    |     assign "value" to index "key"
    |    
    | method operator [] takes integer key returns $TYPE$
    |     load the value at index "key"
    |    
    | method has takes integer key returns boolean
    |     whether or not the key was assigned
    |
    ----------------
    struct TableArray
    | static method operator [] takes integer array_size returns TableArray
    |     create a new array of Tables of size "array_size"
    |
    | method destroy takes nothing returns nothing
    |     destroy it
    |
    | method flush takes nothing returns nothing
    |     flush and destroy it
    |
    | method operator size takes nothing returns integer
    |     returns the size of the TableArray
    |
    | method operator [] takes integer key returns Table
    |     returns a Table accessible exclusively to index "key"
*/
    
globals
    private integer less = 0    //Index generation for TableArrays (below 0).
    private integer more = 8190 //Index generation for Tables.
    //Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
    
    private hashtable ht = InitHashtable()
    private key sizeK
    private key listK
endglobals
    
private struct dex extends array
    static method operator size takes nothing returns Table
        return sizeK
    endmethod
    static method operator list takes nothing returns Table
        return listK
    endmethod
endstruct
    
private struct handles extends array
    method has takes integer key returns boolean
        return HaveSavedHandle(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSavedHandle(ht, this, key)
    endmethod
endstruct
    
private struct agents extends array
    method operator []= takes integer key, agent value returns nothing
        call SaveAgentHandle(ht, this, key, value)
    endmethod
endstruct
    
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$(ht, this, key)
    endmethod
    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
    
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
    method operator [] takes integer key returns $TYPE$
        return Load$FUNC$Handle(ht, this, key)
    endmethod
    method operator []= takes integer key, $TYPE$ value returns nothing
        call Save$FUNC$Handle(ht, this, key, value)
    endmethod
    method has takes integer key returns boolean
        return HaveSavedHandle(ht, this, key)
    endmethod
    method remove takes integer key returns nothing
        call RemoveSavedHandle(ht, this, key)
    endmethod
endstruct
private module $TYPE$m
    method operator $TYPE$ takes nothing returns $TYPE$s
        return this
    endmethod
endmodule
//! endtextmacro
    
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
    
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
    
struct Table extends array
    
    // Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
    implement realm
    implement integerm
    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 hashtablem
    
    method operator handle takes nothing returns handles
        return this
    endmethod
    
    method operator agent takes nothing returns agents
        return this
    endmethod
    
    //set this = tb[GetSpellAbilityId()]
    method operator [] takes integer key returns Table
        return LoadInteger(ht, this, key) //return this.integer[key]
    endmethod
    
    //set tb[389034] = 8192
    method operator []= takes integer key, Table tb returns nothing
        call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
    endmethod
    
    //set b = tb.has(2493223)
    method has takes integer key returns boolean
        return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
    endmethod
    
    //call tb.remove(294080)
    method remove takes integer key returns nothing
        call RemoveSavedInteger(ht, this, key) //call this.integer.remove(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
        local Table this = dex.list[0]
        
        if this == 0 then
            set this = more + 1
            set more = this
        else
            set dex.list[0] = dex.list[this]
            call dex.list.remove(this) //Clear hashed memory
        endif
        
        debug set dex.list[this] = -1
        return this
    endmethod
    
    // Removes all data from a Table instance and recycles its index.
    //
    //     call tb.destroy()
    //
    method destroy takes nothing returns nothing
        debug if dex.list[this] != -1 then
            debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
            debug return
        debug endif
        
        call this.flush()
        
        set dex.list[this] = dex.list[0]
        set dex.list[0] = this
    endmethod
    
    //! runtextmacro optional TABLE_BC_METHODS()
endstruct
    
//! runtextmacro optional TABLE_BC_STRUCTS()
    
struct TableArray extends array
    
    //Returns a new TableArray to do your bidding. Simply use:
    //
    //    local TableArray ta = TableArray[array_size]
    //
    static method operator [] takes integer array_size returns TableArray
        local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
        local TableArray this = tb[0]         //The last-destroyed TableArray that had this array size
        
        debug if array_size <= 0 then
            debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
            debug return 0
        debug endif
        
        if this == 0 then
            set this = less - array_size
            set less = this
        else
            set tb[0] = tb[this]  //Set the last destroyed to the last-last destroyed
            call tb.remove(this)  //Clear hashed memory
        endif
        
        set dex.size[this] = array_size //This remembers the array size
        return this
    endmethod
    
    //Returns the size of the TableArray
    method operator size takes nothing returns integer
        return dex.size[this]
    endmethod
    
    //This magic method enables two-dimensional[array][syntax] for Tables,
    //similar to the two-dimensional utility provided by hashtables them-
    //selves.
    //
    //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
            local integer i = this.size
            if i == 0 then
                call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
                return 0
            elseif key < 0 or key >= i then
                call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
                return 0
            endif
        endif
        return this + key
    endmethod
    
    //Destroys a TableArray without flushing it; I assume you call .flush()
    //if you want it flushed too. This is a public method so that you don't
    //have to loop through all TableArray indices to flush them if you don't
    //need to (ie. if you were flushing all child-keys as you used them).
    //
    method destroy takes nothing returns nothing
        local Table tb = dex.size[this.size]
        
        debug if this.size == 0 then
            debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
            debug return
        debug endif
        
        if tb == 0 then
            //Create a Table to index recycled instances with their array size
            set tb = Table.create()
            set dex.size[this.size] = tb
        endif
        
        call dex.size.remove(this) //Clear the array size from hash memory
        
        set tb[this] = tb[0]
        set tb[0] = this
    endmethod
    
    private static Table tempTable
    private static integer tempEnd
    
    //Avoids hitting the op limit
    private static method clean takes nothing returns nothing
        local Table tb = .tempTable
        local integer end = tb + 0x1000
        if end < .tempEnd then
            set .tempTable = end
            call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
        else
            set end = .tempEnd
        endif
        loop
            call tb.flush()
            set tb = tb + 1
            exitwhen tb == end
        endloop
    endmethod
    
    //Flushes the TableArray and also destroys it. Doesn't get any more
    //similar to the FlushParentHashtable native than this.
    //
    method flush takes nothing returns nothing
        debug if this.size == 0 then
            debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
            debug return
        debug endif
        set .tempTable = this
        set .tempEnd = this + this.size
        call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
        call this.destroy()
    endmethod
    
endstruct
    
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array

    //Enables myHash[parentKey][childKey] syntax.
    //Basically, it creates a Table in the place of the parent key if
    //it didn't already get created earlier.
    method operator [] takes integer index returns Table
        local Table t = Table(this)[index]
        if t == 0 then
            set t = Table.create()
            set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
        endif
        return t
    endmethod

    //You need to call this on each parent key that you used if you
    //intend to destroy the HashTable or simply no longer need that key.
    method remove takes integer index returns nothing
        local Table t = Table(this)[index]
        if t != 0 then
            call t.destroy()
            call Table(this).remove(index)
        endif
    endmethod
    
    //Added in version 4.1
    method has takes integer index returns boolean
        return Table(this).has(index)
    endmethod
    
    //HashTables are just fancy Table indices.
    method destroy takes nothing returns nothing
        call Table(this).destroy()
    endmethod
    
    //Like I said above...
    static method create takes nothing returns thistype
        return Table.create()
    endmethod

endstruct

endlibrary

JASS:
local HashTable hash = HashTable.create() //create it
set hash['hfoo'][StringHash("poop")] = 66 //access large parent and child keys as needed
set hash['hfoo'].unit[99999] = GetTriggerUnit() //still works with multiple-type syntax so you still have the full hashtable API.
call hash.remove('hfoo') //This literally is calling FlushChildHashtable, and should be used when the parentkey and/or HashTable are to be retired
call hash.destroy() //DOES NOT FLUSH THE HASHTABLE. You must manually remove each parent key, first, otherwise you'll have a lot of leaked Tables.

JASS:
library TableBC requires Table
/*
    Backwards-compatibility add-on for scripts employing Vexorian's Table.

    Added 31 July 2015: introduced static method operator [] and
    static method flush2D for Table, HandleTable and StringTable. Now, 
    almost all of the Vexorian API has been replicated (minus the .flush paradox).

    The Table library itself was unchanged to implement these
    enhancements, so you need only update this library to experience the
    improved syntax compatibility.
    
    Disclaimer:
    
    The following error does not occur with HandleTables & StringTables, only
    with the standard, integer-based Table, so you do not need to make any 
    changes to StringTable/HandleTable-employing scripts.
    
    The this.flush(key) method from the original Table cannot be parsed with
    the new Table. For the scripts that use this method, they need to be up-
    dated to use the more fitting this.remove(key) method.
    
    Please don't try using StringTables/HandleTables with features exclusive
    to the new Table as they will cause syntax errors. I do not have any plan
    to endorse these types of Tables because delegation in JassHelper is not 
    advanced enough for three types of Tables without copying every single
    method over again (as you can see this already generates plenty of code).
    StringTable & HandleTable are wrappers for StringHash & GetHandleId, so
    just type them out.
*/

//! textmacro TABLE_BC_METHODS
    method reset takes nothing returns nothing
        call this.flush()
    endmethod
    method exists takes integer key returns boolean
        return this.has(key)
    endmethod
    static method operator [] takes string id returns Table
        local integer index = StringHash(id)
        local Table t = Table(thistype.typeid)[index]
        if t == 0 then
            set t = Table.create()
            set Table(thistype.typeid)[index] = t
        endif
        return t
    endmethod
    static method flush2D takes string id returns nothing
        local integer index = StringHash(id)
        local Table t = Table(thistype.typeid)[index]
        if t != 0 then
            call t.destroy()
            call Table(thistype.typeid).remove(index)
        endif
    endmethod
//! endtextmacro

//! textmacro TABLE_BC_STRUCTS
struct HandleTable extends array
    static method operator [] takes string index returns thistype
        return Table[index]
    endmethod
    static method flush2D takes string index returns nothing
        call Table.flush2D(index)
    endmethod
    method operator [] takes handle key returns integer
        return Table(this)[GetHandleId(key)]
    endmethod
    method operator []= takes handle key, integer value returns nothing
        set Table(this)[GetHandleId(key)] = value
    endmethod
    method flush takes handle key returns nothing
        call Table(this).remove(GetHandleId(key))
    endmethod
    method exists takes handle key returns boolean
        return Table(this).has(GetHandleId(key))
    endmethod
    method reset takes nothing returns nothing
        call Table(this).flush()
    endmethod
    method destroy takes nothing returns nothing
        call Table(this).destroy()
    endmethod
    static method create takes nothing returns thistype
        return Table.create()
    endmethod
endstruct

struct StringTable extends array
    static method operator [] takes string index returns thistype
        return Table[index]
    endmethod
    static method flush2D takes string index returns nothing
        call Table.flush2D(index)
    endmethod
    method operator [] takes string key returns integer
        return Table(this)[StringHash(key)]
    endmethod
    method operator []= takes string key, integer value returns nothing
        set Table(this)[StringHash(key)] = value
    endmethod
    method flush takes string key returns nothing
        call Table(this).remove(StringHash(key))
    endmethod
    method exists takes string key returns boolean
        return Table(this).has(StringHash(key))
    endmethod
    method reset takes nothing returns nothing
        call Table(this).flush()
    endmethod
    method destroy takes nothing returns nothing
        call Table(this).destroy()
    endmethod
    static method create takes nothing returns thistype
        return Table.create()
    endmethod
endstruct
//! endtextmacro

endlibrary

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[GetHandleId(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 TableArray da = TableArray[0x2000]
 
//Use it:
local thistype this = 0
loop
    set this = this.next
    exitwhen this == 0
    set this.save = this.save + 1
    set da[this].real[this.save * 3] = GetUnitX(this.unit)
    set da[this].real[this.save * 3 + 1] = GetUnitY(this.unit)
    set da[this].real[this.save * 3 + 2] = GetUnitFlyHeight(this.unit)
endloop
 
//Flush/destroy it:
call da.flush()
 
//Or, only destroy it (more efficient if you manage memory yourself)
call da.destroy()
Note the power of TableArray, how it splices the one hashtable into a many-dimensional array. This means you can have a multi-dimensional array (and more) per system.
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
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 : \.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
->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:

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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:

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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:

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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:

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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.
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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?)
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
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.
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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?
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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:

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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.
 

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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:

Bribe

Code Moderator
Level 48
Joined
Sep 26, 2009
Messages
9,264
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