Hashtables and MUI

Level 11
Joined
Oct 13, 2005
Messages
233
Before starting this tutorial, you should be comfortable with using GUI. This tutorial was not made to target those just starting out with GUI and may be confusing for those that are.

What is a hashtable?

For starters, it's a data structure capable of holding almost any kind of data in Wc3. It can hold integers, unit, special effects, and anything else you would ever need to store.

How do we use it?

First thing is first, we need to create a hashtable in order to use it. While we're at it, we'll use a variable to keep track of it.
  • Init
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set hashTable = (Last created hashtable)
Now that we have a hashtable, we can use it in a way that's very similar to using an array. The difference? It requires 2 integers instead of 1 and can hold any type we desire. There's also no limit as to how big or small these integers can be. Here's an example:
  • Example
    • Events
    • Conditions
    • Actions
      • -------- Store the value --------
      • Hashtable - Save 5.00 as 0 of 0 in hashTable
      • -------- Load the value --------
      • Set LoadedValue = (Load 0 of 0 from hashTable)
      • -------- Will show the text: 5.00 --------
      • Game - Display to (All players) the text: (String(LoadedValue))
As you can see, a hashtable is a structure that lets you store things in it. You give it a value to store and 2 integer "keys" that will later be used to retrieve the object you stored. In this example, we could just as easily have used an integer, string, unit, floating text, etc. in place of the real value and they could have all be stored in the hashtable.

Why are Hashtables Useful?

I know what you're thinking. "If hashtables are just giant arrays, why do we need them?" Hashtables by themselves are not terribly useful, but when we use them with Handle IDs they can actually be incredibly useful. They allow us to "attach" data to handles that can be retrieved later. We're going to cover this now.

What is a Handle?

For those needing it, here's a simple overview of what a handle is. There are many types in Warcraft 3: integers, reals, strings, units, players, items, special effects, etc. A handle is any type that is not either an integer, real, boolean, or string. An example of some commonly used handles are: units, destructables, items, players, special effects, floating text, and locations (referred to as "points" in GUI). There are many, many more handles than the list I gave, but these are just some of the more commonly used ones.

Handle IDs and why they're useful

