1. Head to the 33rd Modeling Contest Poll and drink to your heart's desire.
    Dismiss Notice
  2. Choose your means of doom in the 17th Mini Mapping Contest Poll.
    Dismiss Notice
  3. A slave to two rhythms, the 22nd Terraining Contest is here.
    Dismiss Notice
  4. The heavens smile on the old faithful. The 16th Techtree Contest has begun.
    Dismiss Notice
  5. The die is cast - the 6th Melee Mapping Contest results have been announced. Onward to the Hive Cup!
    Dismiss Notice
  6. The glory of the 20th Icon Contest is yours for the taking!
    Dismiss Notice
  7. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[Snippet] New Table

Discussion in 'JASS Resources' started by Bribe, Jan 25, 2011.

  1. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    This is a preview of version 4.2 which implements some recently-requested features. The biggest change is the addition of a "tracked HashTable" configuration which allows one (if the feature is enabled) to not have to keep track of the indices in their HashTables, so that now once someone decides to destroy a HashTable they do not have to worry about clearing it out as the system will handle it automatically.

    I am not 100% sure if I have correctly done the allocation and deallocation of references to the referenced container Tables, so if you want to test this out for yourselves please feel free.

    I have not added the Reforged natives to the generated list as I don't see much value in doing so, and it would also break the system for users on older versions of WC3 who don't have the storage space on their HDD for Reforged or want the performance benefits of not being on Reforged.

    Code (vJASS):

    library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.2.0.0
     
        One map, one hashtable. Welcome to NewTable.
     
        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 constant boolean TRACKED_HASHES = false //Set to True to allow the HashTable struct to remember its indexes
     
        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 operator []= takes integer key, handle h returns nothing
            if h != null then
                call SaveFogStateHandle(ht, this, key, ConvertFogState(GetHandleId(h)))
            elseif HaveSavedHandle(ht, this, key) then
                call RemoveSavedHandle(ht, this, key)
            endif
        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 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

    //Newly added in 4.2 - can automatically track HashTable indices.
    private module TRACKER
        static if TRACKED_HASHES then
            static HashTable tracker = 0
            private static method onInit takes nothing returns nothing
                set tracker = Table.create()
            endmethod
        endif
    endmodule
     
    //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

        implement TRACKER

        //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
            static if TRACKED_HASHES then
                local integer i
            endif
            local Table t = Table(this)[index]
            if t == 0 then
                set t = Table.create()
                set Table(this)[index] = t
                static if TRACKED_HASHES then
                    set t = tracker[this]
                    set i = t[0] + 1
                    set t[0] = i
                    set t[i] = index
                endif
            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
            static if TRACKED_HASHES then
                local integer i
                local integer j
            endif
            local Table t = Table(this)[index]
            if t != 0 then
                call t.destroy()               //clear indexed table
                call Table(this).remove(index) //clear reference to that table
                static if TRACKED_HASHES then
                    set t = tracker[this]
                    set i = 0
                    loop
                        set i = i + 1
                        exitwhen t[i] == index //removal is o(n) based
                    endloop
                    set j = t[0]
                    if i < j then
                        set t[i] = t[j] //pop last item in the stack and insert in place of this removed item
                    endif
                    call t.remove(j) //free reference to the index
                    set t[0] = j - 1 //decrease size of stack
                endif
            endif
        endmethod
     
        //Added in version 4.1
        method has takes integer index returns boolean
            return Table(this).has(index)
        endmethod
     
        //HashTables are mostly just fancy Table indices.
        method destroy takes nothing returns nothing
            static if TRACKED_HASHES then
                local Table t = tracker[this] //tracker table
                local Table t2                //sub-tables of the primary HashTable
                local integer i = t[0]
                loop
                    exitwhen i == 0
                    set t2 = t[i]
                    call t2.destroy()           //clear indexed sub-table
                    call Table(this).remove(t2) //clear reference to sub-table
                    set i = i - 1
                endloop
                call t.destroy()           //clear indexed tracker table
                call Table(t).remove(this) //clear reference to tracker table
            endif
            call Table(this).destroy()
        endmethod
     
        static method create takes nothing returns thistype
            static if TRACKED_HASHES then
                local HashTable ht = Table.create()
                set tracker[ht][0] = 0
                return ht
            else
                return Table.create()
            endif
        endmethod

    endstruct

    endlibrary

     
     
    Last edited: Sep 19, 2020
  2. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,533
    Resources:
    9
    Models:
    1
    Icons:
    2
    Maps:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    9
    Currently, Table does not compile when TRACKED_HASHES is true.

    Code (vJASS):

    struct HashTable extends array
        method operator [] takes integer index returns Table
            static if TRACKED_HASHES then
                local integer i
                local Table t
            endif
            local Table t = Table(this)[index]
            //
        endmethod

        method remove takes integer index returns nothing
            static if TRACKED_HASHES then
                local integer i
                local integer j
                local Table t
            endif
            local Table t = Table(this)[index]
            //
        endmethod
     


    So far, the allocation and deallocation of container tables appears to be handled quite well. Here's my test snippet:

    Code (vJASS):

    library TableTest initializer Init requires Table
    globals
        private HashTable tt         = 0
    endglobals
    private function OnGameStart takes nothing returns nothing
        set tt = HashTable.create()
        call BJDebugMsg("Current HashTable instance: " + I2S(tt))
        set tt[1][1] = 0
        set tt[2].real[2] = 0.5
        set tt[3][-1] = -27
        call BJDebugMsg("HashTable entries populated")
        call tt.destroy()
    endfunction
    private function Init takes nothing returns nothing
        call TimerStart(CreateTimer(), 1.00, true, function OnGameStart)
    endfunction
    endlibrary
     
     
  3. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,099
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    @Bribe, thanks for taking time into updating Table resource. Could you provide output of 'git log -p -n 1' for your update? Would be so much easier to review.
     
  4. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Thanks for that! I have removed the duplicate Table locals as you pointed out.

    Sorry where do I use that command line? Is it a Notepad++ plugin or something?
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,532
    Resources:
    23
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    5
    JASS:
    3
    Resources:
    23
    It's a command for a git repository. He's probably supposing you are using it.
     
  6. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Thanks, I haven't used it before but it's been recommended to me at least once or twice. Maybe I should start an account.
     
  7. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    478
    Resources:
    0
    Resources:
    0
    you dont need an account for git. it's just a simple program)
     
  8. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Hmm new information again. Ok well I created a GitHub account so I can have a repository going forward. I'll see if I can figure out how to create the repo and get stuff on there.
     
  9. LeP

    LeP

    Joined:
    Feb 13, 2008
    Messages:
    478
    Resources:
    0
    Resources:
    0
    Github is just ONE offer of git hosting. Git works perfectly fine just on your local computer. In fact even if you use github you still have your code on your computer.
     
  10. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Lately I am using my laptop less and less and have to do most of everything from my phone. The issue is that my laptop with 64GB of storage is just not compatible with WarCraft 3 anymore as I needed to run Windows updates which forced me to uninstall WC3 to make room for the buffer.

    Do you know of a good Git apk for Android? I can't see myself firing up my laptop for this purpose.
     
  11. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,099
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    You do not need Warcraft3 installed to do compilation checks. I'm working via VisualCode with vJass and Wurst both. And only when required, I'm injecting .j code into the map and testing. Even wrote a tutorial about this.
     
    Last edited: Sep 20, 2020
  12. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    You can check out BribeFromTheHive/NewTable as I have just made the two versions comparable with that view.
     
  13. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    565
    Resources:
    14
    Spells:
    8
    Tutorials:
    1
    JASS:
    5
    Resources:
    14
    @Bribe, could this line in the destroy method
    Code (vJASS):
    call Table(t).remove(this)
    supposed to be
    Code (vJASS):
    call Table(t).remove(0)
    ?
     
  14. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,364
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    Thanks for that, it was actually supposed to be tracker.remove(this)

    I am actually now working on 5.0 and am abandoning my earlier preview idea of 4.2 in favor of a separate HashTableEx struct so that users can have both tracked and non-tracked hashtables in the same map.

    BribeFromTheHive/NewTable