[color="#4B0082"] Be sure to read the license agreement before using the code.[/color]
[spoiler][font="Courier New"]"Code On The Cob"
Source Code License Agreement
Copyright (c) 1998 Chris Hargrove
The licenser, Chris Hargrove, is herein referred to as the "licenser".
This license agreement, the source code provided, the executable(s)
compilable thereof, and all other contents of the distributable archive
including the archive itself are herein referred to as "covered" by this
Reading, compiling, running, or otherwise using any item covered by this
license constitutes automatic acceptance of the rules described in this
license, as precedes and follows in the remainder of this file.
All items covered by this license agreement are the sole property of the
licenser, and are licensed as-is to the public for educational use only.
No items covered by this license may be distributed in any archive other
than that which is originally provided, and said archive may only be
distributed via mediums that require no cost for retrieval of said archive
other than those required by the medium itself and its direct providers
thereof. None of the items covered may be modified or duplicated in any
form, with the sole exception of modifications and duplications made by
licensees for educational use only. Said modifications and duplications
may only be made to source code covered by this license, and all said
modifications and duplications become the immediate and sole property of the
licenser. The licenser is not responsible for any damages, direct or
indirect, that may result from usage, proper or improper, of items covered
under this license.
Any violation of the above rules must be authorized via express written
consent of the licenser, or it may be punishable by civil legal action.
THE LICENSE PARAPHRASED:
I'm writing this code so you can read it and learn from it. If you
understand it and find some bits and pieces useful as-is, feel free to make
use of them. But don't go taking credit for that which is not yours, and
don't go trying to make a buck off of my effort directly without my
permission. I'm doing this for your personal growth, not for your wallet
or your ego. In general, don't screw me and I won't screw you. The spirit
of sharing information is why I volunteer my already limited time to write
this series. That same spirit is behind a lot of what I and many others have
learned, and what we're all still learning to this day. This license is
basically my way of saying that I don't want some random guy out there to
ruin it for myself and everyone else. Be ethical.
"To be thrown upon one's own resources, is to be cast into the very lap of fortune" - Benjamin Franklin
Happy returns! This week's article is going to be a bit of a short one, but the topic is nonetheless important to consider in any game project: resource management.
I've probably mentioned the term "resource management" several times over the past few articles, but never actually explained what I meant by it. In a nutshell, a resource management subsystem is a blend of memory and file handling that helps you load and use resource data files (like bitmaps, sounds, models, textures, etc) in a more carefree manner. You tell the resource manager what resources you might need, and it makes sure they're there when you need them. Kind of like a "file butler".
Resources these days take up a lot of space, so you generally can't afford to load every possibly-used resource file ahead of time unless you feel like wasting a ton of memory. For instance, say you're writing a strategy game and one of the units is capable of shooting a certain type of missile, with a certain missile image bitmap. Now when you're playing, that image may never be needed (for example if there are none of those units on the map, or those units are never called upon to shoot, etc). But if that image is ever needed, it needs to be loaded so it can be used. You could try and put in explicit checks for all these scenarios so the resources could be loaded when not present, but that would be pretty redundant and error prone.
What's needed is a more general facility for "on-demand" or "lazy" loading of resources, and that's what our resource manager will handle for us. If we think we might need a resource file, we "register" it to the resource manager, and it gives us a resource ID to represent that resource. Later on, if we actually need that resource, we can use the ID to get the resource data itself (which will be cached in as necessary). If you're familiar with on-demand loading and/or "proxy" design patterns, this stuff shouldn't be anything new (and if you're not, don't worry... it isn't that complicated).
I added two more files to the project, res_man.h and res_man.cpp, which hold the resource manager subsystem. For those of you who are confused, looking at the interface will probably make things a bit clearer. The whole thing really revolves around just two important functions.
[Look at res_man.h]
In addition to only loading a resource when necessary, our resource manager has three different caching types it can consider. The first is a temporary resource, which only lasts until the next frame. This is for resources that you need now but won't care about anytime after. The second is a level resource, which caches out after the current level is over; most in-game resources end up being of this type. The third is a game static resource, for data that should never be cached out like menu backgrounds, font images, system sounds, etc.
If you look at the interface functions, the two real functions of importance are the RES_Register and RES_PtrForId functions. The first is called whenever you want to get an ID for some resource file (a bitmap for example). You tell it the filename, the caching type (temporary, level, or game static), and optional callbacks to control how the resource is loaded and cached out. It then gives you an ID for the resource, which is tied to that filename (if you register the same filename twice, you'll get the same ID). This ID is used in place of wherever one might otherwise need a pointer to that loaded resource file. When a data pointer is actually needed, you call the second function, RES_PtrForId, and it gets it for you.
The benefit here is that until you call RES_PtrForId, the resource exists nowhere in memory so it doesn't take up space. It can also get cached out according to its cache type without hurting anything depending on it, since RES_PtrForId will be more than happy to load the resource back up again.
The implementation for the resource manager in res_man.cpp is pretty small, and shouldn't be difficult to understand. Most of it is built directly above the zone memory allocation routines that were added in the last article, since each cache type has its own zone that it works with to bring in resources. Take a dive into it and see if you can figure out what's going on; there's less than 400 lines of code in the implementation, so you shouldn't have too much trouble.
I kept the code simple and sacrificed efficiency in a couple places, since I want to leave some possible improvements as an exercise to you readers. For example, the routine to find an existing entry based on a name goes through the entry list linearly, and only uses what I call a "lame hash" integer value to speed up rejections. Obviously there are other more efficient data structures that could be used, such as a binary tree, or a real hash table based on string length, checksum or some other hash function, etc. What do you think would be most effective in this case? Experiment with it. Also, the level cache scheme should try and preserve resources that are going to be loaded immediately in the next level, so they're not needlessly cached out. What are some of the ways you could modify the code to do this with a minimum of zone memory fragmentation? Once again, experiment with it. You may end up surprising yourself.
Yup, that's it for this week. I told you it was a short one, didn't I? Hey, consider it a breather. We'll need it, because next time we'll be diving into graphics and DirectDraw!
Until next time,
- Chris"Kiwidog" Hargrove is a programmer at 3D Realms Entertainment working on Duke Nukem Forever.
Code on the Cob is (C) 1998 Chris Hargrove.
Reprinted with permission.