26 November, 2006

Trying To Think Small

Progress on the game engine continues slowly. One of the reasons it is progressing a little slower than it otherwise would normally is that I'm trying things differently - I'm trying to do them the Smalltalk way. And, in the process, I'm attempting to assertain whether or not the Smalltalk way is the better way.

The best example of this (so far) is the resource system. A resource would be anything that is read from disk or memory that requires a Direct3D interface object. The two most obvious kinds of resources are textures and fonts. Later this system would also include sound banks, wave banks, render targets, and more.

The initial pass was nothing more than the brute-force C++ method: there's a font object, a texture object, a scene creates them on initialization, frees them when done, and uses them in between. This works fine, until one realizes that multiple scenes probably will want access to the same resources.

Now, for a typical program, having two different objects load the same object individually probably wouldn't be so bad. However, in this case, it's very bad. For a texture, we'd be using double the VRAM (if two scenes each loaded it). Excusing memory, there's an even bigger problem with doing this. State changes in Direct3D are a performance killer. Each one basically cause the GPU to finish doing everything currently sent to it, halt while it changes the state, and then you can continue to send instructions to it. And switching textures is a type of state change. So, having the same texture loaded in memory multiple times can cause unnecessary state changes.

In C++, this would be fixed by simply making textures global in some way. They would either be global variables, or there would be a texture system that manages all the loaded textures. Both are equally good solutions. However, the former method in Smalltalk (using global variables) isn't quite so elegant. So, for my first pass at a more Smalltalk-ish implementation of a resource manager, I decided to do just that, create a GameResourceManager object, that would handle the management of all loaded textures and fonts.

This worked, but it had some definite problems. The first problem is, of course, how are other objects going to gain access to these resources? Well, Smalltalk "tackles" the global variable problems through hash tables. And this certainly is a viable solution. So, trying a LookupTable in the resource manager worked, but not very well. Why not? Primarily because the resource manager didn't know how to load the resources. Each resource could load itself just fine, but then needed to be added to the resource manager. I didn't want some end-programmer using my library to have to "know" and add their resources to a manager. Equally frustrating was that each user of a resource had to accept the fact that it may not be loaded yet. This required lots of code that looked like this to be strewn about the game:


bg := GameEngine current resources at: 'mytexture'
ifAbsentPut: (TextureResource fromFile: 'media/bg.tga').

Obviously every single time I want to get a texture, I don't want to have to know not only it's name, but how to load it as well. Yuck. So, then I toyed with the idea of the resource manager knowing how to load the textures and automatically adding them to the LookupTable. This definitely wasn't the road I wanted to go down. What happens in the future? Should the resource manager know how to load every kind of resource the game ever needs? No, that would be horrible.

Over the next day, I thought about it some more. At one point I decided to stop and ask myself, "okay - what does Smalltalk do best?" The answer to that is obvious: objects. So, how could I use objects to solve this problem? Does Dolphin have anything that's a similar problem I could look at for help? As I started thinking more, it came to me that there was a similar problem (and one that I'd been making use of this whole time): dynamic libraries.

So, the proposed solution was this: what if there was no resource manager (or, what if Smalltalk itself was the resource manager for me)? What if every resource was really just a subclass? Since classes are objects in Smalltalk, I could make each texture, each font, each sound, etc, an actual class in my game - a singleton of sorts, that other objects could just get, and each would know how to load itself if needed.

The first step was to create the GameResource object. This would be a simple implementation of a resource singleton, and have instance methods for loading, unloading, and restoring (when Direct3D loses the device context). Next, I needed to make my font and texture objects just a subclass of GameResource. These would be slightly more specialized, actually knowing how to load and unload themselves, and how to render, etc. All that was needed now was to make a few class methods "describe" the resource. For a font, this was the #face, #height, #bold, and #italic methods. For a texture, this was the #fileName. And for future resources, there would be equally appropriate methods.

Now to test the idea and see how well it is in practice (note: the above changes took all of 20 minutes - another win for the Dolphin interface and Smalltalk in general).

In the little sample game that I'm working with, I just subclassed TextureResource and created BackgroundTexture. Overwrote the #fileName class method to return the correct filename, and that's it. Now anytime I want that texture for use, it's just:

bg := BackgroundTexture current.

If it isn't ready, it's automatically read from disk and created for me. If that's already been done, then I get a reference to it. What's even better, is that all the resources for the entire game can be listed, restored, loaded, or unloaded in a single line of code. For example, when the GameView is closed and DirectX shutdown, we need to release all the resources:

GameResource allSubclasses do: [:each | each unload].

Now, for a full-blown, giant game this does have drawbacks.

This method can unpredictably access the disk, and loading all the resources would hit the disk a lot, as opposed to reading a single, giant, compressed file that contained all our resources, uncompressing into memory, and then loading from there. However, I see that as trivial for two reasons: I'm not making giant games with this engine, and if I wanted to, I'm sure I could easily create a #loadFromMemory method and a #loadFromDisk method to specify how I would like the resources loaded.

Also, if a game were to have a lot of resources, there would be an awful lot of classes in the hierarchy tree. I still haven't decided if this is a big problem or not. Certainly for a very large game with thousands of resources, it would definitely be a problem. But I see this engine being used for games with an order of magnitude or two fewer resources.

On the flip side, one very nice advantage is being able to inspect every single resource in the game. I can check to see if it's loaded, how many objects are using it, etc. Later on, I could even add DirectX debug views so I could actually view the resources outside of the game while it's running. And that is very appealing.

There are still a few changes I'm considering, but they are pretty minor. Overall, I like the direction this is going, and progress continues forward. Next up will be character maps and hopefully a screenshot of the game running.

However, I'm curious to know if I'm walking on a slippery slope. Perhaps there are some known pitfalls to my current approach that someone can point out to me? Or perhaps there is a better way of creating the resource manager that I didn't see. Let me know what you think!