• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Trigger] Hashtable

Status
Not open for further replies.
Level 6
Joined
Oct 1, 2012
Messages
166
Well, I'm guessing you'd prefer it in GUI, so here it goes, the simples hash ever.

  • Hashtables
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set Hash = (Last created hashtable)
      • Hashtable - Save 4 as 1 of 1 in Hash
      • Hashtable - Save 15.00 as 1 of 1 in Hash
      • Unit - Create (Load 1 of 1 from Hash) Footman for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
      • Unit - Set life of (Last created unit) to (Load 1 of 1 from Hash)
Hashtables, as I always understood them (and it serves me quite well to this day) are two dimensional coordinate systems with X and Y values always being integers. To put more simply - these are tables of values with coordinates (x, y).

You can save numeric values to every "coordinate", but you can also save other values (like a player), but I myself always prefer doing it only with numbers, as once I tried saving ability handle and the game crashed (yet that might be because of the fact, that abilities are recognized by the game as integers and it is a little messed up, but whatever, that's not the case).

Anywho... as seen in this trigger, first thing you have to do is create a hashtable. Then you save it as a variable with your chosen name. You can of course make it shorter like this:

  • Custom script: set udg_Hash = InitHashtableBJ()
But this doesn't really matter as this won't make any problems anyhow.

After you made a hashtable, you can freely save some values to it. You can save integers, reals, strings and handles (the last being, say, units or players, as I mentioned above, but not abilities or items, as these are, in fact, integers).

As you see, I saved integer "4" to X=1 and Y=1 in the Hash hashtable. I also saved real "15.00" to X=1 and Y=1 in the same hashtable. These do not collide with one another as they are of a different type.

So, these are the basics. If you want to make a spell with hashtables, then you must remember, that you can't save an ability to the hastable, you can save it's code. If you want to get to know what is the ability's code, go to Ability Editor and push CTRL+D. Then, next to every ability's name, you will see it's code (like A000 for your first custom-made ability or Aply for polimorph). Same goes with items, buffs, unit types, etc.

And one more thing: A000 is an integer in ASCII, you put it in the hastable as 'A000' like that:

  • Custom script: SaveIntegerBJ('A000', 1, 1, udg_Hash)
I really didn't know what to tell you, so I guess that's what you wanted.

Cheers
 
Level 28
Joined
Sep 26, 2009
Messages
2,520
Hashtables are a way to save something under 2 different "indexes", where the order of both indexes is important. E.g. saving something under "[2][1]" is not same as saving it under "[1][2]" - you'll see why below.

Hashtables can (or rather: "should") save practically everything you would need to save in WCIII, but afaik it's not the case - just like Rybak pointed out, some actions are buggy... like Ability that will crash your WE.

Note:
These actions are buggy all the time, so if they crash your WE once, they will do that every time -> actions that do not do that are not buggy.
So if you're not sure what actions are buggy, you can open empty map and try that action yourself.



#1 - Creating Hashtable
First, you have to create hashtable. This actions should be done only once per hashtable - meaning if you use Hashtable for spell, then creating new Hashtable every time someone casts the spell is a big NO NO.
Also there's limit for maximum number of hashtables. I thinks it's somewhere around ~250, however if you generalize and correctly use hashtables, then you won't need many (better said: you may use 1 hashtables for multiple systems).

Here's how you create hashtable:
  • Actions
    • Hashtable - Create a hashtable
    • Set Hashtable_var = (Last created hashtable)
Or you can use the action rybak wrote:
  • Custom script: set udg_Hashtable_variable_name = InitHashtableBJ()
(although there may be better way to create it through custom script, as many BJ functions are replaced by better ones in JASS).

Indexes for hashtable can either be integers or strings.
Note
For hashtable, the indexes are actually called 'Keys', so if you are to specify what 'Key' you want to use, then it means that it wants the *index*... however 'Key' is more appropriate, as the word "index" implies that it will be *integer* number, while "Key" can stand for "Key word", which is true for both Integers and strings.



#2 - Saving/Loading data in/from Hashtable
This is how you save things in hashtable:
  • Hashtable - Save *something* as *Child* of *Parent* in *Hashtable name*
Of course, you have to specify what you want to save - if you want to save number without decimal point, choose the "Save integer" option; if you want to save True/False statement, use "Save Boolean" option, etc.
E.g. I will save Boolean value 'TRUE' into hashtable.
  • Hashtable - Save True as 1 of 2 in Hashtable_var

Loading value from hashtable is done in 2 ways:
a) By loading that value into variable/array
  • Set Boolean_var = (Load 1 of 2 from Hashtable_var)
  • Set Boolean_arr[1] = (Load 1 of 2 from Hashtable_var)