This, although you might not realize it now, is incredibly useful when combined with Hashtables. With the new patch, you are now able to get the Handle ID (it's an integer) of things such as units. As far as you should be concerned, these IDs are unique to each handle. Now, as previously mentioned, hashtables require two integers when storing data inside of it. One of these integers could easily be the Handle ID of a unit that's part of a MUI spell.

Since you might still not understand its usefulness, here's an example of a spell that heals 10 life per second for 10 seconds:

While I'm at it, please use "Unit - A unit Starts the effect of an ability" instead of "Unit - A unit Begins casting an ability".
  • Start
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Heal
    • Actions
      • -------- Store the duration in the Hashtable under the unit's Handle ID --------
      • Hashtable - Save 10.00 as 0 of (Key (Target unit of ability being cast)) in hashTable
      • -------- Add the unit to the group of units that are being healed over time --------
      • Unit Group - Add (Target unit of ability being cast) to HealOverTimeUnits
Every second, this trigger goes through all the units being healed and heals them.
  • Healing
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in HealOverTimeUnits and do (Actions)
        • Loop - Actions
          • -------- Get the remaining time in seconds --------
          • Set RemainingTime = (Load 0 of (Key (Picked unit)) from hashTable)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • RemainingTime Greater than 0.00
            • Then - Actions
              • -------- Heal the unit and update the time remaining --------
              • Unit - Set life of (Picked unit) to ((Life of (Picked unit)) + 10.00)
              • Hashtable - Save (RemainingTime - 1.00) as 0 of (Key (Picked unit)) in hashTable
            • Else - Actions
              • -------- The healing is done, clean up --------
              • Unit Group - Remove (Picked unit) from HealOverTimeUnits
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in hashTable
I hope you're able to understand all of this. The only thing you might be wondering about is the "Hashtable - Clear all child hashtables of child (Key (Picked unit)) in hashTable". When you're done using the hashTable for a specific unit, you need to clean up just like you would clean up memory leaks. This function cleans up all data stored with the Handle ID of the unit that was being healed.

This might be more work than using a bunch of waits inside a single trigger, but this way of doing things is completely MUI.

Now, another popular example. We're going to make a single target knockback spell that uses Hashtables. It will work just like the HealOverTime trigger because it will also use a group to hold all the units being knocked back. The difference is that more values will be stored in the hashTable and of course the unit will be knocked back instead of healed.

Here's another thing to keep in mind. Unless you handle your values carefully, spells that use the same hashtable can easily overwrite values in other spells. So, I recommend creating a hashtable global for each spell that needs to use it unless you know what you are doing and can prevent conflicts. Anyways, here are the knockback functions:

  • KnockbackTarget
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Knockback
    • Actions
      • -------- Just for configuration/readability --------
      • Set KnockbackAngle = (Angle from (Position of (Triggering unit)) to (Position of (Target unit of ability being cast)))
      • -------- Distance unit is knocked back per second --------
      • -------- .04 is how often the units are moved, so we multiply the distance by .04 --------
      • Set KnockbackDistance = (400.00 x 0.04)
      • -------- How long the unit is knocked back for --------
      • Set RemainingTime = 2.00
      • -------- Store the values --------
      • Hashtable - Save KnockbackDistance as 0 of (Key (Target unit of ability being cast)) in knockbackTable
      • Hashtable - Save KnockbackAngle as 1 of (Key (Target unit of ability being cast)) in knockbackTable
      • Hashtable - Save RemainingTime as 2 of (Key (Target unit of ability being cast)) in knockbackTable
      • Unit Group - Add (Target unit of ability being cast) to KnockbackUnits
This trigger moves all the units currently being knocked back.
  • KnockbackUnits
    • Events
      • Time - Every 0.04 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in KnockbackUnits and do (Actions)
        • Loop - Actions
          • -------- Load all necessary variables stored in the Hashtable --------
          • Set KnockbackDistance = (Load 0 of (Key (Picked unit)) from knockbackTable)
          • Set KnockbackAngle = (Load 1 of (Key (Picked unit)) from knockbackTable)
          • Set RemainingTime = (Load 2 of (Key (Picked unit)) from knockbackTable)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • RemainingTime Greater than 0.00
            • Then - Actions
              • -------- Move the unit and update time remaining --------
              • Unit - Move (Picked unit) instantly to ((Position of (Picked unit)) offset by KnockbackDistance towards KnockbackAngle degrees)
              • Hashtable - Save (RemainingTime - 0.04) as 2 of (Key (Picked unit)) in knockbackTable
            • Else - Actions
              • -------- Knockback is done, clean up --------
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in knockbackTable
              • Unit Group - Remove (Picked unit) from KnockbackUnits
As you noticed, the usage of a unit group is very similar to the Heal example. What you should also notice is that a different key is used for each value be stored (0 for distance, 1 for angle, 2 for time remaining). It's important that each of these keys are different because otherwise the data would be overwritten.

While remembering each key in an example like this isn't tricky, sometimes with larger systems they can become mixed up. There's a handy function that will get a String ID of a string just like you can get the Handle ID of a unit. So instead of using 0 for distance, we could have gotten the String ID of "distance". Here's what the first trigger might look like if we used String IDs instead:

  • KnockbackTarget
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Knockback
    • Actions
      • -------- Just for configuration/readability --------
      • Set KnockbackAngle = (Angle from (Position of (Triggering unit)) to (Position of (Target unit of ability being cast)))
      • -------- Distance unit is knocked back per second --------
      • -------- .04 is how often the units are moved, so we multiply the distance by .04 --------
      • Set KnockbackDistance = (400.00 x 0.04)
      • -------- How long the unit is knocked back for --------
      • Set RemainingTime = 2.00
      • -------- Store the values --------
      • Hashtable - Save KnockbackDistance as (Key distance) of (Key (Target unit of ability being cast)) in knockbackTable
      • Hashtable - Save KnockbackAngle as (Key angle) of (Key (Target unit of ability being cast)) in knockbackTable
      • Hashtable - Save RemainingTime as (Key time) of (Key (Target unit of ability being cast)) in knockbackTable
      • Unit Group - Add (Target unit of ability being cast) to KnockbackUnits
The KnockbackUnits trigger would need to be updated to use the String IDs as well, but that should be straightforward enough that I shouldn't have to show it here. The example in the demo map uses String IDs, so you can look there for more information.

As a final note, these triggers are merely examples. As you may or may have not noticed, the knockback trigger would require memory leak fixing since it leaks 25 locations each second for every unit that is being knocked backward. I intentionally left the triggers how they are so they would be easier to understand. Those who wish to use these kinds of triggers in their maps should make sure to fix any memory leaks that were not cleaned up in the examples.
 

Attachments

  • Hashtable Tutorial.w3x
    27.3 KB · Views: 2,913
Last edited:
Level 1
Joined
Jun 30, 2009
Messages
1
While I would have preferred more information on how hash tables work (which would probably ruin the tutorial for everyone else), I still think this is a excellent tutorial.
 
Level 6
Joined
Jul 8, 2008
Messages
150
I think it's a great tutorial but lacking a bit in information...

For example, if you save a value of 1, then save another with the same value, the previous will be overwritten.

Pretend a Paladin with "Heal Over Time" is fighting a Death Knight with "Damage Over Time" (the exact opposite of your spell). Now, the Paladin will cast "Heal Over Time" on himself and the duration will be saved of 0. Now, the Death Knight casts "Damage over Time" on the Paladin and the duration will be saved of "0". Won't the Paladin's spell stop healing him?

Also, do points and other things that leak need to be assigned to a variable and then destroyed, or is clearing the child hashtable enough?
 
I think it's a great tutorial but lacking a bit in information...

For example, if you save a value of 1, then save another with the same value, the previous will be overwritten.

Pretend a Paladin with "Heal Over Time" is fighting a Death Knight with "Damage Over Time" (the exact opposite of your spell). Now, the Paladin will cast "Heal Over Time" on himself and the duration will be saved of 0. Now, the Death Knight casts "Damage over Time" on the Paladin and the duration will be saved of "0". Won't the Paladin's spell stop healing him?

Also, do points and other things that leak need to be assigned to a variable and then destroyed, or is clearing the child hashtable enough?
If you use the same hashtable for both spells then yes, it will bug, but if, as you should, you use different global hashtables for each spell, then there will be no problem.
 
Level 11
Joined
Oct 13, 2005
Messages
233
If you use the same hashtable for both spells then yes, it will bug, but if, as you should, you use different global hashtables for each spell, then there will be no problem.

I will make sure this point is emphasized in the tutorial, thanks for pointing it out.

Also, do points and other things that leak need to be assigned to a variable and then destroyed, or is clearing the child hashtable enough?

I would still assign the values to variables and destroy them manually just to be sure.
 
Level 6
Joined
Jul 8, 2008
Messages
150
And then what after I've created a global hashtables? If I use a spell that has a periodic event, how will I find that hashtable again, since there is no "Save hashtable" function? Do I have to create a hashtable array variable, and at the beginning of my action have an integer variable and add + 1 to it, and set it as the last created hashttable?

Example:

Create a Hashtable
If
MUICount Less than 1000
Then
Set MUICount = MUICount + 1
Else
Set MUICount = 0
Set HashT[MUICount] = Last Created Hashtable

Is that what I should do?
 
Level 11
Joined
Oct 13, 2005
Messages
233
I'm extremely confused as to what you're trying to do. You should only need one hashtable per spell. The primary use of a hashtable is to "attach" data to handles. In my examples, I attached the information to units to keep track of how much more healing they needed and knockback information.

When using hashtables, you should not need to use global array variables as well, only the hashtable. If you can explain what you're trying to accomplish, I can try to point you in the right direction.
 
Level 19
Joined
Sep 4, 2007
Messages
2,826
I'm extremely confused as to what you're trying to do. You should only need one hashtable per spell. The primary use of a hashtable is to "attach" data to handles. In my examples, I attached the information to units to keep track of how much more healing they needed and knockback information.

When using hashtables, you should not need to use global array variables as well, only the hashtable. If you can explain what you're trying to accomplish, I can try to point you in the right direction.

Are hashtables also useful for storing multiple data for individual units in a group? By that I mean can they also contain references for those units?
 
Level 16
Joined
Oct 12, 2008
Messages
1,570
The tutorial is very nice. Not extremely usefull to me because i already kinda figured it out, but very usefull to new people and people who are just new to hashtables.

This way gives a much easier MUI way for GUI, and not to forget, spells stack much easier this way! You can just set the duration back to 10, or add 10 to the duration, whatever is your preference.

Good job!

BTW: When will THW support GUI hashtable actions? (Now it gives a question mark, as you can all see)
 
Level 6
Joined
Jul 8, 2008
Messages
150
I'm extremely confused as to what you're trying to do. You should only need one hashtable per spell. The primary use of a hashtable is to "attach" data to handles. In my examples, I attached the information to units to keep track of how much more healing they needed and knockback information.

When using hashtables, you should not need to use global array variables as well, only the hashtable. If you can explain what you're trying to accomplish, I can try to point you in the right direction.

Well I was trying to create a hashtable per unit every time it casted the spell so a spell is MUI and SUI (Same Unit Instanceable).

How would you create only 1 hashtable per spell??? Explain everything! :grin:
 
Level 11
Joined
Oct 13, 2005
Messages
233
Well I was trying to create a hashtable per unit every time it casted the spell so a spell is MUI and SUI (Same Unit Instanceable).

How would you create only 1 hashtable per spell??? Explain everything! :grin:

This would be a rather simple problem in JASS since timers can be created dynamically while timers in GUI need to be created at map init. In GUI, I would probably prefer to use an approach with arrays that used some careful indexing. I recommend asking for help in the Triggers and Scripts forum since it isn't entirely about hashtables.
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Ok since this is gonna be dealing hashtables (and I must say very easy-to-understand tutorial, GOOD JOB!) there's one thing on efficiency I think should have been mentioned since all users that want to make MUI spells using hashtables are gonna be reading this tutorial from now on.

You know how using indexing GUI'ers turn the periodic event on and off, yupp something like that.

Here's a very fast draft I did (it's just a draft so don't tell me there's something wrong :p):
  • Main
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Animate Dead
    • Actions
      • -------- bla bla bla --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Number of units in DT) Equal to 0
        • Then - Actions
          • Trigger - Turn on Loop <gen>
        • Else - Actions
      • Unit Group - Add (Triggering unit) to DT
  • Loop
    • Events
      • Time - Every 2.00 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in DT and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • -------- bla bla bla --------
            • Then - Actions
              • -------- bla bla bla --------
            • Else - Actions
              • -------- Clearing hashtables and all --------
              • Unit Group - Remove (Triggering unit) from DT
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Number of units in DT) Equal to 0
                • Then - Actions
                  • Trigger - Turn off (This trigger)
                • Else - Actions
