• 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.

Alternatives for "Units Enters Region" Events?

Level 15
Joined
Jun 9, 2008
Messages
309
In my current project, I have several mechanics that depend on regions. One I especially like is that a unit's armor and art will adapt depending on what "climate zone" they are in.

My first impulse was to make it a simple "unit enters region" trigger (whenever a unit enters the region, it gets the specific upgrades or downgrades).

The trouble is, in my 480x480 map with 23 players, I cannot shake the feeling that anything that uses regions makes the map more unstable and likely to lag or to crash. It seems it cannot perfectly handle potentially hundreds of units triggering the action all across the map.

So I am looking for alternatives. One thing I considered using other events than "enters region", say "unit is given an order" and then check "is in region" in the conditions. Not sure that makes it less hard to handle for the computer, though.

Another idea I had was to place dummy units that cast spells on any nearby units, and that specific spell casting triggers the actions. But that's obviously kinda messy, and I haven't tested it yet.

I don't want to check every unit in the map periodically, that sounds like a bad idea.

The most simple solution might be to have much smaller regions, subdivide every "climate zone" into much smaller units, but of course that is a bit annoying to put in place, too.

Any thoughts?
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
I can't think of a way to do this without using one of these:

1) Use a Region Event or the Unit comes within range Event.
2) Periodically check unit positions.

If Region Events really do cause problems then I would try #2 but optimize the hell out of it. For starters, there's no reason you need to periodically check EVERY unit, that's what Unit Groups are for. You can manage said groups in a strategic way, adding and removing units as needed. So for example, you can have one central Unit Group that contains Units that can actually adapt to their climate (I assume not every Unit does this). Then have a "Snow" Unit Group which periodically adds units from the Central group to it whenever they're within the snow region(s) - or maybe better yet just check the Terrain Type at their position for Snow. You could even use an Array for the Units instead of a Unit Group. Then have a Timer which runs 20 times per second, checking the position of a new unit in the Array each time. That way you can spread the process of checking the position of say 100 units over the course of 5 seconds rather than in a single frame.
 
Last edited:
Level 15
Joined
Jun 9, 2008
Messages
309
I can't think of a way to do this without using one of these:

1) Use a Region Event or the Unit comes within range Event.
2) Periodically check unit positions.

If Region Events really do cause problems then I would try #2 but optimize the hell out of it. For starters, there's no reason you need to periodically check EVERY unit, that's what Unit Groups are for. You can manage said groups in a strategic way, adding and removing units as needed. So for example, you can have one central Unit Group that contains Units that can actually adapt to their climate (I assume not every Unit does this). Then have a "Snow" Unit Group which periodically adds units from the Central group to it whenever they're within the snow region(s) - or maybe better yet just check the Terrain Type at their position for Snow. You could even use an Array for the Units instead of a Unit Group. Then have a Timer which runs 20 times per second, checking the position of a new unit in the Array each time. That way you can spread the process of checking the position of say 100 units over the course of 5 seconds rather than in a single frame.
Might be worth its own thread, but I implemented a strategy inspired by what you said -

I put all eligible units in universal unit groups (arry per player) upon being trained or trigger-created.

The problem is, for some reason this only works for Player1 units. It's really weird. The functions are always something like "put last created unit in ArmyClimateX(Player number of owner of last created unit)" and it never works for computer players.