Now the variable "Boolean_var" and array "Boolean_arr[1]" will contain the value you saved into hashtable under parent '2' and child '1'.

b) By loading the value directly - you simply load the value immediately when you are using it, instead of loading it into variable first and then using the variable. However I find most of the time that it doesn't work - it returns the default value of said variable type.

If the stuff you want to load is not in the hashtable, then it will return:
a) NULL - this is the case of all Handles (e.g. units, points, player groups, etc.)
b) default value - that's the case of non-handles (e.g. integers, booleans, reals, ...)

Now why are the indexes important. First let's look again how the command for saving anything into hashtable looks:
  • Hashtable - Save *something* as *Child* of *Parent* in *Hashtable name*
Notice the *Child* and *Parent* - these are the keys that can be either integers or strings.


#3 - How saving/loading works
This is how hashtable would look if you were to visualize it:
attachment.php
When you save stuff into hashtable, the game first finds the Hashtable name you used (= hashtable variable you used), then it finds the Parent key and then it finds the Child key. Then it saves that information into correct variable type it represents (e.g. Boolean won't be saved as integer, but as boolean).

The same goes for loading. The game first finds the Parent, then it finds the Child and lastly, it finds the correct variable type.
What does this imply: You can save different variable types under same Child of same Parent of same Hashtable.
If you do this:
  • Actions
    • Hashtable - Save 10.00 as 1 of 1 in Hashtable_var
    • Hashtable - Save 55 as 1 of 1 in Hashtable_var
    • Hashtable - Save True as 1 of 1 in Hashtable_var
    • Hashtable - Save stringXYZ as 1 of 1 in Hashtable_var
And then you load... e.g. integer from child '1' of parent '1' from Hashtable_var, then it will correctly return number 55 as that value was saved as an integer in 1 of 1 in Hashtable_var.

Of course, doing this:
  • Actions
    • Hashtable - Save 10.00 as 1 of 1 in Hashtable_var
    • Hashtable - Save 35.49 as 1 of 1 in Hashtable_var
and then loading "real" from child '1' of parent '1' in Hashtable_var will return number 35.49, because into variable type "real" was first saved number 10.00 and then that number was replaced by number 35.49 as that is real as well.


Example of how it would work in some situation:

1) We have empty Hashtable called Hash for simplicity

2) We save 'True' as 'Forgive' of 'FirstTick' in Hash
attachment.php


This is nothing special. Since there was no such parent, then there could be no such children and so we saved data into unique "layer" in the hashtable - we don't have to worry about rewriting anything.
3) We save 'True' as 'Forgive' of 'Resistance' in Hash
attachment.php


As you can see, even though the child's name is same as in step #2, we save it under different parent, hence these two won't collide with each other.
4) We save integer number 5 as 'Forgive' of 'FirstTick' in Hash
attachment.php


Notice:
We saved different variable type, which is why our Boolean value was not rewritten. If I were to save another Boolean value here, the current one would be removed/replaced.
5) Now some time later, after we have saved many things inside our hashtable we want to load something
attachment.php


