• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Lua][vJass] New Table

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
It's all about the allocated space.

Hashtables have parent and child keys. The original Table by Vexorian, and the basic Table type used in this resource, use parent keys above 0 for the Table indices. Vexorian used the volatile StringHash to make 2D syntax, but that ha a risk of random data collision instead of a clean reserved space like this one has.

TableArrays are negative integers. If you BJDebugMsg a TableArray variable, you'll see a negative integer at least the array size less than 0. The [] syntax in TableArrays simply adds the value to the negative integer to grab the correct parent key. For example, a TableArray size 10 will have index -10. The index at 0 will access -10. The max index, at 9, will access -1.

The parent key 0 is unused in all cases. It is too dangerous to risk giving it to a user.

Table[] syntax returns Tables due to the recursive potential without needing to typecast every index.

A HashTable is a Table which automatically creates Tables as needed to represent parent key functionality. Accessing ht['hfoo'][whatever] for the first time will create a Table and store whatever inside it. Second access of the 'hfoo' parent key will retrieve the already-stored Table which contains the value whatever.

To recap, Tables are positive integers, TableArrays are a reserved group of negative integers and HashTables are Tables which delegate Tables within themselves. In every case, the very last [bracket] in the syntax is used as the child key. Now you know how one hashtable is used for the entire resource.

The backwards-compatible Table["somestring"][someIndex] works safely because the static method operator [] within Table is simply a HashTable.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Ok, just to be sure, so you can't use the 2d syntax for Table ?
But i could use either HashTable or TableArray for 2d syntax.
I suppose that TableArray is more efficient, but you have to define the size before use.

Is there a practical limit of Hashtables/Table/TableArray creations (i mean living at the same time), or is it much more than the usual 8190 ?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
JASS:
HashTable hash
TableArray tab_array
Table tab

// create them ...

set hash[parent][child]=whatever
set tab_array[parent][child]=whatever
set tab[parent][child]=whatever // illegal

EDIT :


I've noticed that flushing a TableArray is O(N), in some cases you can indeed only destroy it without flushing it.
But then the "defaults" value of the array "index" will be "random", when it will be re-allocated.
So i would prefer use an HashTable i think in my case, since they are used dynamically.

Also, in my case, i don't need the 2d syntax for TableArray, i just need to create some of them.
But it's just to know if 2d syntax is supported or not.
 
Bribe, may I ask why does this throw an error:
attachment.php


The only solution was to enclose the values inside a parenthesis (see if-statement)
 

Attachments

  • wat.png
    wat.png
    12.8 KB · Views: 394

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
Bribe, may I ask why does this throw an error:
attachment.php


The only solution was to enclose the values inside a parenthesis (see if-statement)

Because operator [] returns a Table and there is no operator < implemented? if you want to compare "pointers" do integer(t[i]) < ...
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
I don't want to compare pointers lol

I want to compare the values saved in the tables obviously

I know, but your code doesn't show this. Your code wants to compare tables which ofcourse doesnt make sense.
As tables can store any type you have to specify which types to compare. I have never used this but i guess something like t[i].integer

That's the price you pay for having the ability to do table[1][2][3][4]
e: or the price to store multiple types? as i said, i don't use this
e: i guess it's a combination of both
 
Last edited:

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
It's okay, I have just enclosed them in parentheses e.g. (table[indexA]) > (table[indexB])

Still, thanks for the explanation :D

That feels like a jasshelper bug. use the endorsed integer(t[i]) i would say. Or rather use t[i].integer as integer() ist "pointer"
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Say I have this:
JASS:
scope Test initializer TheTest
   
    struct HashTable3D extends array
        method operator [] takes integer index returns HashTable
            return HashTable(this)[index]
        endmethod
    endstruct
   
    function TheTest takes nothing returns nothing
        local HashTable3D ht = HashTable.create()
        call BJDebugMsg("ht = " + I2S(ht))
        set ht[1][2][3] = 42
        set ht[11][12][13] = 43
        //How to flush?
       
    endfunction
   
endscope
How can I properly remove the value in ht[1][2][3] and ht[11][12][13] while also removing all internal "Tables within Table"? Basically, how can I clear/flush those values properly?
 
