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

Hash tables split for optimization: is it worth it ?

Status
Not open for further replies.
Level 1
Joined
Jul 14, 2022
Messages
4
Hi!
Sorry for a beginner's questions 😅
My goal is to make some units cast spells on auto-attack. I've noticed some FPS drop even with 3-4 players, so I have to upgrade some triggers.
But I'm a bit confused: I've read that large hash tables and multiple if\then\else statements in trigger with "A unit is attacked" event - both can cause lags.
I'm not sure which method is better: create a single hashtable for ~50 unit types, or split it for 25 each? Which one is faster?

  • Unit Attacked
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • -------- Unit Attacked --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Level of Unit Attacked (Dummy Ab) for (Attacked unit)) Greater than 0
        • Then - Actions
          • Custom script: set udg_Hash_Id = GetUnitTypeId(GetAttackedUnitBJ())
          • Set VariableSet Unit_Id[Hash_Id] = [Hash_Id]
          • Set VariableSet Attacker[Hash_Id] = (Attacking unit)
          • Set VariableSet Target[Hash_Id] = (Attacked unit)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • (Level of Unit Hash 1 (Dummy Ab) for (Attacked unit)) Greater than 0
            • Then - Actions
              • -------- Hash 1 -------
              • Trigger - Run (Load 0 of Hash_Id in Hash_1.) (ignoring conditions)
            • Else - Actions
              • -------- Hash 2 --------
              • Trigger - Run (Load 0 of Hash_Id in Hash_2.) (ignoring conditions)
or
  • Unit Attacked
    • Events
      • Unit - A unit Is attacked
    • Conditions
    • Actions
      • -------- Unit Attacked --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Level of Unit Attacked (Dummy Ab) for (Attacked unit)) Greater than 0
        • Then - Actions
          • Custom script: set udg_Hash_Id = GetUnitTypeId(GetAttackedUnitBJ())
          • Set VariableSet Unit_Id[Hash_Id] = [Hash_Id]
          • Set VariableSet Attacker[Hash_Id] = (Attacking unit)
          • Set VariableSet Target[Hash_Id] = (Attacked unit)
          • Trigger - Run (Load 0 of Hash_Id in Hash_1.) (ignoring conditions)
Another question: do I really need Attacker and Target variables being arrays to prevent multiple triggers "interference"?
Should I worry about the next A unit Is attacked event overwriting the Attacker and Target variables before previous trigger completed, messing up some spells?