The picture shows how we found the real value saved as Magic of Resistance in Hash.

The order at which is the hashtable searched through is shown by numbers. First the parent in said hashtable is searched for (1). If it has been found, it goes through that parent's childs and searches for the one we want (2). If it did find the child, it searches for the correct variable type (3). If correct variable type has been found, it gives us that value.

In case the seach was stopped in any step, it returns us default or null value.


Now we no longer have any need for some/all data in hashtable and we want go get rid of them.
There are two types of clearing hashtables. By now it should be obvious what type of clearing does what, but let's write it just to be sure:

1) Clearing Child
As the name suggests, we clear all data related to Parent in Hashtable - it removes all Childs of specified Parent.
Example: We remove all children of Parent "Resistance" from Hash:
  • Hashtable - Clear all child hashtables of child (Key Resistance) in Hash
attachment.php


As you can see, all the children related to parent "Resistance" are removed. Because parent "Resistance" contains no children after that action, it can no longer contain any data and so we can consider it an empty parent.
Since we cleared only parent "Resistance", this action did not affect the "FirstTick" parent and so its data remain unaffected.
2) Clearing Parent
This completely clears the hashtable, as all parents are removed. I don't think it needs any more explanation than this.


A very important note:
Clearing Hashtable does not mean it removes leaks. It only means that it will return NULL or default value.
If you save point (= location) into hashtable, that location is not assigned to any variable and you clear the hashtable, then you will get memory leak, because you lose all reference to that location.

So how do you clear leaks? Well, it's the same way you remove them normally.

Let's say that in child '1' of parent '1' in hashtable Hashtable_var you saved location. This is how you remove it:
  • Set loc = (Load 1 of 1 in Hashtable_var)
  • Custom script: call RemoveLocation(udg_loc)
Now, since the possible memory leak has been removed, you can clear the hashtable (or at least the child) without any fear of having memory leaks.



#4 - Using handle ID
Handle IDs are great way to use the identification number of any handle as either Parent, Child or even the value itself.
The ID is always an integer number and every handle has it.

What are handles?
Well, they're basically everything in WCIII -> units are handles, player groups are handles, players, points, unit groups, special effects, etc. etc. are all handles.
What isn't a handle: integers, real numbers, boolean values (there may be some more than this, but there are the most commonly used ones).

Each handle upon creation has its unique ID. Example situation:
You have 2 Footman units in map. Even though they are same unit-types, they are each different handle - different object. Hence they have different handle IDs.

You could say that Handle ID is the same as unit indexing systems found on hive (these assign unique number for each unit in map), but handle ID is not limited to units only.
However the ID is quite a large ingeted number (around 7 or more digits) so they can't be used as indexes for variables... however they can be used as Child or Parent keys for hashtables.

Getting handle ID of a unit is in GUI usually done this way:
  • Set int = (Key (Triggering unit))
this will assing the handle ID of (Triggering unit) into the integer variable "int".

However there is a better way:
  • Custom script: set udg_Integer_variable_name = GetHandleId(handle)
This approach is better because you are not limited to function calls like "Triggering unit" or "Last created special effect" etc. -> you can also use variables, which makes the whole process faster and simpler.

Example: I will assign the handle id into integer variable called IntID by using:
a) function call:
  • Custom script: set udg_IntId = GetHandleId(GetTriggerUnit())
GetTriggerUnit() is the jass equivalent for GUI's (Triggering Unit)

b) variables:
  • Set unit_var = (Triggering unit)
  • Custom script: set udg_IntId = GetHandleId(udg_unit_var)

Well, let's say I want to save "Target point of (ability being cast)" into Hashtable for some MUI spell. Since the spell is MUI, I cannot save it under some predetermined keys (e.g.: "... as 1 of 1 in Hash"), as it would replace the current point that is being saved there. That's where the Handle ID comes in - using Handle ID as e.g. Child key will save that location into unique layer. That location will be replaced only if the SAME unit casts the spell again.
  • Custom script: set udg_ID = GetHandleId(GetTriggerUnit())
  • Hashtable - Save Handle Of(Target point of ability being cast) as (Key Spell_TargetPoint) of ID in Hashtable_var