Makes sense eh?

Umm anyways, about hashTables, gotta make things a piece of cake, specially since now GUI users can basically use their arrays to loop through variables instead of using them to index.

I guess that could be useful for those Jassers who don't use vJass, although I doubt anyone would prefer hashtables over structs, 'cause that's just absurd...

Good job on this tutorial :)
 
Well I was trying to create a hashtable per unit every time it casted the spell so a spell is MUI and SUI (Same Unit Instanceable).

How would you create only 1 hashtable per spell??? Explain everything! :grin:

Huh? You can increase the index by X and save it in the SpellHashtable, and after the instance shut down, you decrease it? Whats so hard about it?

Anyway, I would prefer structs, as already told.
 

Rmx

Rmx

Level 20
Joined
Aug 27, 2007
Messages
1,164
When you do indexing system in GUI ... the spell will be MUI SUI ext..

But hashtables if you want it SUI ( Same Unit Insta... ) you have to index that unit bla bla .

Anyway Hashtable is easy to use but still same as indexing if Indexing is recycled right ^^
 
Level 6
Joined
Jul 8, 2008
Messages
150
Yeah what I said was pretty dumb lol. Must have been tired when I wrote it.

Anyway, what I meant to say was this: how could you make hashtables SUI? Seeing as how when you cast the spell again, the previous values for that unit will be overwritten.