If so, I wonder which one is faster (that's in case if multiple unit types use the same trigger):

  • Actions
    • Set VariableSet Temp_Id[1] = Unit_Type[1]
    • Set VariableSet Temp_Id[2] = Unit_Type[2]
    • Set VariableSet Temp_Id[3] = Unit_Type[3]
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • Unit_Id[Temp_Id[1]] Greater than 0
      • Then - Actions
        • Set VariableSet Ab1_Id = Unit_Id[Temp_Id[1]]
        • Set VariableSet Unit_Id[Temp_Id[1]] = 0
      • Else - Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • Unit_Id[Temp_Id[2]] Greater than 0
          • Then - Actions
            • Set VariableSet Ab1_Id = Unit_Id[Temp_Id[2]]
            • Set VariableSet Unit_Id[Temp_Id[2]] = 0
          • Else - Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • Unit_Id[Temp_Id[3]] Greater than 0
              • Then - Actions
                • Set VariableSet Ab1_Id = Unit_Id[Temp_Id[3]]
                • Set VariableSet Unit_Id[Temp_Id[3]] = 0
              • Else - Actions
      • Unit - Order Attacker[Ab1_Id] to Neutral - Firebolt Target[Ab1_Id]
      • Set VariableSet Attacker[Ab1_Id] = No unit
      • Set VariableSet Target[Ab1_Id] = No unit
      • Set VariableSet Ab1_Id = 0
or just

  • Actions
    • Set VariableSet Temp_Id[1] = Unit_Type[1]
    • Set VariableSet Temp_Id[2] = Unit_Type[2]
    • Set VariableSet Temp_Id[3] = Unit_Type[3]
    • Set VariableSet Ab1_Id = ((Unit_Id[Temp_Id[1]] + Unit_Id[Temp_Id[2]]) + Unit_Id[Temp_Id[3]])
    • Unit - Order Attacker[Ab1_Id] to Neutral - Firebolt Target[Ab1_Id]
    • Set VariableSet Unit_Id[Temp_Id[1]] = 0
    • Set VariableSet Unit_Id[Temp_Id[2]] = 0
    • Set VariableSet Unit_Id[Temp_Id[3]] = 0
    • Set VariableSet Attacker[Ab1_Id] = No unit
    • Set VariableSet Target[Ab1_Id] = No unit
    • Set VariableSet Ab1_Id = 0
or even

  • Actions
    • Set VariableSet Temp_Id[1] = Unit_Type[1]
    • Set VariableSet Temp_Id[2] = Unit_Type[2]
    • Set VariableSet Temp_Id[3] = Unit_Type[3]
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • Attacker[Temp_Id[1]] Not equal to No unit
      • Then - Actions
        • Unit - Order Attacker[Temp_Id[1]] to Neutral - Firebolt Target[Temp_Id[1]]
        • Set VariableSet Attacker[Temp_Id[1]] = No unit
        • Set VariableSet Target[Temp_Id[1]] = No unit
      • Else - Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • Attacker[Temp_Id[2]] Not equal to No unit
          • Then - Actions
            • Unit - Order Attacker[Temp_Id[2]] to Neutral - Firebolt Target[Temp_Id[2]]
            • Set VariableSet Attacker[Temp_Id[2]] = No unit
            • Set VariableSet Target[Temp_Id[2]] = No unit
          • Else - Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • Attacker[Temp_Id[3]] Not equal to No unit
              • Then - Actions
                • Unit - Order Attacker[Temp_Id[3]] to Neutral - Firebolt Target[Temp_Id[3]]
                • Set VariableSet Attacker[Temp_Id[3]] = No unit
                • Set VariableSet Target[Temp_Id[3]] = No unit
              • Else - Actions
Thanks for the help, guys! 😀
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
Don't worry about the Hashtables, you're not even close to making a dent nor should you really worry about a thing like that.

Here's an issue though:
  • Custom script: set udg_Hash_Id = GetUnitTypeId(GetAttackedUnitBJ())
  • Set Variable Unit_Id[Hash_Id] = Hash_Id
  • Set Variable Attacker[Hash_Id] = (Attacking unit)
  • Set Variable Target[Hash_Id] = (Attacked unit)
A unit's Unit-Type Id is a fairly large integer and will be larger than the [index] limit of arrays (32768). This size is fine for Keys in Hashtables though.
I imagine the reason it works (if it works) is that Hash_Id gets defaulted to 0 when applied to the [index]. So in other words, you're basically doing this:
  • Set Variable Unit_Id[0] = 12345678 (some large int)
  • Set Variable Attacker[0] = (Attacking unit)
  • Set Variable Target[0] = (Attacked unit)
Luckily, you can almost certainly use non-Array variables here instead. Triggers are created in separate instances which are then queued up and run one after the other. So if 3 units were to attack at the same exact time, first those attacks would be queued up because 3 things can't logically happen at the same time, and then something like this would occur:

Step 1: Attacks are ordered into a queue. One of those attacks is then chosen to execute first.
Step 2: Any triggers using the "A unit is attacked" Event run in response to this attack.
Step 3: The Attacked unit/Attacking unit are tracked in their respective Event Responses. These are generally no different from your own global Variables.
Step 4: Once all of these triggers have finished (triggers with Waits are considered finished the moment a Wait is met) then the next instance can occur.
Step 5: Repeat Steps 2 -> 5 for each attack until the queue is empty.

Hopefully someone can correct me if I'm wrong about this logic but this is how I've come to understand it.

There are some VERY niche cases where triggers combine their Actions rather than running separately but you should not really worry about those.
For example, Trigger A sets some variables and then causes Trigger B to run which modifies those same variables before Trigger A has finished. This can happen with a small number of Events/Actions but even then it could be perfectly fine if structured properly.

Anyway, I've heard a few people say that the use of the "A unit is attacked" Event causes performance issues, so even with these changes you may not see much of a noticeable difference. Perhaps a better method would be to give your units Autocast abilities and use those as the Event for firing the spell. Or maybe use a Timer/Timers + Unit Groups to periodically order the units to cast spells.
 
Last edited:
Level 1
Joined
Jul 14, 2022
Messages
4
A unit's Unit-Type Id is a fairly large integer
Oh, thanks! That's explains alot! Had no idea that Unit-type is so big.
That's some good news that non-array variables are fine, it makes things easier.
And ofc. I can just check if the Attacker \ Target equal to No unit before starting a new trigger (if it even needed).

I've got such trigger glitches when I've used For loop Integer A function in multiple triggers (for some reason I was sure that default Blizz' Integer A is local variable). That was… a horrible mess, and it was really hard for me to find what's going wrong 😰 Since then I've become a bit paranoic about triggers' variables interferences 😃

I'm new to this forum, where can I give you likes?
 
Level 1
Joined
Jul 14, 2022
Messages
4
Another question is about conditions optimization. Should I put them into Trigger's conditions, into Action Conditions, or do multiple if\then\else?
I'm not sure how exactly conditions check works: will it check until the first false condition, or it won't skip further checks and complete whole block?

  • Events
  • Conditions
    • A Greater than 0
    • B Greater than 0
    • C Greater than 0
  • Actions
    • -------- Do some stuff --------
vs

  • Events
  • Conditions
  • Actions
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • A Greater than 0
        • B Greater than 0
        • C Greater than 0
      • Then - Actions
        • -------- Do some stuff --------
      • Else - Actions
vs

  • Events
  • Conditions
  • Actions
    • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
      • If - Conditions
        • A Greater than 0
      • Then - Actions
        • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
          • If - Conditions
            • B Greater than 0
          • Then - Actions
            • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
              • If - Conditions
                • C Greater than 0
              • Then - Actions
                • -------- Do some stuff --------
              • Else - Actions
          • Else - Actions
      • Else - Actions
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,537
I'm not sure how exactly conditions check works: will it check until the first false condition, or it won't skip further checks and complete whole block?
The first Condition to fail will return out of the function early (skip everything after it) so you should always put the more common Conditions first.
Another question is about conditions optimization. Should I put them into Trigger's conditions, into Action Conditions, or do multiple if\then\else?
Your first example is best to do when able.
If Then Else is generally used so you can take advantage of the ELSE part of it ---> If VAR == 1 then do A else do B
It also comes in handy when you need to ask a question later on in your trigger due to some varying possibilities, like checking if a missile is near any units.

Keep in mind that you may want to use variables if you're checking the same thing multiple times.

Example A:
  • Events
  • Unit - A unit gains a level
  • Conditions
  • Unit-type of (Triggering unit) Equal to Paladin
  • Actions
  • Set Variable MyUnit = (Triggering unit)
  • Unit - Add AbilityA to MyUnit
  • Unit - Add AbilityB to MyUnit
  • Unit - Add AbilityC to MyUnit
Example B:
  • Events
  • Unit - A unit gains a level
  • Conditions
  • Unit-type of (Triggering unit) Equal to Paladin
  • Actions
  • Unit - Add AbilityA to (Triggering unit)
  • Unit - Add AbilityB to (Triggering unit)
  • Unit - Add AbilityC to (Triggering unit)
Example A is more efficient than Example B. This is because (Triggering unit) is actually a Function call which returns the (Triggering unit):
vJASS:
GetTriggerUnit()
It's faster to reference a variable than to run that function multiple times, and Example A calls GetTriggerUnit() twice while Example B calls it four times.
Note that setting the MyUnit variable has to run that function as well.
 
Last edited:
Level 1
Joined
Jul 14, 2022
Messages
4
The first Condition to fail will return out of the function early (skip everything after it) so you should always put the more common Conditions first.

Your first example is best to do when able.

It's faster to reference a variable than to run that function multiple times
Thanks again! Good to know that my intuition ain't failing me ☺️
 
Status
Not open for further replies.
Top