• 🏆 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!

Hashtable key = variable (possible?)

Status
Not open for further replies.
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I searched for it in the editor, but couldn't find it.
Is it possible to use a 'unit' variable as a key for a hashtable?
I will give as example what I am trying to do:
GDD_Event becomes equal to 0.00
Conditions: -----
Actions: Load (Key(Damage)) of (Key(GDD_DamageSource)) from Table
----------

I know I can do it if I set the custom value of the damaging unit to something unique, but I want to do it with a hashtable to avoid changing the unit's value. In a matter of fact - I don't need hastable for it when I'm using the custom value of the unit.

Hmm, fix me if I'm wrong, but "Triggering unit" in GDD is equal to GDD_DamagedUnit, right? If so I guess I could save the "Damage" into the DamagedUnit's cell and load it instead, but then it wouldn't be MUI. If the "writing" event runs again before the unit is damaged - the value will be changed.

Anyways.. I have another question regarding hashtables:
What does "Convert Unit-Type to Order" do? When is it used?
And what is "Units of Type" doing?
(Both of the above are Handles)

If I do: Save Temp_Real as (Key (Range)) of (Key (Units of type Archer)) in Table
Then I have an event, where an Archer is the triggering unit - if I do Set Temp_Real = Load (Key(Range)) of (Key(Units of type (Unit-type of (Triggering unit)))) from Table
Will it load the value I've initially entered?
 
Level 12
Joined
Jan 2, 2016
Messages
973
GREAT! Thanks!! I know how to use custom scripts, so I can do that =)
You just saved me A LOT of time and work. I had started doing something like:
save a counter into the attacked unit's table (how many shots have been launched at it)
save the damage into a cell, which is determined by the counter
save a time variable = the distance between the units / the speed of the missile + the time elapsed in the game = the time when the unit will take that damage
run a loop from "how many times has the unit been hit already + 1" to "how many times it has yet to be hit"
checking if the current shot will arrive before any of the others and if so - moving their values further down (was gonna do this with a loop inside the loop), and putting the values from this attack on their place.
And then the other trigger was going to increase the hits count (and save it into the unit's table).... stuff like that.
Now I can just save the damage into the attacking unit's table (and 2 counters to check how many units it needs to hit and how many units it has hit, so I can clear the leaks from the hashtable once all the units take their damage) :)

Just one more question: How do you clear child hashtables using jass?

EDIT: You have written how to save a value, not how to load xP
I tried to do it inversed "call LoadReal(udg_Table, GetHandleId(udg_GDD_DamageSource), GetStringId(Damage), udg_TempReal)" but didn't quite work like that...
While we are at it, can you tell me if this "GetStringId" correct?

EDIT 2: Nevermind, I found out how to do it: "set udg_TempReal = LoadReal(udg_Table, GetHandleId(udg_GDD_DamageSource), 0)", but I'd still like to know how to load a Key String :p
 
Last edited:
Level 12
Joined
Jan 2, 2016
Messages
973
^ TL DR, Questions remaining:
1) How do I refer to a Key String in Jass
2) How do I clear parent/child hashtables in Jass
3) What does "Convert Unit-Type to Order" do? When is it used?
4) If I do: Save Temp_Real as (Key (Range)) of (Key (Units of type Archer)) in Table
Then I have an event, where an Archer is the triggering unit - if I do Set Temp_Real = Load (Key(Range)) of (Key(Units of type (Unit-type of (Triggering unit)))) from Table
Will it load the value I've initially entered?

EDIT: Nevermind (1) and (2) - I converted 1 of my triggers to custom text and I took what I needed (had forgotten I could do that).
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
the problem with "Key" is that if you are using JNGP, the function will not appear in GUI for you, since the editor is based on 1.24. Even on the vanila editor, if you try clicking on some of the types(ability et. al.) it will crash the editor :D

1.) You cant really get the "key" of a string, since it has no "key" in sense that handles do, its not handleId. Strings work a bit differently, but you can compute a value for string using native StringHash, which returns integer.

2.) http://www.hiveworkshop.com/forums/...ble-key-variable-possible-274609/#post2776641