Level 4
Joined
Jan 27, 2016
Messages
89
Any reason this might not work? Was trying for a while to get a hashtable thats not local to work with no luck.
 

Attachments

  • ayy.PNG
    ayy.PNG
    8.8 KB · Views: 113
Level 22
Joined
Sep 24, 2005
Messages
4,821
I think you should Custom script:endfunction first. Also, the last line should be above the endfunction.

Code:
DisplayToForce mumbo jumbo
Custom script:endfunction
Custom script:global
Custom script:your stuff here
Custom script:endglobal

Sorry for the shitty code.
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
Shouldn't the Hashtable keep track of all used parent keys so the user doesn't have to deallocate them himself? The operator doesn't inline as it has multiple lines anyway so I feel it would be an added improvement.

I mean, since HashTable is just a table of tables, adding one more table for indexing used parent keys should be fine in my oppinion and leaving this to the user seem just unnecessary to me.

Also, since we can't know which key the user uses it should probably have a external table mapped to the current table rather than being held internally, if that made any sense?

function indexParentKey takes HashTable hash, integer parentKey // index function deindexParentKey takes HashTable hash, integer parentKey // deindex function flushIndex takes HashTable hash returns nothing // removes the entire index

Alternativly something like this

JASS:
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!
            call IndexTable(this, t)
        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 DeindexTable(this, t)
            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
        local integer i = 0
        local Table table = GetIndexTable(this)
        local integer size = table[-1]           
        local Table t
        loop
            exitwhen i == size
            set t = table[i]
            call t.destroy()
            set i = i + 1
        endloop
        call RemoveTableIndex(this)
        call Table(this).destroy()
    endmethod
  
    //Like I said above...
    static method create takes nothing returns thistype
        return Table.create()
    endmethod

endstruct
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
That increases the operation count and the load on each HT call. I've left it up to the user because the code is otherwise quite bulky. I could've added operations to access keys like an array, but decided against it in the end. The reasoning is that the need to access such high values from both a parent and child perspective was already so niche that putting work into developing a highly robust version of it would be a waste of my time.

That was some years ago though and you seem like you've found a scenario I haven't encountered. What is the purpose of such wild and dynamic data?
 
Level 15
Joined
Nov 30, 2007
Messages
1,202
That increases the operation count and the load on each HT call. I've left it up to the user because the code is otherwise quite bulky. I could've added operations to access keys like an array, but decided against it in the end. The reasoning is that the need to access such high values from both a parent and child perspective was already so niche that putting work into developing a highly robust version of it would be a waste of my time.

That was some years ago though and you seem like you've found a scenario I haven't encountered. What is the purpose of such wild and dynamic data?
Well technically I haven't, just needed a HashTable and remebered it was here. But when I read the documentation it not having default deallocation just bothered me.

But I have in the past in one of my graveyarded spells: FarmLand Generator which stored terrain temporarily. So that each unit had a hashtable storing the x and y coordinates the farm used. Take this with a grain of salt though as I don't remeber how it worked exactly as it was so long ago. (Actually i probably stored them in an array).

My personal oppinion is that the solution that requires least user input to manage is better, for those that would rather have speed there could always be static ifs. Just my two cents on the subject anyway.

If i ever run into a scenario when i would need that I'll make sure to come back here and grub about it again! :)
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
4,992
Call me dumb but I'm not sure about how I should go about flushing a TableArray.
JASS:
local TableArray ta = TableArray[0x2000]
local integer hid = GetHandleId(<unit>)
//doing things with hid as the parent index:
set ta[hid].integer[1] = 5
set ta[hid].integer[2] = 7
//later I no longer need to store things under that particular hid but other parent keys are still being used
//do I do nothing?
//or do I:
call ta[hid].flush()
call ta[hid].destroy()
I guess what I'm asking is: if I flush and destroy the table returned by the parent key (in a tablearray) does that fuck up the TA if I need to use that key again later (say another unit was allocated the same handle ID later)? Or does it just create a Table if none exists yet?
 
