Memory Management
Chapter 7 - SCI Memory management
The dreaded "Out of heap space" message. If you have been making a game long enough, you have probably encountered this issue. The bad news is, this seemingly random message stops your game cold. The good news is, you can probably fix the issue, and make your memory management very predictable.
The SCI Heap
To learn how to fix these problems, you must first understand how the SCI heap works.
The SCI heap is a 64KB section of memory that contains most of the objects needed to run the game. In particular, it contains the instances of all the objects you created, and the actual code for the objects themselves. For example, if you use the Wander motion class in a room, the code that is in Wander.sc will get loaded into the heap. Furthermore, when you actually assign the Wander motion to an actor, an instance of the Wander class is created, and that is also put on the heap. For more information on the SCI Heap, see the SCI Heap section in the SCI Specifications.
64KB is not a lot of memory. The code comprising your game is undoubtedly a lot more than that. Therefore, only a small portion of code can be in memory at the same time. This is why when you leave a room, the code for that room is unloaded (removed from the heap to make room), and the code for the new room is loaded (added to heap, so it can be used).
The heap is one contiguous chunk of memory. When the SCI interpreter needs to create an object on the heap, it looks through it from the beginning until it finds a space that is big enough.
Creating an object in the heap. There is not enough room at the 16K mark, so it must go at the next available spot.
If there is no room available, or if there is not a single contiguous chunk big enough for the object, then you'll get the dreaded "out of heap space"' error, and your game is over.
General strategies for minimizing heap usage
First we'll start with some ways you can minimize the amount of memory you use.
1) Make your scripts smaller. The smaller your scripts are, the less memory they will take up. So if you can, write more compact code. SCI Studio shows the size of your script, so you can see exactly how much room it's taking up.
2) Make the main.sc script smaller. The main script (among several others) is always loaded into memory, and so anything you can do to reduce space there will help. If you have a lot of Said statements and logic in there, those will take up space in every room. Consider putting them in a region class instead.
3) Use text resources for your Print statements. In addition to taking a string, Print can also take two integers: the text resource package, followed by the number of the particular piece of text in that package. It is tedious to maintain a text resource for a particular room, but it will save a significant amount of space. Normally, the strings inside Print statements are counted as part of the code for a script, so they take up space on the heap. If they are text resource instead, then only one resource is loaded into memory at a time. After it has been used, it is thrown away. In the old days when the games lived on floppy disks, it might have been slow loading a text resource every time you wanted to display it. But with today's computers, it won't be an issue.
Here are some things that don't help:
1) reducing the size of views
2) reducing the size of pics
views and pics are loaded into 'hunk' space, not "heap" space. Hunk space is another issue altogether, but making the size of your views and pics smaller won't help the "out of heap space" error.
Heap fragmentation
More than anything, the "out of heap space" error is probably caused by heap fragmentation. Consider the following scenario:
Here we have, on the left, all the static scripts that are always loaded (main.sc, game.sc, etc.). And we are currently in Rm001, so that script is loaded. Rm001 references the Wander motion class. It references a bunch of other classes too (which I have not shown), but for the purposes of this tutorial, I have singled out the Wander class.
Now let's see what happens when the ego leaves the room. If the game unloads the room script, but leaves the Wander script behind (we'll get to why this might happen a little later), we'll have this:
OK, fine. Now, say the ego has moved into room 2. Room 2 happens to have a little more logic associated with it, so it is bigger than Room 1.
Well, we see that even though there is actually enough room in the heap for Rm002, it happens not to be in one contiguous chunk. So in fact, there is no place to put the code for Rm002, and thus you will get the dreaded "out of heap space". SCI doesn't pack down the heap. It doesn't move objects around to free up more space. Objects are loaded and then always left at that particular spot.
Now the question is: why would Wander not be unloaded? Surely Rm001 referenced a bunch of other classes, such as MoveTo, or Act, or Prop. I sort of implied that those got unloaded. Well, it turns out that they won't necessarily unload themselves. During a change in rooms, the game will always unload the room script. Furthermore, it has a list of other script resources that it also unloads. If the script resource that contains the Wander class isn't in that list, it won't be unloaded.
Open the main.sc script, and look for the startRoom method.
startRoom:
(method (startRoom roomNum)
DisposeLoad(
NULL
FILEIO_SCRIPT JUMP_SCRIPT EXTRA_SCRIPT WINDOW_SCRIPT
TIMER_SCRIPT FOLLOW_SCRIPT REV_SCRIPT DCICON_SCRIPT
DOOR_SCRIPT AUTODOOR_SCRIPT
)
// etc. ...
This is a list of script resource numbers (the mapping of those names to actual script numbers is in game.sh). If you add WANDER_SCRIPT to that DisposeLoad list, then the wander script will be unloaded too. If room 2 references the Wander class, it will be OK - it will get loaded as necessary. However, the key point is that it will be loaded after the Rm002 script has been loaded (since Wander it is only loaded when Rm002 makes use of it). The result will look something like this:
And everyone is happy.
It is just a bug in the template game that the Wander script isn't in that list. I think it's the only one that is missing. But the take-home point is: if you ever add a new script resource that is used like this (like some sort of custom motion class, or object of any kind), then you will need to add your script resource number to this list as well. Locale and region scripts do not need to be added -- those are unloaded automatically by the game. But if you made a "FlyTo" motion class that was in its own script resource for example, then it should be added here.
Examining the heap
To determine if your heap is being fragmented, you can add the following code to main.sc (or whatever room you're interested in).
Showing the heap usage when the user types "show memory":
(if(Said('show/memory'))
ShowFree()
FormatPrint(
"Free Heap: %u Bytes\nLargest ptr: %u Bytes\nFreeHunk: %u KBytes\nLargest hunk: %u Bytes"
MemoryInfo(miFREEHEAP)
MemoryInfo(miLARGESTPTR)
(>> MemoryInfo(miFREEHUNK) 6)
MemoryInfo(miLARGESTHUNK)
)
)
The dialog that comes up will show the empty spaces in the heap, and their size (in hexadecimal). If there is more than one big empty spaces, your heap is fragmented in such a way that you are likely to get "out of heap space" errors. It isn't unusual to have a bunch of small empty spaces near the beginning, as long as it is just followed by a single large empty space.
If you suspect heap fragmentation, and you want to look at the heap in more detail, you can hold down both shift keys, and press the "-" key on the numeric keypad while playing the game. This will put you into Sierra's debugger. Press "o" or "O" to get a list of the objects on the heap, and their positions. From this, you can probably determine which are the offending objects, and figure out what you need to do. Or you might find that the heap isn't fragmented, and there is simply too much logic in your room.
< Previous: Chapter 6 - The z-coordinate Next: Chapter 8 - Advanced Use of Control Areas >