3.) dont think that is remotly useful, you could try converting it to custom text in some dummy trigger and posting it here?(Im too lazy to search for such things in GUI :D)

4.) you have the code there, try it! :D I dont know to be honest.
 
Level 12
Joined
May 22, 2015
Messages
1,051
GetHandleId() will return an integer. You can use an integer variable to store it and use that, maybe. I can't remember if the GUI hashtable functions let you just put an integer variable in for the keys.

JASS hashtables are so much easier to use lol. It makes sense, easy to remember, and you don't have to click through menus like 10 times to make one save or one load.
JASS:
set id = GetHandleId(GetTriggeringUnit())
call SaveInteger(table, id, 0, 100)
...
set damage =  LoadInteger(table, id, 0)

EDIT:
Regarding 4) in your other post:
Yes. It doesn't save the variable memory location or anything, it just saves the data there. Saving a variable will save the value stored in the variable at that time, not what the variable is whenever you load it.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Thanks for the information, all :)
I guess I will test (4) myself and post the result here.
And yeah, the StringHash is the key - just need to write f. ex: "set udg_TempReal = LoadReal(udg_Table, GetHandleId(udg_GDD_DamageSource), StringHash("Damage"))" :)
 
Level 12
Joined
May 22, 2015
Messages
1,051
Don't use StringHash. It's slow and has nasty bugs with collision.

You don't even need it here. Just count your child keys up one by one:
key 0 is your first variable
key 1 is your second, etc.

This is important.

One thing you can do if you still want your code to be very readable is even just use a global constant.

udg_damageKey = 1337
JASS:
set id = GetHandleId(GetTriggeringUnit())
// Set damage to 100
call SaveReal(udg_damageTable, id, udg_damageKey, 100)
...
set damage = LoadReal(udg_damageTable, id, udg_damageKey)

You can also use this method with StringHash to avoid doing the StringHash function call every time you want to use the damage key. However, you should still not use it for the reasons stated by Zwiebelchen.
 
Level 12
Joined
May 22, 2015
Messages
1,051
Yeah, I've considered that there may be a collision, but I was thinking to set all my keys to strings, thus not allowing collisions to happen. But hmm, if it is slower... perhaps I'll just use numbers instead...

When you are more comfortable with JASS, you can also set up multiple triggers in the same "trigger". Each trigger (that appears in the left side when viewing your triggers) is just a block of text that gets added to the main script that is used when playing the game. You're allowed to write as many triggers as you want in a single one of these blocks of text.

What this allows for is the ability to have these hardcoded numbers all contained within one file. The desire to use StringHash is so that you can keep track of what's happening between triggers more easily, but having those triggers that "talk" to each other in the same block of text makes this a lot easier to keep track of while just using 0, 1, 2, etc.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Hmm, I tried to do this:
Hashtable - Save MultishotRange as (Key Range) of (Key (Units of type Death Archer)) in Table
--------
Set MultishotRange = (Load (Key Range) of (Key (Units of type (Unit-type of GDD_DamageSource))) from Table)
Game - Display to (All players) the text: (String(MultishotRange))
And it shows 0, so I guess that's not the way to do it.

Anyone has any idea how to make this work?
 
Level 12
Joined
May 22, 2015
Messages
1,051
Hmm, I tried to do this:
Hashtable - Save MultishotRange as (Key Range) of (Key (Units of type Death Archer)) in Table
--------
Set MultishotRange = (Load (Key Range) of (Key (Units of type (Unit-type of GDD_DamageSource))) from Table)
Game - Display to (All players) the text: (String(MultishotRange))
And it shows 0, so I guess that's not the way to do it.

Anyone has any idea how to make this work?

Units of type Death Archer
This creates a group (which you leak).

The load part creates a NEW group (which you will also leak). This is not how you want to do it.

Key of (anything) is the same as GetHandleId(anything), and GetHandleId() always returns a specific, hidden ID. You cannot choose these IDs. I think they are like memory locations or maybe just stored integers so that each object has its own ID number. By making a new group, you are getting a new ID, so you will never be able to refer back to the original group that you used for the key.