Level 39
Joined
Feb 27, 2007
Messages
4,992
No that actually was not clear from the documentation though I see why it is. So if I want to use handle IDs as a parent key I'll have to manually nest Tables in Tables and can't actually use a TableArray for that? All I want is 2D (realistically 3D) array indexing where I can use a handle ID as the parent-est key.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
TableArray allocation actually reserves a contiguous set of negative integers. For example if I allocate tablearrays like
JASS:
local TableArray ta = TableArray[5]
local TableArray tb = TableArray[10]
local TableArray tc = TableArray[5]

Let's say ta is the first tablearray allocated in your map, then ta would hold the value of -5 and it reserves negative ints from that value (-5) up to -1. Then the next allocation for tb would return the value of -15 and it would reserve a size of 10 ie, from -15 up to -6, and so on for tc.
And when you do ta[3] for example, it just returns a table whose value is <ta + 3> which is Table(-2). Then you can do what you want with the table like ta[3][4] = 5, which just means Table(-2)[4] = 5.
That is the reason why you cant use a parent key larger than the size of the tablearray as it would give unwanted access to other Tables not included in the reserved Tables of that TableArray.

EDIT:
If you want to use large parent keys like handle ids of units, yes you need to actually nest tables, which is already implemented by the HashTable struct.

EDIT:
if I flush and destroy the table returned by the parent key (in a tablearray) does that fuck up the TA if I need to use that key again later (say another unit was allocated the same handle ID later)?
I totally forgot to answer this but it's fine to flush the Table from a TA then reuse that Table from the same parent key later. In the same way that you can flush an individually allocated Table and store to it again later, but do not destroy any Table returned by a TableArray.
 
Last edited:
Level 12
Joined
Jun 12, 2010
Messages
413
Hey Bribe, do you think you could add the following method to struct handles?

JASS:
    method setValue takes integer key, handle h returns nothing
        if null != h then
            call SaveFogStateHandle(ht, this, key, ConvertFogState(GetHandleId(h)))
        else
            if HaveSavedHandle(ht, this, key) then
                call RemoveSavedHandle(ht, this, key)
            endif
        endif
    endmethod

I was thinking of publishing a system that uses Table that I'll use in my own map, but I need this method to make it easier to set handles without worrying about whether you are passing a null value. The issue with passing a null value is that the native will actually not do anything, so if there was already a value stored, it will remain there.

The reason this method is added to the handle struct and not to others is to avoid having to create a new function for each handle type. This method allows you to work with any handle type while only declaring a single method.

Thanks in advance!

EDIT: A similar method could be added to the agent struct, since that type is inherited by many others and it would be more efficient than the handle version, since it does not require a ConvertFogState workaround.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Hey Bribe, do you think you could add the following method to struct handles?

JASS:
    method setValue takes integer key, handle h returns nothing
        if null != h then
            call SaveFogStateHandle(ht, this, key, ConvertFogState(GetHandleId(h)))
        else
            if HaveSavedHandle(ht, this, key) then
                call RemoveSavedHandle(ht, this, key)
            endif
        endif
    endmethod

I was thinking of publishing a system that uses Table that I'll use in my own map, but I need this method to make it easier to set handles without worrying about whether you are passing a null value. The issue with passing a null value is that the native will actually not do anything, so if there was already a value stored, it will remain there.

The reason this method is added to the handle struct and not to others is to avoid having to create a new function for each handle type. This method allows you to work with any handle type while only declaring a single method.

Thanks in advance!

EDIT: A similar method could be added to the agent struct, since that type is inherited by many others and it would be more efficient than the handle version, since it does not require a ConvertFogState workaround.
You are welcome to code your own safety-enabled version of this resource for your own needs, but I'd rather not cheat other users out of performance gains.
 
Level 12
Joined
Jun 12, 2010
Messages
413
You are welcome to code your own safety-enabled version of this resource for your own needs, but I'd rather not cheat other users out of performance gains.
I don't see how it would really be cheating anyone, since it doesn't affect old code and would just get optimized away if you never called the function. It would just be a new method that you could use if you needed safety, without any backwards-compatability implications. But if you don't mind then I guess I can release a modified version of Table alongside my library, my main gripe with doing so was having to keep it up-to-date with Table itself (even if it just involves copy-pasting a few lines hehe).