Here is specifically the trigger that very much doesn't seem to work for the AIs:

  • Camouflage Periodical Uncle3 X17
    • Events
      • Time - Every 0.22 seconds of game time
    • Conditions
    • Actions
      • Trigger - Turn off (This trigger)
      • Player Group - Pick every player in Climateplayers and do (Actions)
        • Loop - Actions
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • And - All (Conditions) are true
                • Conditions
                  • (Number of units in ArmyClimate1[(Player number of (Picked player))]) Equal to 0
                  • (Number of units in ArmyClimate3[(Player number of (Picked player))]) Greater than 0
            • Then - Actions
              • Game - Display to (All players) for 5.00 seconds the text: All Gone!
              • Unit Group - Pick every unit in ArmyClimate3[(Player number of (Owner of (Picked unit)))] and do (Actions)
                • Loop - Actions
                  • Unit Group - Remove (Picked unit) from ArmyClimate3[(Player number of (Picked Player)]
                  • Unit Group - Add (Picked unit) to ArmyClimate1[(Player number of (Owner of (Picked unit)))]
                  • Animation - Change (Picked unit)'s vertex coloring to (100.00%, 100.00%, 100.00%) with 0.00% transparency
            • Else - Actions
      • Trigger - Turn on (This trigger)
And yes, the AI players are all in Climateplayers, I made a trigger to check for that (change unit size if picked unit belongs to a player in Climateplayers).

The issue seems to specifically be ArmyClimate3, exclusively for AI players.

Note that I changed vertex coloring of units to keep track. They turn black after being first put in ArmyClimate3, and are supposed to be restored to normal colours once being picked by this trigger.

Again, it never works for AI players...
 

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
Unit Group arrays need to be initialized manually. This means giving them a Size in the variable editor. Alternatively, you can use Custom Script to create and assign the Unit Group object yourself - which serves useful when relying on Unit Indexing / Dynamic Indexing which can have varied [indexes].

So if your map has 24 Players then set the Size to 24. If you want to use the Neutral players as well then set it to 28. This means that your ArmyClimate3 index will now work in a range of [0] to [28].

The same holds true for Player Group arrays, Timer arrays, and... something else I'm probably forgetting.

Random notes:
  • Turning off/on the trigger doesn't do anything here (as far I can see).
  • And - All (Conditions) are true is unnecessary. That's the default behavior of Conditions.
  • If you're aiming for efficiency, use Integer arrays to track the number of units in the groups instead of using (Number of units in...). That function requires looping over the entire Unit Group and counting each unit one by one (no idea why they didn't optimize this).
  • Use a variable for the (Player number) and (Picked unit) to make it even more efficient.
 
Last edited:
Level 15
Joined
Jun 9, 2008
Messages
309
Unit Group arrays need to be initialized manually. This means giving them a Size in the variable editor. Alternatively, you can use Custom Script to create and assign the Unit Group object yourself - which serves useful when relying on Unit Indexing / Dynamic Indexing which can have varied [indexes].

So if your map has 24 Players then set the Size to 24. If you want to use the Neutral players as well then set it to 28. This means that your ArmyClimate3 index will now work in a range of [0] to [28].

The same holds true for Player Group arrays, Timer arrays, and... something else I'm probably forgetting.

Random notes:
  • Turning off/on the trigger doesn't do anything here (as far I can see).
  • And - All (Conditions) are true is unnecessary. That's the default behavior of Conditions.
  • If you're aiming for efficiency, use Integer arrays to track the number of units in the groups instead of using (Number of units in...). That function requires looping over the entire Unit Group and counting each unit one by one (no idea why they didn't optimize this).
  • Use a variable for the (Player number) and (Picked unit) to make it even more efficient.
Very interesting. So is this something particular for unit group arrays paired with players?
For instance, I have used unit arrays for a while now where I left the array size at "1" and it seems to work as intended. I believe I read somewhere that if you leave it at the default "1", it treats that as "no upper limit", but I guess that was a misconception.
 
The trouble is, in my 480x480 map with 23 players, I cannot shake the feeling that anything that uses regions makes the map more unstable and likely to lag or to crash. It seems it cannot perfectly handle potentially hundreds of units triggering the action all across the map.
I can definitely understand that feeling. It feels like a lot of stuff going on.

However, there is a common saying in software--premature optimization is the root of all evil. I've implemented a ton of complex systems in the past to avoid what I thought would be laggy, only to find out the "simpler" solution was totally sufficient. :grin: (and sometimes even faster!)

The other thing to consider with Warcraft III is the cost of the "engine"-based code vs. our custom code. The code that makes up wc3's engine is compiled and often optimized for the specific use-case. Our custom code, on the other hand, is interpreted on-the-fly--and trying to re-implement a concept that the engine already supports will often end up being less efficient by several orders of magnitude. So when you're trying to roll your own solution, it's always good to ask: "what am I trying to get out of my solution that the built-in solution doesn't provide?"