If you use JASS, you can do this better. Each object you make in the object editor has an ID when you create it. If you press ctrl+d while in the object editor, it will display the "raw data" of everything. It will show this for units and abilities etc as well as some stuff in the object data. You will see beside the unit name it is like:
h001 - Some Unit

h001 is the unit type ID. This is what you want to use for this kind of check. I am pretty sure you can't do it in GUI. Anyway, it will look like this:
JASS:
call SaveReal(udg_Table, 'h001', StringHash("Range"), udg_MultishotRange)
...
call LoadReal(udg_Table, GetUnitTypeId(udg_GDD_DamageSource), StringHash("Range"))

Replace h001 with the ID you see in your object editor for your Death Archer. Despite having a letter (possibly more than one) and being in single quotes, it is processed as an integer, so don't worry about that.

GetUnitTypeId(someUnit) gives you this ID. This is what you should use to compare the numbers.

I assume (Key Range) converts to StringHash("Range") because I don't actually know what that is lol. Sorry if that is incorrect. If it is an object (unit, unit group, player, etc.), then just change it to GetHandleId(udg_Range).
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
Don't use StringHash. It's slow and has nasty bugs with collision.
It may be slow, but it does not have problems with collisions, or at least the probability for such is insignificant given its use. Also you can cache it into a constant variable.

It is useful to generate unique key values when you do not know what other key values exist so want a low collision chance.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I have one last question (I hope).
Perhaps the answer is obvious, but I need to make sure, cuz if it's not how I think it is - it will be pretty simple to achieve what I need:

If I start a 3 seconds timer1 and save it into a hashtable (Key Timer) of (Key Triggering Unit)
then after 1 second I set the timer1 to 3 seconds again and save it as (Key Timer) of (Key ~another unit~). Will the timers saved in these 2 units be different, or will they be the same?
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
It may be slow, but it does not have problems with collisions, or at least the probability for such is insignificant given its use. Also you can cache it into a constant variable.

It is useful to generate unique key values when you do not know what other key values exist so want a low collision chance.
Depends on what strings you use, obviously.
Yes, StringHash has a pretty low probability of collision. But the problem is: with a large enough sample size, you will eventually have it happen.

If you use just one table for everything (which is not recommended anyway, as hashtables get slower the more data you put in), sooner or later you might have a collision case.
And the problem is: unless you manually check every string, this creates bugs that are SUPER HARD to find, as not only will it only appear in some rare cases (if spell A and spell B are used simultanously), but it will also have you hunt for that one string that has the same StringHash, which is the proverbial needle in the haystack.

I just wouldn't do it. It's not worth the trouble.


And I want to be honest here:
In my last 7 years of coding spells, I have literally never encountered a case in which I had to attach data to something else but timers. And as I always create/destroy timers when I need them, the parentkey as GetHandleId(timer) will always be unique, so there is no need to account for collision.
This only goes for spells, of course.
There are plenty of cases in which you want to attach data to units/rawcodes, etc., but these cases are almost always exclusive to systems. And these should have their own hashtables for speed reasons.


Random sidenote:
Why exactly do you need a hashtable for storing stuff like range anyway? This is a typical use-case for a global constant.


If I start a 3 seconds timer1 and save it into a hashtable (Key Timer) of (Key Triggering Unit)
then after 1 second I set the timer1 to 3 seconds again and save it as (Key Timer) of (Key ~another unit~). Will the timers saved in these 2 units be different, or will they be the same?
I think I understand the fundamental problem now...

First of, let me tell you that they will be the same, so just to answer your question.
However, your question reveals to me that you haven't quite grasped the principle of https://en.wikipedia.org/wiki/Object-oriented_programming ... which is kind of a necessity if you want to use hashtables.

You see, a timer is an https://en.wikipedia.org/wiki/Object_(computer_science).
If you store a timer in a variable, you don't actually put that timer in this memory slot, but a reference to that timer.
Think of it like a link in Windows. What happens if you create a link, then delete the original file? Right, the link will stop working.
Timers are the same: creating a new timer is like creating a copy of the original file. Setting a variable to a timer is like creating a link to the original file. You can delete the linked file by nulling/overwriting the variable. You can delete the original file by destroying the timer. Once you destroyed the timer (= deleted the file), every link to it will stop working.