Do note: If you are using Handle ID as a parent of some Hashtable and into that very same Hashtable you save other important things related to that unit (so you save them under handle ID as well), then clearing all children of Parent (Handle ID) from that hashtable will not be the best idea, because you will loose all children - that includes those that still hold meaningful and important data of that unit.


I think I covered all important parts of hashtables.

Hope it helps :thumbs_up:



Attachments:
 

Attachments

  • Hashtable1.png
    Hashtable1.png
    2 KB · Views: 475
  • Hashtable2.png
    Hashtable2.png
    3.3 KB · Views: 451
  • Hashtable3.png
    Hashtable3.png
    3.7 KB · Views: 490
  • Hashtable4.png
    Hashtable4.png
    6 KB · Views: 464
  • Hashtable5.png
    Hashtable5.png
    9.7 KB · Views: 496
  • Hashtable.png
    Hashtable.png
    10.4 KB · Views: 538
Last edited:
@Nichilus, great man to write all this. :thumbs_up:

With "InitHashtableBJ()" you were right, just remove the BJ and the result will be the same here, but faster.

Indexes for hashtable can either be integers or strings.
What do you mean with strings as index?

Your "Hashtable pic" is not working.

And to this Save Ability bug. It bugs in gui, yes, but you can save abilities with jass/custom script. You can save their raw-code as integers, just like you can do with unit types.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
As you see, I saved integer "4" to X=1 and Y=1 in the Hash hashtable. I also saved real "15.00" to X=1 and Y=1 in the same hashtable. These do not collide with one another as they are of a different type.

just to elaborate a bit more, all handles share the same "space", so storing unit at the place where location was stored will overwrite it

I thinks it's somewhere around ~250...

Indexes for hashtable can either be integers or strings.

You can have either 255 or 256 initialized hashtables per map(I tested this earlier, but I forgot haha).

Please elaborate on "integers or strings". Strings are used in gamecache.

Simple check from common.j:

native SaveInteger takes hashtable table, integer parentKey, integer childKey, integer value returns nothing

b) default value - that's the case of non-handles (e.g. integers, booleans, reals, ...)

oddly enough,
JASS:
scope s initializer i
    
    private function i takes nothing returns nothing
        local hashtable h = InitHashtable()
        local string s = LoadStr(h, 0, 0)
        if s == null then
            call BJDebugMsg("null")
        elseif s == "" then
            call BJDebugMsg("\"\"")
        endif
    endfunction
    
endscope

shows that even strings will return null not ""


So how do you clear leaks? Well, it's the same way you remove them normally.

Let's say that in child '1' of parent '1' in hashtable Hashtable_var you saved location. This is how you remove it:
  • Set loc = (Load 1 of 1 in Hashtable_var)
  • Custom script: call RemoveLocation(udg_loc)
Now, since the possible memory leak has been removed, you can clear the hashtable (or at least the child) without any fear of having memory leaks.

this does not remove all "leaks", because you still leak handleId of the location, but is good enough in most cases

What isn't a handle: integers, real numbers, boolean values (there may be some more than this, but there are the most commonly used ones).

Neither string nor code(this is really nonexistant in GUI, I agree) are handles as well

However there is a better way:
  • Custom script: set udg_Integer_variable_name = GetHandleId(handle)
This approach is better because you are not limited to function calls like "Triggering unit" or "Last created special effect" etc. -> you can also use variables, which makes the whole process faster and simpler.

This is also required way if you use JNGP, because it has no "Key" function in GUI

This should cover all up. Other than this, the post is almost worth a Tutorial submission
 
Status
Not open for further replies.
Top