EDIT: The reason I chose to make it a method instead of the currently inexisting []= operator for the handle type was precisely to keep open the possibily of an unsafe but faster setter being added for the handle type eventually.
 

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
@_Guhun_ Thanks for your input. In general, releasing new resources that cover base/primitive subjects which fuel high-level stuff above (e.g. spells and effects) should be avoided. Bribe brought you a all-around resource which has found many extensions and usages such as Vector<T> and List<T> mentioned by @IcemanBo (here, to provide "generics" into Jass world). Focus on creating best user experience maps, rather than resurrecting discussion that this forum seen a thousand times.
And if you want to write high-performance code, please join me on linux-kernel battlefields, not the Warcraft3 ones : )
 
Level 12
Joined
Jun 12, 2010
Messages
413
@_Guhun_ Thanks for your input. In general, releasing new resources that cover base/primitive subjects which fuel high-level stuff above (e.g. spells and effects) should be avoided. Bribe brought you a all-around resource which has found many extensions and usages such as Vector<T> and List<T> mentioned by @IcemanBo (here, to provide "generics" into Jass world). Focus on creating best user experience maps, rather than resurrecting discussion that this forum seen a thousand times.
And if you want to write high-performance code, please join me on linux-kernel battlefields, not the Warcraft3 ones : )

Actually, going a bit off-topic here, I think it's quite a good time to write new basic libraries: because of all the new natives that were added, there are a few new niches to be filled. I'm sorry if there was a misunderstanding and you thought that I wanted to release a separate Table library for general uses, that's really not what I wanted to do. In fact, that's why I asked Bribe to add this new method that I needed, instead of releasing a new Table library. When I said I would release it alongside my system, I meant it as a snippet in the same thread, for people who were going to use that system specifically :wgrin:

I'm not sure this specific behaviour of the hashtable natives is something that has been discussed "a thousand times", and it's definitely not documented in the Table library itself. So just having a method that addresses the issue is already sort of documenting it, which would be nice. Also, this isn't really about performance, but about user convenience. It was Bribe who rightfully had some performance concerns about making it the default way to set handles (which isn't what I was suggesting).
 
Level 12
Joined
Jun 12, 2010
Messages
413
@_Guhun_ it almost sounds to me like you should invest in learning some Lua. You do not need the Table library whatsoever in Lua because there are no firm "types" as we know them in JASS.

I haven't made to jump to using Lua (well, actually TypeScript) in my map because of the early desync issues it had and also because it would be a considerable time investment to port everything to Lua. I'm still waiting for more maps to come out that use Lua before I finally make the jump. But that's certainly a good recommendation, since Lua (or basically anything) is so much better than JASS.

Still, I don't think we should just forget about old vJass libraries, since some people will still use JASS for a few years (hell, people used Python 3 (2008) less than Python 2 until recently). In fact I really appreciate that you still update the vJass version of your Damage Engine to this day.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
But if you don't mind then I guess I can release a modified version of Table alongside my library, my main gripe with doing so was having to keep it up-to-date with Table itself (even if it just involves copy-pasting a few lines hehe).

To hone in on this, I'd like to point out that Table hasn't had an update since 31 July 2015 so I don't think you need to worry about having to keep your modified code updated with mine.
 
Level 12
Joined
Jun 12, 2010
Messages
413
To hone in on this, I'd like to point out that Table hasn't had an update since 31 July 2015 so I don't think you need to worry about having to keep your modified code updated with mine.
That's a fair point... for some reason I thought you had recently updated it to include the new types from 1.31, I was just misremembering... I had actually implemented it myself into the library in my map. If it's not getting updated for new types Blizzard may introduce then that's not much of a concern.
 

Bribe

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

JASS:
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:
Currently, Table does not compile when TRACKED_HASHES is true.

JASS:
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:

JASS:
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
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Currently, Table does not compile when TRACKED_HASHES is true.

JASS:
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:

JASS:
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

Thanks for that! I have removed the duplicate Table locals as you pointed out.

@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.

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

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
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.
you dont need an account for git. it's just a simple program)
 

LeP

LeP

Level 13
Joined
Feb 13, 2008
Messages
539
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.
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.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
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.
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.
 
Top