This is what happens here:
You create a timer in instance A and start it, then create a link to it and store it in a variable.
Now you take that link and create another link from it for instance B. Then you restart that timer.
What happens? Well, you are still using the same original timer (in the analogy the same file), which means that both spell instances will now have the same time remaining.
 
Level 12
Joined
Jan 2, 2016
Messages
973
First of, let me tell you that they will be the same, so just to answer your question.
However, your question reveals to me that you haven't quite grasped the principle of https://en.wikipedia.org/wiki/Object-oriented_programming ... which is kind of a necessity if you want to use hashtables.
Didn't you see the part where I wrote "Perhaps the answer is obvious, but I need to make sure, cuz if it's not how I think it is - it will be pretty simple to achieve what I need:"
I kind a knew that it wouldn't work, but was hoping it would, cuz now I'd need to make a timer array and index them and who knows what else to make it work... And again it wouldn't be fully MUI.
In my last 7 years of coding spells, I have literally never encountered a case in which I had to attach data to something else but timers. And as I always create/destroy timers when I need them, the parentkey as GetHandleId(timer) will always be unique, so there is no need to account for collision.
Hmm, perhaps this will be useful for what I want to do..
Can you post here how to do that? I need to create a timer in a trigger, store a real in a hashtable (with key - the timer I created), and in another trigger - have an event "the timer I created expires" and I need to extract the real from the hashtable. I can handle the rest myself.
Random sidenote:
Why exactly do you need a hashtable for storing stuff like range anyway? This is a typical use-case for a global constant.
Well, I don't exactly know how to call unit's range via triggers, so I manually save the range of a unit-type into a hashtable, linked to the unit type, and then when a unit triggers an event - I check its range by calling it from the hashtable.

This way I'm saving a lot of rows in my trigger, by replacing the "if unit-type is eqal to ..... set range to xxx, else if unit-type is equal to ...... set range to yyy, and so on..."
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Hmm, perhaps this will be useful for what I want to do..
Can you post here how to do that? I need to create a timer in a trigger, store a real in a hashtable (with key - the timer I created), and in another trigger - have an event "the timer I created expires" and I need to extract the real from the hashtable. I can handle the rest myself.
This is very simple:

- Create a new timer
- Store your real as "0" of "Key(timer)"

- In the event "timer has expired":
- Load "0" of "Key(expired timer)"
- do your stuff
- Flush the Child hashtable of "Key(expired timer)"
- Destroy the timer


This is how this looks in Jass:

