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!
7 comments:
Brand new to Smalltalk and not much of a programmer in the first place, but I've always been interested in game design, and recently decided to push myself into it seriously. Started by browsing the language scene, and Smalltalk looks very attractive in a lot of ways, but I'm wary of putting too much effort into learning a fringe language without seeing proof that it's fit for the purpose. Your blog seems to be aiming for exactly that proof, so I think I'll just follow you for a while!
I can't help thinking there must be a more elegant solution than making each resource its own class. A class per resource type of course, but at that point don't instances make more sense from a performance/scalability standpoint, not to mention language philosophy? After all, it's different data of the same type, not a different type of data.
(Disclaimer: I'm still just scratching the surface, so my apologies if what follows is blatantly ignorant.) Could you use a middle ground, using class variables and private constructors to handle the resource list? For instance, in your TextureResource class, keep a LookupTable of loaded resources; let's call it theResourceList. Have a private constructor which loads a resource and returns an instance pointing to it, which is also added to the LookupTable.
Meanwhile, the end programmer sees none of this. He just does:
TextureResource get: 'media/bg.tga'.
The #get: method checks the theResourceList and either returns the existing instance if present, or constructs a new one and returns that.
With appropriate methods in the sublcasses, you can still unload everything with a single call to GameResource, and if you need to manage all the resources from a centralized list, you can just collect it in a similar manner.
As I said, I'm a rank newbie; I'm curious whether there are any obvious holes in this design?
tejón,
Your idea is exactly what I wanted to do initially. And, like you, I still wonder if a class per resource is just wrong. Inside it feels dirty to do, and I know for a fact that it doesn't scale well. Once I start getting into the hundreds of textures, it will become a real headache to maintain.
Something to keep in mind is that there are no constructors in Smalltalk. Classes are objects, and have their own methods (the class methods). Calling #new on a class isn't like the new operator in C++; see my first blog post.
I've been using this method of resource management now for a week or so working on the proof-of-concept game (a little Asteroids clone). The current method of doing the resource management is a dream and working very well.
That said, I'm still trying to come up with some "middle ground" solution as you put it. But, like I said in my post, I want a solution that doesn't require lots of code knowing how to load the resource and I don't want the lower-level code making game-level decisions about how to load the resource (eg, from files instead of memory).
I'm very open to all ideas, though. Thanks for the post!
I was using "constructor" in the sense of generating a fresh instance, but I think I see your point -- class or instance, each resource is an object either way.
Going through the Dolphin tutorials, they brushed across the Model-View-Presenter paradigm, which appears to be the "right way" to do things in Smalltalk. I'm not in front of the class browser right now, but I think the class they used in the example was ListModel? The tutorial was a money-management system with a list of accounts, which handled themselves and reported updates to the container list. It seems to be a similar task, could that shed any light?
Hi Guys,
I'm new to smalltalk (and Dolphin) as well. But perhaps the mundane task of creating so many classes can be killed by using the Compiler class and somehow creating those other classes on the fly. Making them subclasses of another class so that you can track them and release the resources as needed. Perhaps the idea is too far out there.. but it just might work.
Cheers,
Ben
"Should the resource manager know how to load every kind of resource the game ever needs? No, that would be horrible."
Why not just use your original technique, but pass allocation through a class method on your resource type, so the resource manager doesn't have to know jack?
I assume the end programmer would at least know what type of resource he wants, and probably its filename. (If you want to hide that, pass it through a dictionary -- at some point there will be an absolute list of what's available and how to refer to it.)
bg := Texture with: 'media/bg.tga'.
Texture>>with: aHandle
^GameEngine current resources at: aHandle
ifAbsentPut: (Texture fromFile: aHandle)
tejon,
That's actually a really good idea. I'll have to give that one a whirl and see what it ends up like.
Thanks!
Extending that, it just occurred to me: you can use 'self' in a class method, can't you? So that code could be placed in a parent GenericResource class by replacing 'Texture fromFile:' with 'self fromFile:', saving even more work.
Musing on the best use of objects: OOP, especially so pure as Smalltalk's, seems to be best suited for emergent behavior rather than directed. I wonder if it would make sense to make the game resources completely autonomous and self-managing, with the physical resources being abstracted in the same way as you would do a game world. For example, your video RAM could report how much of itself is free, and your textures could keep track of their own current necessity and prior frequency of use, and cooperatively load/unload themselves accordingly.
Post a Comment