Destroying a group that has had an enumeration called for it leaks RAM. (Example in spoiler.)
function FTRUE takesnothingreturnsbooleanreturntrueendfunctionfunction Trig_Untitled_Trigger_001_Actions takesnothingreturnsnothinglocalgroup g
set g=CreateGroup()call GroupEnumUnitsInRange(g,0,0,50000,Filter(function FTRUE))call GroupClear(g)// irrelevant, reallycall DestroyGroup(g /*Leaks because "g" has had an "Enum" called on it*/)set g=nullendloopendfunction
What should I do?
This is a magic snippet:
I use this in all my maps. Inside any enum function, we may place a filter. We can use this to execute code, and return false so no units are ever added. Let's say we want to heal every unit on the map for 200 health.
This is how you may be used to doing it:
globals// values to carry to the DoThings functionreal AmountToHeal
endglobalsfunction DoThings takesnothingreturnsnothingcall SetWidgetLife(GetEnumUnit(), GetWidgetLife(GetEnumUnit()) + AmountToHeal
endfunctionfunction Trig_Untitled_Trigger_001_Actions takesnothingreturnsnothinglocalgroup g = CreateGroup()call GroupEnumUnitsInRect(g,bj_mapInitialPlayableArea,null /*leak*/)set AmountToHeal = 200.0
call ForGroup(g,function DoThings)call DestroyGroup(g /*leak*/)set g=null /*at least this doesn't leak*/
Implement the magic snippet. Now, examine this code:
globals// values to carry to the DoThings functionreal AmountToHeal
endglobalsfunction DoThings takesnothingreturnsboolean
/*nothing becomes boolean*/
call SetWidgetLife(GetFilterUnit(), GetWidgetLife(GetFilterUnit()) + AmountToHeal
/*GetEnumUnit() becomes GetFilterUnit()*/
/*returnfalse appears here*/
endfunctionfunction Trig_Untitled_Trigger_001_Actions takesnothingreturnsnothing// Store values before the enum insteadset AmountToHeal = 200.0
// Note the next line.call GroupEnumUnitsInRect(GROUP,bj_mapInitialPlayableArea, Filter(function DoThings))// The end.endfunction
Explanation for those who don't understand:
[SPOILER]I will explain the last line. We must put something in the filter. We could put in the following if we like:
function ReturnTrue takesnothingreturnsbooleanreturntrueendfunction//...call GroupEnumUnitsInRect(GROUP,bj_mapInitialPlayableArea, Filter(function ReturnTrue))
But then we need to clear our group later, or use a FirstOfGroup loop (largely deprecated). This way we never even actually add the units to the group. This method is only useful when you do not need to store the group with units in it for any period of time, ie. you are not storing a group of units, but rather just want to do things for a bunch of units, like in the example.
The rest of the comments explain the process of changing the callback that usually goes into ForGroup into a filter for the Enum instead. Be sure not to return nothing. It must return boolean or bad things will happen (untested personally, but apparently it can cause desynchs or something).[/SPOILER]
If you need to store a group of units, for example, all units that a spell has hit so far, you can use dynamic groups, but not with [LJASS]CreateGroup[/LJASS] and [LJASS]DestroyGroup[/LJASS]...
To use dynamic groups, groups should be recycled instead of destroyed. I recommend Recycle to recycle groups. Download this snippet and install it into your map. Then replace: [LJASS]CreateGroup()[/LJASS] with [LJASS]Group.get()[/LJASS] and
[LJASS]call DestroyGroup(g)[/LJASS] with [LJASS]call Group.release(g)[/LJASS]
This will reuse the groups and store them in a list until they are reused. The groups are cleared before they are stored, so there is no difference in use to destroying the group, except you should be careful not to store a reference to the group and do something to it after releasing it (that would be hard to debug).
Thus we solve the leak that occurs when we destroy a group that has had an enum called for it.
What else should I know aside from RAM leaks?
Any Enum call clears all units from a group before it adds units to the group. To enum units in a group and keep the previous units, instead use your global GROUP variable, and in the filter, add the units to the group you wish to add to.
Adding a unit to a group does not leak its handle id if it is removed from the game.
Removing a unit from the game does not remove its reference from the group! This is called a "shadow reference". Now, from my understanding, it is generally accepted that a shadow reference is never included a ForGroup, but I think I found that it can be if you call the ForGroup immediately after removing the unit from the game. Shadow references take up a little RAM, so you should clean out a group occasionally. This can be done with this simple snippet written by CaptainGriffen:
Simply call GroupRefresh on a group to clear out the shadow references. This is only needed when you have a permenant group in a map that keeps track of units, and only needed to free up some RAM. It is O(n) complexity, so don't spam its use if possible. (I never use it personally, unit attachment solves this in O(1) complexity, but that doesn't belong to this tutorial.)
There is a deprecated thing called a FirstOfGroup loop. It works like this:
GROUP group = CreateGroup()endglobalsfunction ReturnTrue takesnothingreturnsbooleanreturntrueendfunctionfunction Trig_Untitled_Trigger_001_Actions takesnothingreturnsnothinglocalunit u
call GroupEnumUnitsInRect(GROUP,bj_mapInitialPlayableArea,Filter(function ReturnTrue))loopset u=FirstOfGroup(GROUP)exitwhen u==nullcall GroupRemoveUnit(g,u)// Do things to uendloopendfunction
This is not as efficient as using a filter for your actions, as a general rule. It is also vulnerable to shadow references if the units have been stored in the group for any period of time (in other words, is not done instantly after an Enum generally). It also requires you to make some sort of filter, even if you don't need it, otherwise you will have a leak due to the null filter (or boolexpr). In short, it is pointless to use this method, except it gives you access to your local variables so you don't need to carry things over. I recommend you do not use it, but you are welcome to all the same.
GroupClear clears all references out of a group, including shadow references.
You should never need to use GroupClear on your global GROUP, because Enum calls clear it anyway, and you should never need to actually add units to it.
for doing things to a bunch of units, and put your actions in the Filter, returning false at the end. (Example: heal or damage an AoE of units.)
Use Recycle for tracking a bunch of units for a time. (Example: record what units have been hit by a spell.) Release your group at the end using Group.release(groupVariable), and I'd recommend to null your references to it unless you trust yourself not to accidentally use them (or else you can end up with issues which are hard to debug).
If you have a permanent global group for tracking units, use GroupRefresh on it occasionally to free some RAM up. :thup:
You can now use groups without leaks, in efficient, sensible ways. :)