JASS:
function OnExpire takes nothing returns nothing
     local timer t = GetExpiredTimer()
     local real MyReal = LoadReal(HASH, GetHandleId(t), 0)

     //do all the things

     call FlushChildHashtable(HASH, GetHandleId(t)
     call DestroyTimer(t)
     set t = null
endfunction

function Cast takes nothing returns nothing
     local timer t = CreateTimer()
     call SaveReal(HASH, GetHandleId(t), 0, MyReal)
     call TimerStart(t, TIMEOUT, false, function OnExpire)
     set t = null
endfunction

Well, I don't exactly know how to call unit's range via triggers, so I manually save the range of a unit-type into a hashtable, linked to the unit type, and then when a unit triggers an event - I check its range by calling it from the hashtable.
There is a way to get the attack range of units, though it's a bit hacky:
- root the unit via an immobilizing ability like entangle
- order the unit to attack the target unit with "smart" (not "attack"!)
- the "smart" order will now return true if the unit is in range and false if it is out of range
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
EDIT: Oh, wait.... Jass seems to be smarter than GUI. The Jass event is a general one, isn't it?
In Jass, you don't even need an event, as TimerStart allows to define a function to call when the timer expires.

This is why JASS spells require literally only half the code of GUI spells for timed abilities.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, I read a basic Jass guide.
The Script you gave me should be written in the Map's custom scripts, right?
and then via custom text "call OnExpire()" and "call Cast()" I can just call these functions.
However, let me see if I've gotten it right:
my trigger's actions go on, then I put custom text, where I call "Cast" and then another custom text to call "OnExpire", and then I continiue with my trigger?
Or do I need to add the "OnExpire" in some other trigger?
Or I don't need to call OnExpire at all, and it will be runing automatically every time a timer expires?

EDIT: Nevermind, didn't notice you are calling "OnExpire" inside "Cast". Thank you very much ^_^

EDIT 2: Okay, I am getting compile errors, can you point them out?
JASS:
function MS_Damage takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local real damage = LoadReal(udg_Table, GetHandleId(t), StringHash("Damage"))
    local unit target = LoadUnit(udg_Table, GetHandleId(t), StringHash("Target"))
    local unit dummy = LoadUnit(udg_Table, GetHandleId(t), StringHash("Dummy"))
    call UnitDamageTargetBJ( dummy, target, damage, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL )
    call FlushChildHashtable(udg_Table, GetHandleId(t))
    call DestroyTimer(t)
    set t = null
endfunction

function MS_Values takes udg_TempReal, udg_MultiTarget, udg_Temp_Unit, udg_Temp_Real returns nothing
    local timer t = CreateTimer()
    call SaveReal(udg_Table, GetHandleId(t), StringHash("Damage"), udg_Temp_Real)
    call SaveUnit(udg_Table, GetHandleId(t), StringHash("Target"), udg_MultiTarget)
    call SaveUnit(udg_Table, GetHandleId(t), StringHash("Dummy"), udg_Temp_Unit)
    call TimerStart(t, udg_TempReal, false, function MS_Damage)
    set t = null
endfunction
EDIT 3: Fixed it! Turned out there wasn't a function called "LoadUnit" or "SaveUnit" it was "Save/Load UnitHandle"
 
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
EDIT 2: Okay, I am getting compile errors, can you point them out?
It's not LoadUnit it's LoadUnitHandle.

Get Newgen. It allows to convert triggers to custom text on the fly and has a function browser and autocomplete options.

Also, you are leaking unit references for target and dummy in the callback.

--> put this at the end of MS_Damage:
set target = null
set dummy = null


Btw, you can also fire a GUI trigger inside the callback in case you feel more comfortable with that. Simply insert this:
call TriggerEvaluate(udg_MyTrigger)
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
If you use just one table for everything (which is not recommended anyway, as hashtables get slower the more data you put in), sooner or later you might have a collision case.
And the problem is: unless you manually check every string, this creates bugs that are SUPER HARD to find, as not only will it only appear in some rare cases (if spell A and spell B are used simultanously), but it will also have you hunt for that one string that has the same StringHash, which is the proverbial needle in the haystack.
You would need a lot of strings for that to even start being a problem.

Hashtable scaling is perfect for most purposes. It will work fine for hundreds of abilities to share as each will add data and then remove data once finished. It only is a problem when using them for persistent data storage, in which case you should not be using the "shared" hashtables anyway.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Okay, 1 more question aired about hashtables.
There is a variable for attack type, but I can't save it into a hashtable.
I tried to save it as an integer, but it says that there is a mismatch, so I guess the AttackType isn't an integer.
How do I save attack type into a hashtable?
(I tried to save it as a string too, but that didn't work either)
 
Level 12
Joined
May 22, 2015
Messages
1,051
I use this URL to figure things out like this:
http://wiki.thehelper.net/wc3/jass/common.j

Seems like it is a type of variable called: attacktype

There doesn't seem to be a function to save this type of variable in a hashtable, anyway.

However, there seems to be this:
JASS:
    constant attacktype     ATTACK_TYPE_NORMAL         = ConvertAttackType(0)
    constant attacktype     ATTACK_TYPE_MELEE          = ConvertAttackType(1)
    constant attacktype     ATTACK_TYPE_PIERCE         = ConvertAttackType(2)
    constant attacktype     ATTACK_TYPE_SIEGE          = ConvertAttackType(3)
    constant attacktype     ATTACK_TYPE_MAGIC          = ConvertAttackType(4)
    constant attacktype     ATTACK_TYPE_CHAOS          = ConvertAttackType(5)
    constant attacktype     ATTACK_TYPE_HERO           = ConvertAttackType(6)

Each one is just a constant using this ConvertAttackType(integer) function. You could store the integers (that match the numbers here) and use the ConvertAttackType yourself. I've never done that before and there could be issues I don't know about, but that's what I found.

EDIT:
To keep yourself from messing up with the numbers, you could make your own constants:
JASS:
    set udg_ATK_INT_NORMAL         = 0
    set udg_ATK_INT_MELEE          = 1
    set udg_ATK_INT_PIERCE         = 2
    set udg_ATK_INT_SIEGE          = 3
    set udg_ATK_INT_MAGIC          = 4
    set udg_ATK_INT_CHAOS          = 5
    set udg_ATK_INT_HERO           = 6

and use them like:

JASS:
call SaveInteger(udg_Table, key1, 0, udg_ATK_INT_PIERCE)
...
set attackInteger = LoadInteger(udg_Table, key1, 0)
call UnitDamageTarget(u1, u2, 100, true, false, ConvertAttackType(attackInteger), ...)
 
Level 12
Joined
May 22, 2015
Messages
1,051
You would need a lot of strings for that to even start being a problem.

Hashtable scaling is perfect for most purposes. It will work fine for hundreds of abilities to share as each will add data and then remove data once finished. It only is a problem when using them for persistent data storage, in which case you should not be using the "shared" hashtables anyway.

How much persistent data in a hashtable is too much? A rough estimate / order of magnitude would be good enough.

I have a table storing data for all my items (what items they build from, what items they build into, and their cost if they are a base item). The table has something like 200 entries (I don't know exact number but that's roughly where it is). I use it for item combining stuff.

I also have another similar table which will have probably less in it, but it will be used a lot more often (not just on item pickup). It stores data for items for their effects. The keys are the item IDs. There's probably only around 30 or 40 items using that table, but there are often unit groups being stored in there (usually which units have that type of item). This number will increase as I move more item triggers to JASS and / or add more items in general, Should I stop doing this?

Also is this a problem if you have say, 2 parent keys, but each parent key has a ton of child keys? If I add a 3rd parent key, do the child keys of the other two parent keys cause the whole table to be slower?
 
Level 12
Joined
Jan 2, 2016
Messages
973
Gosh, it started giving me errors again.... can you tell me what's wrong? x_x
JASS:
function MS_Damage takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local real damage = LoadReal(udg_Table, GetHandleId(t), StringHash("Damage"))
    local unit target = LoadUnitHandle(udg_Table, GetHandleId(t), StringHash("Target"))
    local unit dummy = LoadUnitHandle(udg_Table, GetHandleId(t), StringHash("Dummy"))
    local integer int = LoadInteger(udg_Table, GetHandleId(t), StringHash("AttackType"))
    call UnitDamageTargetBJ( dummy, target, damage, ConvertAttackType(int), DAMAGE_TYPE_NORMAL )
    call FlushChildHashtable(udg_Table, GetHandleId(t))
    call DestroyTimer(t)
    set t = null
    set target = null
    set dummy = null
endfunction

function MS_Values takes nothing returns nothing
    local timer t = CreateTimer()
    call SaveReal(udg_Table, GetHandleId(t), StringHash("Damage"), udg_Temp_Real)
    call SaveUnitHandle(udg_Table, GetHandleId(t), StringHash("Target"), udg_MultiUnit)
    call SaveUnitHandle(udg_Table, GetHandleId(t), StringHash("Dummy"), udg_Temp_Unit)
    call SaveInteger(udg_Temp_Integer, GetHandleId(t), StringHash("AttackType"), udg_Temp_Integer)
    call TimerStart(t, udg_TempReal, false, function MS_Damage)
    set t = null
endfunction

Says "Invalid argument type (Integer)" and points towards the call TimerStart row.
EDIT: Oh, found my mistake...
 
Level 12
Joined
May 22, 2015
Messages
1,051
Gosh, it started giving me errors again.... can you tell me what's wrong? x_x
JASS:
function MS_Damage takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local real damage = LoadReal(udg_Table, GetHandleId(t), StringHash("Damage"))
    local unit target = LoadUnitHandle(udg_Table, GetHandleId(t), StringHash("Target"))
    local unit dummy = LoadUnitHandle(udg_Table, GetHandleId(t), StringHash("Dummy"))
    local integer int = LoadInteger(udg_Table, GetHandleId(t), StringHash("AttackType"))
    call UnitDamageTargetBJ( dummy, target, damage, ConvertAttackType(int), DAMAGE_TYPE_NORMAL )
    call FlushChildHashtable(udg_Table, GetHandleId(t))
    call DestroyTimer(t)
    set t = null
    set target = null
    set dummy = null
endfunction

function MS_Values takes nothing returns nothing
    local timer t = CreateTimer()
    call SaveReal(udg_Table, GetHandleId(t), StringHash("Damage"), udg_Temp_Real)
    call SaveUnitHandle(udg_Table, GetHandleId(t), StringHash("Target"), udg_MultiUnit)
    call SaveUnitHandle(udg_Table, GetHandleId(t), StringHash("Dummy"), udg_Temp_Unit)
///////////////////////////////////////////////////////////////////////////
    call SaveInteger(udg_Temp_Integer, GetHandleId(t), StringHash("AttackType"), udg_Temp_Integer)
///////////////////////////////////////////////////////////////////////////
    call TimerStart(t, udg_TempReal, false, function MS_Damage)
    set t = null
endfunction

Says "Invalid argument type (Integer)" and points towards the call TimerStart row.

I highlighted the problem in the quote. You are trying to save an integer into an integer lol :p

The WC3 error messages are REALLLY stupid sometimes.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Hmm, actually would it be better if I make "function MS_Damage takes unit, unit, real, integer returns nothing" and set some local variables to the values of the global ones in MS_Values, instead of storing them in a hashtable?
JASS:
call TimerStart(t, udg_TempReal , false, function MS_Damage(dummy, target, damage, int))
or it wouldn't work like that?
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
How much persistent data in a hashtable is too much? A rough estimate / order of magnitude would be good enough.
Multiple thousand, if not 10s of thousands of elements.

I have a table storing data for all my items (what items they build from, what items they build into, and their cost if they are a base item). The table has something like 200 entries (I don't know exact number but that's roughly where it is). I use it for item combining stuff.
This is still reasonable so should be fine. The performance degradation is only really noted as an issue by people using it as a bulk data storage for automatically computed data. For example I recall Nes running into the problem when he was trying to store 200,000+ mappings in one for some area based complex data structure.

I also have another similar table which will have probably less in it, but it will be used a lot more often (not just on item pickup). It stores data for items for their effects. The keys are the item IDs. There's probably only around 30 or 40 items using that table, but there are often unit groups being stored in there (usually which units have that type of item). This number will increase as I move more item triggers to JASS and / or add more items in general, Should I stop doing this?
You should never mix keysources as you are just asking for collisions. If one parent key source is from object type ID and another parent key source is from handle id of groups then you should give them separate hashtables.

or it wouldn't work like that?
Function references (code) do not carry arguments. That call will throw a syntax error.
 
Level 12
Joined
Jan 2, 2016
Messages
973
Hmm, and is it wise to save unit types, units, and timers, as parents in the same hashtable?

And... When I fix the syntax errors - is it better to use a hashtable, like I am doing now, or would it be better to create local variables with my 1-st function, and take them with my second one?

And If I do change my script to work with local variables (not from a hashtable), do I need to set them to null in the 1-st trigger or in the 2-nd trigger?
Example:
call MS_Damage
set target = null
set dummy = null

OR

function MS_Damage takes unit dummy, unit target, real damage, integer int returns nothing
.....
set target = null
set dummy = null
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
Hmm, and is it wise to save unit types, units, and timers, as parents in the same hashtable?
You can only save integers. Units and timers are handles.

If you are referring to the GetHandleId value of units and timers then I will refer you back to what I said about different key sources. GetHandleId acts as a key source allowing any handle to be used as unique parent or child indices without collisions. However for this to hold true it must be the only key source used for parents or used for a parent's children. Types are a completely different key source and possibly each kind of type (eg units, upgrades, abilities, buffs etc) is a different key source.

Since you cannot mix key sources without risking collisions you will need separate hashtables. One with GetHandleId values as parents and some other key source (string, constant etc) as children. The other with unit types as parents and some other key source as children.

And If I do change my script to work with local variables (not from a hashtable), do I need to set them to null in the 1-st trigger or in the 2-nd trigger?
The requirement to null local variables is a bug (local declared local handle reference counter leak). It only applies to local declared local variables (not parameter declared local variables). If you do not null local declared local handle variables after they are assigned a handle then they will leak a reference count to that handle upon function return and prevent the handle id from ever being recycled. If you do not null a parameter declared local handle variable then nothing bad happens at all since the reference counter is correctly decremented.upon function return.

Local declared local handle variable
JASS:
function ldlhv takes nothing returns nothing
    local unit u
endfunction

Parameter declared local handle variable
JASS:
function pdlhv takes unit u returns nothing
endfunction
 
Level 12
Joined
May 22, 2015
Messages
1,051
Is there a way to save Weather effects in a hashtable? xP

I don't think so. You can, however, use some kind of indexing system for them and just store the index (integer) of the weather effect and then use that to pull it out of an array. I doubt you'd have more than 8192 weather effects, so the system could even be extremely naive.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Btw you can remove
JASS:
StringHash("Damage")
StringHash("Target")
StringHash("Dummy")
from your script.

It's not needed, as you create and destroy a timer dynamically (as local) in this script; this means that your parent key will already be unique and there is no way of collision unless you just randomly entered a huge integer as parent key in another system/spell into the table and it happens to have the same value as your timer's handle id.

You can just enter 0, 1, 2 instead of the string hash values.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
You can just enter 0, 1, 2 instead of the string hash values.
They should still be assigned to variables though for readability and maintainability.
Or use global integers like:
Exactly like that.

That way you know what each key value is for (readable name) and can change the key values in 1 place (the globals decleration).
 
Level 12
Joined
May 22, 2015
Messages
1,051
You should never mix keysources as you are just asking for collisions. If one parent key source is from object type ID and another parent key source is from handle id of groups then you should give them separate hashtables.

The groups are the stored data. Sometimes I have different child keys (maybe a group handle - might not have any yet, but they could be - I use unit handles for some for sure).

The parent keys are always item type IDs like: 'I001'

Is it safe to use other IDs in there as parent keys as well? I realised I am doing that as well.
EX:
JASS:
call SaveInteger(udg_table, 'I001', 0, 1337)
call SaveInteger(udg_table, 'A001', 0, 9001)
call SaveInteger(udg_table, 'h001', 0, 3)

Can those types of IDs ever collide? I figure if there is a collision, I can deal with that single instance since the parent keys are constant in all these cases.
 

Dr Super Good

Spell Reviewer
Level 63
Joined
Jan 18, 2005
Messages
27,191
Can those types of IDs ever collide? I figure if there is a collision, I can deal with that single instance since the parent keys are constant in all these cases.
I do not know if it is possible for an item, unit and ability type to have the same id and still work. If it is then collisions are possible. In any case the solution would be separate hashtables for each, and since there is a limited number of types the 256 hashtable limit is not an issue.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
Type Id collisions are possible. However, mostly they only use one of the two... in some cases, they use one for some things and the other for some other things.

I recall ability Tooltips were pretty bugged that way, but mostly it will not cause any differences.

256 hashtables is quite much if you prefer arrays.

I dont use Table and only use 4 hashtables and cannot imagine having the need of using more than 10.
I do have an idea for something that uses x hashtables to make a x+1 dimensional table.
So I would use 4 hashtables to make a 5 dimensional table... however, I only saw the need of 3D.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I do not know if it is possible for an item, unit and ability type to have the same id and still work.
It's pretty much impossible. The world editor won't allow you to enter the same ID upon object creation.


@those asking questions:
In general, it's a good practice to have a hashtable for every system used. This not only avoids collision, but it also keeps the table lookups fast and efficient.

For spells, you usually only attach to two different types anyway: units and timers. And while collision is impossible that way, it's still a good practice to have a global table each:
For example, have a general "UNIT_HASH" containing data stored to GetHandleId(unit), then the childkeys indexed by the spell member identifiers (global constants that are unique for each variable in coded spells) and one more table "TIMER_HASH" for all dynamically created and destroyed timers (This is by far the more frequently used one of the two ... you rarely need to attach to units).
 
Status
Not open for further replies.
Top