WarCraft III Maps by Antistone
How Memory Leaks
Introduction
If you listen to rumor, memory leaks are the scourge of map-making. These sinister leaks pop up all over the place, whenever you try to do anything remotely cool in the editor. They pool and fester, growing to titanic proportions, eating up system resources, and spreading like wildfire, even if you don't continue to feed them. They're the leading cause of crashes, server splits, crippling lag, and just about anything else that can possibly go wrong with your map. In order to hold them at bay, you need to ward your map with magical incantations, done exactly as described in whichever tutorial you find first, or else the Memory Leak Gods will be displeased and your map shall be Smitten.
Most of these rumors, of course, are complete fabrications.
There's a lot of concern about memory leaks out there, but not a lot of solid information. You'll find a lot of tutorials telling you how to exorcise memory leaks from you map, but not much that will tell you what a memory leak really is, how it works, or why it shows up in the first place (which is just as important, because you need to be able to reliably identify memory leaks in order to do anything about them). This page aims to provide information about how and why memory actually "leaks."
By the Numbers
First, we'll need to review a bit about how variables work. Variables come in different types, but for now, let's just consider integer variables; an integer variable will let you store a non-fractional number, so you can remember it and use it again later. For example, a simple trigger like this:
Set IntOne = 7
Display to (All Players) the text: String(IntOne)
Will print the number 7. You can think of a variable as a box where you can store information:
Rather than simply using the number 7, we might set the variable to the value of some expression. For example, suppose we wanted to store the number of units currently in the map; we could do something like this:
Set IntOne = Number of Units in (Units in (Playable Map Area))
Display to (All Players) the text: String(IntOne)
And if there were currently 12 units in the map, this would display the number 12. However, the value of IntOne is not "The number of units in the playable map area," it's just "12". If the number of units in the playable map area changes, the variable won't change (unless we set it again)--it is just remembering the specific number, and nothing else. Once we've set it's value, the variable doesn't remember how it was set, it just remembers what its new value is.
If we have multiple variables, they can each store a different number:
Set IntOne = 7
Set IntTwo = IntOne
Set IntOne = 5
Display to (All Players) the text: String(IntOne)
Display to (All Players) the text: String(IntTwo)
Displays 5 and then 7. The fact that we changed the first variable has no effect on the second variable; each of them stores its own number in its own box.
Army Logsitics
Now suppose we have some unit variables, instead of integer variables. Suppose we create a footman and assign it to a variable:
Unit - Create 1 Footman for Player 1 (Red) at (Center of (Playable map area)) facing Default building facing degrees
Set UnitOne = (Last created unit)
What's in our variable? Based on how integer variables work, you might think that our "UnitOne" variable has a box which now contains our footman:
However, what happens if we assign the same value to another variable?
Set UnitTwo = UnitOne
Now what's the situation? Are there two footmen in two boxes, one for each variable?
While player 1 is pleased with the mysterious increase in his forces, doesn't it seem kind of odd that assigning a variable would create another unit on the map? If it doesn't do that, what then? A unit in some abstract space, off the map?
No, there is still only one footman, even though we have 2 variables now. We can easily test this by making some change to the footman--for example, killing him.
Kill UnitOne
Even though our trigger only said to kill UnitOne, UnitTwo is also dead, as can easily be confirmed by asking the trigger to determine whether UnitTwo is alive. We say that these two variables are "aliases"--two different names for the same thing.
But if that's the case, what are in the variable "boxes"?
The boxes don't actually contain the unit--instead, they contain information that identifies the unit, and lets us easily find it.
When we assign the value of UnitOne to UnitTwo, we aren't creating a duplicate of the footman, we're just creating a duplicate of this information that lets us find it. We now have two different ways to find the same unit.
This type of variable has different names in computer programming; it's often called a "pointer" or a "reference," but in WarCraft 3, the usual name is a "handle," because the variable doesn't contain the unit, it just provides us with a way to "get a hold of" the unit; a way to manipulate it (the things attached to handles are usually called "objects"). Most variable types in WarCraft 3 are handles. This is different from simple types, like integers, described above, where the variables actually contain the information they represent. Those simpler types are generally called "primitives," and types that act like that in WarCraft 3 include integers, reals, and booleans.
We can have as many unit variables as we want, all set to the same unit, and it's still only one unit. Conversely, we can have units with no variables at all set to them...
Set UnitOne = No Unit
Set UnitTwo = No Unit
...and the unit can still run around and fight on the map, even though we no longer have a handle for it.
Since we no longer have any variables assigned to this footman, we have no convenient way to access or manipulate it--and so, for example, we couldn't easily kill it, or remove it from the game. However, the game still keeps track of it for us--after all, player 1 would be rather annoyed if his footman suddenly disappeared--so it can still run around, fight, and so forth, and there are certain ways that our triggers could get a new handle for this unit (such as through an event response).
Large-Scale Army Logsitics
To review: integers, reals, and booleans are "primitive" types; the variable actually contains the information it represents. Units are "handles," which just provide a way to get access to information. Let's set up some units and unit variables as a background:
Unit - Create 1 Footman for Player 1 (Red) at (Somewhere) facing Default building facing degrees
Set UnitOne = (Last created unit)
Unit - Create 1 Rifleman for Player 1 (Red) at (Somewhere) facing Default building facing degrees
Set UnitTwo = (Last created unit)
Unit - Create 1 Paladin for Player 1 (Red) at (Somewhere) facing Default building facing degrees
Set HeroOne = (Last created unit)
How about unit groups? Unit group variables are also handles, like units--the actual unit-group isn't stored inside the variable, the variable just tells us where to find it. For purposes of this discussion, we don't need to know exactly how a unit group works--just think of it like a special, expandable box that can store lots of unit handles at once.
Add UnitOne to GroupOne
Add UnitTwo to GroupOne
Add HeroOne to GroupOne
In order to get a unit, you need to use the "create unit" action (or let a player train, build, or buy one). When you create a unit group variable in the variable manager, the game automatically creates an empty unit group and puts a "handle" to that group into your variable. So, if we have a variable called GroupTwo, even though we haven't done anything with it yet, it points to an empty unit group.
What happens if we set GroupTwo to the value of GroupOne?
Set GroupTwo = GroupOne
The "set variable" action always changes the variable's raw value (the thing in the box), so we're not creating or changing any actual groups, we're just changing where the GroupTwo handle points.
We've made GroupOne and GroupTwo into aliases--any units that we add or remove from GroupOne also get added or removed from GroupTwo, because they're really the same group--just two names for the same object.
But what about the unit group that GroupTwo used to point to? Just like we can have a unit even when there are no variables pointing to it, we can still have a unit group, too. The empty group is still there, we just don't have any way to access it. Unlike a unit, which we can still get a handle to in various ways (like event responses, or "all units in playable map area"), once we no longer have a variable pointing to a unit group, it is lost forever.
A unit group doesn't take up any space on the playable map, like a footman does, but as long as it exists, it still takes up space in memory. Since it's taking up space, but there's no way for us to find it in order to use it (or delete it), it's just dead space--we can't ever get that memory back, or put it to any good use (until the map ends). This is called a "memory leak."
Now, there's nothing special or magical about a unit group that's been leaked. It's still a perfectly ordinary unit group; it doesn't take up any more (or less) space than it would take up if we had a variable pointing to it, and it won't do anything on its own. Because of this, just one leaked unit group won't generally be noticed--modern computers have a whole lot of memory, and unit groups are pretty tiny, comparatively. But if you leak enough unit groups (or any other objects), you can start to notice a difference.
So, you may be thinking, "oh, is that all? OK, I just won't use the 'set variable' action on any unit groups, and I'll be fine." However, just like you can create new units with a trigger action (the "create unit") action, there are other ways in triggers to create new unit groups, even if you don't have any variables involved at all. Seem odd? Consider the following trigger action:
Set GroupTwo = Units owned by Player 3 matching ((Matching Unit) Not Equal to (Triggering Unit))
The game doesn't just keep around a unit group of all units owned by player 3 except for the triggering unit for the current trigger event, just in case you need it. So, in order to make GroupTwo point to this group, it has to create a new group for you, which it then automatically fills up with the appropriate units.
That trigger action still involves a variable, but suppose you did something like this instead:
Pick every unit in (Units owned by Player 3 matching ((Matching Unit) Not Equal to (Triggering Unit))) and do actions...
The "units owned by player matching conditions" part of the trigger action is a function that creates a new unit group for you. The function doesn't know what you're going to use the group for--you might assign it to a variable, or loop through all the units, or all manner of things. It doesn't know how long you'll need it, or what you're going to do with it. It just creates the group for you.
Therefore, with this action, we're still creating a new unit group, just like in the previous example, but this time, we never assign a variable to it. With no variables pointing to it, we don't have any way to get a handle on it, so the group becomes a memory leak.
Most types in WarCraft 3 are really handles, so this can happen with all sorts of things--unit groups, player groups, points, floating text, special effects, the list goes on. Even units could be considered "memory leaks" if they outlive their usefulness and there's no convenient way to get a hold of them.
Plugging Leaks
So what can you do?
Well, first of all, consider whether you really care. A modern personal computer probably has somewhere around a gigabyte of memory--that's 1,073,741,824 bytes. Most objects only take up a handful of bytes. Even if they take up hundreds of bytes, that doesn't add up very fast. Unless you're leaking like the Titanic, you can probably play your map for an hour or two without even getting close to running out of memory (it will take a little extra time to clean up the leaked memory when your map is over, but that's it). Even if you run out of memory, your map isn't going to crash--your computer will just start moving things into "virtual memory" (your hard disk), which is slower than main memory, but it still works.
People are frequently concerned about lag resulting from memory leaks, but you probably won't notice significant lag until your computer has to start moving things to virtual memory to free up space. In fact, in some cases, cleaning up your memory leaks can actually make a program run slower, because it takes time to free the memory after it's been allocated, and repeatedly allocating and freeing memory in tiny little pieces can cause memory to become "fragmented," which can make it take even longer to allocate and free memory in the future. Also, fixing memory leaks is dangerous if you don't know what you're doing--if you do something wrong when trying to fix a leak, or if you try to "fix" a leak that isn't really a leak, you can cause crashes and other confusing bugs, and it's usually very difficult to trace the bug backwards to figure out where you did something wrong.
But, sometimes, you do need to care about memory leaks. For example, if you have a trigger that runs every second with a great big loop that leaks a bunch of memory on every iteration, you can notice problems. If you're really confident in your ability to identify and handle leaks, you might decide it's worth the trouble of fixing them just so you don't need to wait so long after your map ends. So here's how to fix a leak:
First, you need to make sure that the object gets assigned to a variable. Otherwise, you never have a handle to it, and it leaks immediately. So, rather than writing a trigger like this:
Pick every unit in (Units owned by Player 3 matching ((Matching Unit) Not Equal to (Triggering Unit))) and do actions...
You'll want to write one more like this:
Set GroupOne = Units owned by Player 3 matching ((Matching Unit) Not Equal to (Triggering Unit))
Pick every unit in GroupOne and do actions...
This way, you get a handle to the new unit group created by the "units owned by player matching conditions" function.
The second step is to "remove" or "destroy" the object when you're done with it. Exactly how you do this depends on the type of object. Some objects (units, special effects, floating text, etc.) have various ways to remove them using trigger actions in the GUI (remove unit, destroy special effect, etc.). Other objects (like unit groups) require you to use custom script, like this:
Custom Script: call DestroyGroup ( udg_GroupOne )
Removing an object frees up the space in memory where it used to exist, so that memory can now be used for something else.
IMPORTANT: Do not destroy the object until you're done with it! Trying to pick all units in GroupOne after you've destroyed it is a bad idea. Once you destroy a variable, do not use that variable again for any reason until you've performed a "set variable" action to give it a new value. The object the handle points to is no longer there, but if another object happens to get put in that same space, and you try to manipulate it with your old handle, you can get really weird bugs.
Sometimes, it's hard to know when a new object is being created. Remember that "primitive" types (integers, reals, booleans) don't actually involve objects, so they never leak. Strings are also treated specially, different from regular handles, and you generally don't need to worry about them. So clearly, a function that gives you a non-handle type will never be giving you a new object--so "arithmetic," "convert integer to string," "current gold of player," and other things like that are always safe.
The "units owned by player" function creates a new object, but the "all players" function does not (there's one special player group of "all players" that the game keeps track of for you). A good rule of thumb is that if the function takes any parameter (that is, if there's some word underlined in blue that you can change--like the player you care about in the "units owned by player" function)--then it probably creates a new object. If it takes no parameters (like "all players"), it might still be creating a new object, but you need to be much more careful. If you're not sure, you can ask your resident triggering guru, but if you just can't figure it out, it's better not to delete it. If you allow an object to leak, you just lose a little memory; if you delete an object that you really needed to keep around (like the "all players" group), you may be spending the next two weeks trying to figure out why your map keeps randomly crashing.
Stopping Leaks Before They Happen
Many times, you can make algorithmic changes in your triggers that will prevent memory leaks from ever being an issue. For example, suppose you need to do something with every hero in your map once per second. You could write a trigger like this:
Pick every unit in (Units in (Playable Map Area) matching ((Matching Unit) is a hero Equal to True) and do actions...
That will create (and leak) a new unit group every second when it runs. It will also be relatively slow, because it needs to hunt down all the heroes every time it runs.
Alternately, you could create a unit group variable called Heroes, and add all heroes to that group when they're first created. Then you can just use:
Pick every unit in Heroes and do actions...
Since you're using the same unit group every time, no new groups are created, and thus no memory leaks. Also, it's a lot faster, because the game doesn't need to find all the heroes every time it runs. This also will let you look at heroes that aren't in the "playable map area" for some reason (for example, if they're dead).
If you only want to do something to some heroes, you can use an "If/Then/Else" with conditions inside the loop to decide how to react to each hero in the group.
Memory Leaks and You
Memory leaks occur when you no longer have a handle for an object that exists in memory, so you can neither use that object, nor delete it. You can prevent memory leaks by deleting objects when you're done with them.
There is nothing magical about leaked objects. They don't start growing tentacles and extra eyes. They just sit there, in some dark corner of memory. The only problem with them is that they take up space, and don't do anything good for you.
Leaking a little bit of memory is hardly noticeable, but deleting objects that you still need can cause Big Trouble. It's often better to avoid or ignore memory leaks,