In this case, it's less about functionality--it's more about performance concerns. So the best thing to do is to measure it! And often times, you'll want to stress-test it--come up with an impractically harsh scenario to test in. If it works reasonably in that environment, you're pretty much guaranteed that it'll work for yours.

I was curious, so I made a map that generates 512x512 regions throughout a 480x480 map via code. Every single cell is covered, and each region has an event attached to it for a single trigger. This leads to 13689 regions (and 13689 events). Imagine that it looks something like this:
RegionExampleTwo.png

Next, a unit gets spawned every 0.5 seconds randomly on the map. Every 2 seconds, any idle units in that group get issued an order to move to a random area on the map. When a unit enters a region, it just adds a number to a leaderboard.

After running the map for about 8 minutes, I had 1000 units spawned and actively moving--and almost 120,000 "unit enters region" events fired (~200-300 events fired per second).
KnightsGalore.png


The FPS was a solid 60 throughout the whole session (I have it capped at 60 so it hovers around 59-60). I didn't have any frame drops. My machine might be reasonably fast (although my CPU is really old), but if it was really a bottleneck I would've expected some noticeable difference in performance. The only quirk I noticed was that units were randomly stopping in the middle of their orders (and then continuing), but I believe that is just a quirk with how wc3 handles that many orders/pathing at one time. Not anything to do with the regions aspect.

The main takeaway is that regions most likely won't cause any performance concerns for your use case. So if I were in your shoes, I'd strongly consider it--as it often ends up being a lot easier to debug/maintain over time. And in general, event-based systems often end up being more efficient compared to polling-based systems (i.e. something that has to analyze every unit's position every X seconds)--as the events only execute when something meaningful has happened, whereas the polling systems often have to uselessly check a bunch of units that haven't even moved since they were last queried. Also, to my knowledge, I haven't heard of people having issues with regions/performance in the past, so you're probably fine!

I'll attach the test map if you want to play around with it. You'll need to enable JassHelper if you want to run it via the editor. Hope it helps give you confidence in whichever approach you choose!
 

Attachments

  • RegionTest.w3m
    275 KB · Views: 6

Uncle

Warcraft Moderator
Level 73
Joined
Aug 10, 2018
Messages
7,877
Very interesting. So is this something particular for unit group arrays paired with players?
For instance, I have used unit arrays for a while now where I left the array size at "1" and it seems to work as intended. I believe I read somewhere that if you leave it at the default "1", it treats that as "no upper limit", but I guess that was a misconception.
It's particular for these 3 types of Arrays: Unit Groups, Player Groups, and Timers.

I assume it's because they're more complex by design and are handled in a more efficient manner. Every other Array type can remain at a size of 1 and will dynamically adjust as needed.

The highest an index can be is [32,768] regardless of what you do.
 
Last edited:
It's particular for these 3 types of Arrays: Unit Groups, Player Groups, and Timers.

I assume it's because they're more complex by design and are handled in a more efficient manner. Every other Array type can remain at a size of 1 and will dynamically adjust as needed.

The highest an index can be is [32,768] regardless of what you do.
Very interesting. So is this something particular for unit group arrays paired with players?
For instance, I have used unit arrays for a while now where I left the array size at "1" and it seems to work as intended. I believe I read somewhere that if you leave it at the default "1", it treats that as "no upper limit", but I guess that was a misconception.
Just for some extra detail on the feature:

When you set the "size" of the variable array in GUI, that just tells the editor to generate some code to "fill up" the array N times with the initial value that you selected. For example, if I have the following variables below:
VariableWindow.png

Then it will generate this code which will fill up the arrays on initialization (creating 28 empty unit groups and 10 timers):
JASS:
function InitGlobals takes nothing returns nothing
    local integer i= 0
    set i=0
    loop
        exitwhen ( i > 28 )
        set udg_GroupArray[i]=CreateGroup()
        set i=i + 1
    endloop

    set i=0
    loop
        exitwhen ( i > 10 )
        set udg_TimerArray[i]=CreateTimer()
        set i=i + 1
    endloop

endfunction

As Uncle mentioned, it is going to be most useful with Unit Groups/Player Groups/Timers (and Dialogs), especially since GUI doesn't let you create empty unit groups/empty player groups/new timers/new dialogs without using a custom script. But you could technically use this feature with any array that accepts an initial value (e.g. you could make an integer array pre-filled with 5's). But you often don't need it with other types, like units, because most of the time you're filling it yourself rather than relying on it to be "pre-filled" with things.

