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

General Questions about Trigger Optimizing and Performance

Status
Not open for further replies.
Level 2
Joined
Nov 12, 2022
Messages
7
Hello everybody,

I was wondering if the triggers itself can be optimized for a better performance. I could not find general rules, which can be applied on a more detailed level.
Some guides might imply some information about it, but do not enter into more detail.

The most guides explain optimizing by getting rid of leaks and tipps regarding the build up of the map itself, e.g.:
http://world-editor-tutorials.thehelper.net/cat_usersubmit.php?view=41198

However, there are multiple ways to build a method with trigger to fulfil a certain purpose, which seem to be the same in terms of performance, but may have a very small difference, which could grow to a recognizable scale.

I thought about these simple questions:
I will explain the issue in more detail below. The compared methods do exactly the same in terms of their purpose in the game.
1. Is there a difference betwenn accessing the Player of the Unit: (Triggering Player) - (Owner of Unit(Triggering Unit)
2. Is there a difference in adding abilitys to one ability: creating 2 triggers for the same ability doing different things - merging the functions into 1 trigger
3. Is there a difference in: storing points/regions in an arrays - creating and instantly destroying the points/regions when used

To be clear, this is not about how to prevent leaks or structuring the code/triggers for a better overview, but what the game can handle better by using less memory storage / CPU power.
This can be achieved through optimizing the triggers and variables itself, e.g. using 1 variable for each player vs using a variable as an array with the length of the number of players. This example can have a small and maybe neglectable difference in the storage space (8 variables with 1 value vs 1 variable with 8 values), but maybe not in time or steps needed to access the variable values.

Assumptions for the storage system of wc3:
I imagined the system to querying e.g. a unit or a player in a trigger like some kind of relational database system: a number of dynamic tables, which are referencing the entities in the game. This needs to be somehow dynamic to adress the trigger events. When an Event occurs (e.g. (A Unit starts the effect of an ability)), the whole event - or to say its entity - is stored as a data row, having some ID, the ID of the Triggering Unit, the ID of the Player of the Triggering unit, the ID of the spell being casted and so on, depending on what kind of event it is.

When we want to adress the Unit casting the spell, we have the direct link to it through our data row, in which we find the ID of the Unit and therefore the Unit in a single step (constant) and without e.g. iterating over all units (which would not be constant anymore, since it depends on the number of units).

For question 1:
Lets say, we want to do something with the player of the unit in a trigger. To handle this entity, we need to access it and we can do it in (at least) 2 ways in the event response:
(Triggering Player) or
(Owner of Unit(Triggering Unit)).
Both work fine, but may have a difference in how many steps the right value can be found internally. Since the (Triggering Player) is already stored in the data row, we would need 1 step to find it. For the second method, we would find the (Triggering Unit) in 1 step, but need to apply the (Owner of Unit)-function on it, resulting in 2 steps. This is still a constant time, but might need more steps and would be worse. Even when this is on a neglectable scale. But when the later method is used hundrets or thousands of times it could have a small impact on the memory being used and therefore can be optimized to use resources more efficient.
I am not sure if my assumptions are correct and would like to know, where one could find out how this works in more detail.
(I know, it might be not be worth it, but I am still curious)

For question 2:
Lets say we want to have an ability (A), so that a second (B) and thrid ability (C) are casted, when activated. This can be done in (at least) 2 different methods:
Creating 1 Trigger, which activates for (A) and checks conditions for ability (B) and afterwards condition for ability (C)
Creating 2 Triggers, both activate for (A) and check respectively the condition for (B) and (C)
The question here is, which could be more efficient. Are Triggers harder to handle for the game or is a longer trigger worse for the performance? Here I would also differ betwenn the memory usage (which occupies more) and the steps needed to finish each method. Image here, like above, that this is applied hundrets or even thousands of time in a map.
(if there is a very neglectable difference I would still like to know, like above ;) )

For Question 3:
I have seen the long array-method in some tower defenses: The regions centers are stored in a long array and are accessed in the trigger for the unit movements. Since this is some trigger which is accessed quiete a lot, it could make the biggest difference of the 3 examples given in this post.
A long array needs more memory space, but would be constant. Creating and deleting the points in each iteration would need more computanional power, but saves memory space, since only a subset of the regions exist at the same time. I guess one could make some overview function with dependencies between these 2 methods, since the number of times they are accessed would be the main factor. However, most points are dynamically and therefore dont have that option to begin with.
My conclusion here would be, that it is always more efficient to store (not-dynamically) points, than to create and delete them so that the game can handle them better.



All thoughts are based on my assumptions above. If there is even a small impact, it would be nice to know to understand it better and optimize the performance of maps even more. The optimization on this level might not be really necessary, but was very interesting for me to think about and could increase the performance bigger maps with many triggers and funtionality.

Thanks for everybody taking the time to read =)


Best regards
fjen
 

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,201
I am not sure if my assumptions are correct and would like to know, where one could find out how this works in more detail.
With JASS owner of triggering unit is almost certainly slower because JASS function calls are slow and it requires an additional function call. Similar to variable lookup, JASS performs the lookup from the string of the variable name and then checking the local followed by global tables that hold variables. This even means that longer variable and function names are slower to lookup, why Vexorian's optimiser tries to shorten them as much as possible.

This is so pronounced that trigonometric function such as sine or cosine, functions that are notoriously slow in real programming, are faster to use for testing unit facing than multiple arithmetic comparisons due to the additional variable resolutions required compared with a single function resolution and call.
The question here is, which could be more efficient.
Same trigger multiple conditions should be more performant. Starting trigger threads is much more expensive that just running some extra code. Trigger threads are apparently even more expensive than some pesudo threads such as timer callbacks.

That said neither are optional. Instead, you would want to use a map data structure that maps code such as in the form of a trigger to an ability type. This allows resolution of cast ability with a complexity of O(1) using just a single event bound to a single trigger with as good as infinite scalability. Most "spell systems" do this.
For Question 3:
Long arrays should be faster because it is fewer function calls. Data fed system may be slower than static linked system but this trade-off is well worth it as far as actually creating and setting up the system.

The additional memory used by the arrays is trivial in 2022. You are look at a few kilobytes at most. Warcraft III likely leaks more in a few minutes of play due to internal programming bugs or not being able to free deduplicated strings.

My general answer to this is that you are concerning yourself with micro-optimisations without really having a need. In most cases performance issues due to triggers will be much more obvious coding problems such as poorly scaling algorithms, impossible complexity or even just Warcraft III doing an operation very slowly. It is not worth sacrificing coding quality to chase miniscule performance gains as long as such bigger problems exist to solve. Always look for big problems first, and only look into mico-optimisations when you are left with no choice.

My answer above was assuming the JASS VM is used. The Lua VM is a lot more performant than the JASS VM and may have different performance and optimisation quirks.
 
Status
Not open for further replies.
Top