It seems everyone has suggested indexing system. How would that work? Could it be done in GUI?
 

Rmx

Rmx

Level 20
Joined
Aug 27, 2007
Messages
1,164
Paladon's "indexing system" is rather dangerous at best--indexes are never recycled if even one is somehow lost.

What do you mean is rather Dangerous :S, it will 100% recycle everything when all spells are finished :O, and how could it get LOST !, You will check by a game message .. Debug message if the trigger turn off and make everything until you are 100% sure, it is a perfect system, also if recycled correctly it will be better, but Paladon until everything finishes it will recycle :)
 
Level 8
Joined
Nov 25, 2008
Messages
194
The problem about paladons would just be that it only resets if there is NO active instance running, so if you have an often used spell using this and it lasts quite long, and the counter never drops to 0 you might reach 8190 (or what it was), so I prefer a simple recycling, by setting all variables of the highest index into the expired index and decrease the max index. Well i didn't look at PP's yet but I'm sure it does something similar
 
Level 17
Joined
Mar 17, 2009
Messages
1,349
Ok, supposing each second one unit triggers the ability, knowing that each hour is 3600 seconds, you need over two hours before you fill the array. And since this is usually an impossible situation, well, no worries on using Paladon's system.
Yet, I prefer PP's, but I just happen to use Paladon's since it's no big deal and since it's neater (imo).
 
Top