So ultimately, it just depends on what you're trying to do. It's not a hard-and-fast rule. For example, if I were trying to fill a unit group array myself with custom groups:
  • Actions
    • Set VariableSet GroupArray[0] = (Units owned by Player 1 (Red).)
    • Set VariableSet GroupArray[1] = (Units owned by Player 2 (Blue).)
    • Set VariableSet GroupArray[2] = (Units owned by Player 3 (Teal).)
Then you can just leave the size as 1. But if you want a pre-filled array of empty groups (like in your case), then you can set the size to however many empty groups you want!
 
Level 25
Joined
Mar 29, 2020
Messages
1,466
I can definitely understand that feeling. It feels like a lot of stuff going on.

However, there is a common saying in software--premature optimization is the root of all evil. I've implemented a ton of complex systems in the past to avoid what I thought would be laggy, only to find out the "simpler" solution was totally sufficient. :grin: (and sometimes even faster!)

The other thing to consider with Warcraft III is the cost of the "engine"-based code vs. our custom code. The code that makes up wc3's engine is compiled and often optimized for the specific use-case. Our custom code, on the other hand, is interpreted on-the-fly--and trying to re-implement a concept that the engine already supports will often end up being less efficient by several orders of magnitude. So when you're trying to roll your own solution, it's always good to ask: "what am I trying to get out of my solution that the built-in solution doesn't provide?"

In this case, it's less about functionality--it's more about performance concerns. So the best thing to do is to measure it! And often times, you'll want to stress-test it--come up with an impractically harsh scenario to test in. If it works reasonably in that environment, you're pretty much guaranteed that it'll work for yours.

I was curious, so I made a map that generates 512x512 regions throughout a 480x480 map via code. Every single cell is covered, and each region has an event attached to it for a single trigger. This leads to 13689 regions (and 13689 events). Imagine that it looks something like this:
View attachment 484687

Next, a unit gets spawned every 0.5 seconds randomly on the map. Every 2 seconds, any idle units in that group get issued an order to move to a random area on the map. When a unit enters a region, it just adds a number to a leaderboard.

After running the map for about 8 minutes, I had 1000 units spawned and actively moving--and almost 120,000 "unit enters region" events fired (~200-300 events fired per second). View attachment 484688

The FPS was a solid 60 throughout the whole session (I have it capped at 60 so it hovers around 59-60). I didn't have any frame drops. My machine might be reasonably fast (although my CPU is really old), but if it was really a bottleneck I would've expected some noticeable difference in performance. The only quirk I noticed was that units were randomly stopping in the middle of their orders (and then continuing), but I believe that is just a quirk with how wc3 handles that many orders/pathing at one time. Not anything to do with the regions aspect.

The main takeaway is that regions most likely won't cause any performance concerns for your use case. So if I were in your shoes, I'd strongly consider it--as it often ends up being a lot easier to debug/maintain over time. And in general, event-based systems often end up being more efficient compared to polling-based systems (i.e. something that has to analyze every unit's position every X seconds)--as the events only execute when something meaningful has happened, whereas the polling systems often have to uselessly check a bunch of units that haven't even moved since they were last queried. Also, to my knowledge, I haven't heard of people having issues with regions/performance in the past, so you're probably fine!

I'll attach the test map if you want to play around with it. You'll need to enable JassHelper if you want to run it via the editor. Hope it helps give you confidence in whichever approach you choose!
this map is the most cursed thing I've seen on this website for while :grin: nice job
 
Top