Beginning Game Audio Programming [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Beginning Game Audio Programming [Electronic resources] - نسخه متنی

Mason McCuskey

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
توضیحات
افزودن یادداشت جدید


AUDIO ENGINE ERROR HANDLING

One of the most important traits of any engine is its robustness, or resistance to crashing under extreme circumstances. Of course, it's always important to make sure your code doesn't crash, but when you're dealing with engines, it's especially important. Even if your engine isn't being sold for a half-million dollars and is only getting used by you, I guarantee that you'll forget the intricacies of it by the time it's finished and you start developing the game that uses it. It's no fun to have to re-learn how an API or engine works halfway into writing the actual game. So, spend the time up front, and put in some solid error handling.

One way to do that is through the use of C++ exception handling. If you haven't heard of exception handling before, you should stop now and learn what it is before you continue. I'm going to assume from this point forward that you understand C++'s try, throw, and catch statements. If these are new to you, use one of the links I've provided on the CD to brush up.


Should You Use Exception Handling?


I know a lot of programmers, and all of them have varying opinions on exception handling. Some of them insist that it's the only way to write fail-safe code. Others insist exception handling is the work of the devil, right up there with gotos and premature optimization. The supporters say that exception handling makes your code more robust, and easier to maintain. The pundits assert that it makes for sloppy, bloated code and lazy programming.

When it comes to exception handling, I fall somewhere in the middle of the two camps, leaning slightly toward the "exception handling rocks!" camp.


Rules to Live by When Using Exceptions


I think that exception handling can make your code easier to read and easier to write, provided you follow a few cardinal rules.



    If you write the throw, you write the catch.

    Throw when something unexpected happens, not when something bad (but plausible) happens.

    Catch errors as soon as possible, but not any sooner.


The rules are explained in the following sections.

Cardinal Rule Number One


If you write the throw, you write the catch.

In other words, whenever you throw an error, you must also decide what catches the error and what it does.

Any thrown error that your program doesn't catch is an "unhandled exception," and brings your program to a screeching halt with an error dialog similar to Figure 3.2. I've seen many good programmers errantly write code that crashes just because a relatively harmless exception "escapes" without being caught by anything.


Figure 3.2: What happens when you fail to catch an exception.

Also, this rule is particularly important when you're dealing with a large code base that doesn't use exceptions.

Cardinal Rule Number Two


Throw when something unexpected happens, not when something bad (but plausible) happens.

The Cheshire Cat version of this rule goes like this: Exception handling handles exceptional situations. It doesn't handle bad things that are considered normal.

The classic example of this rule is when someone is trying to log in to a server. There are two "normal" paths of execution: either the server lets them in, or the server denies them access.

These two code paths should be handled without exceptions (for example, by returning true if access is granted or false if not).

On the other hand, an exceptional situation might be that the server returned garbage: It didn't say yes; it didn't say no—it spoke some gobbledy gook that makes no sense whatsoever. In this case, throwing an exception would be appropriate, because this situation is truly bizarre—your user has problems much bigger than a mistyped password.

Other exceptional situations could involve the client being unable to talk to the server, running out of memory, or being unable to send data through the network.

Why follow this rule? For starters, exception handling is insanely slow. The people who wrote your C++ compiler assumed that you wouldn't be throwing exceptions unless something was very wrong, in which case, hey, who cares if it runs slow, at least it didn't just crash. To be honest, I don't know if they actually thought that, but for whatever reason, throwing an exception is really slow.

Another reason (just in case you needed another!) is that heavy use of exceptions makes code difficult to read.

Okay, the horse died a couple paragraphs ago, so I'll stop now and move on to the next rule.

Cardinal Rule Number Three


Catch errors as soon as possible, but not any sooner.

Obviously, you don't want to catch an error before you can do anything about it. The code that catches the error should be high enough up the chain of command so that it can decide what to do when something goes wrong.

Here's an example to illustrate the rule (just one this time, I promise!). Let's say you're writing some code to read in a wave file and store it in a CSound class. You have several functions, set up as shown in Figure 3.3.


Figure 3.3: Hypothetical call stack for a game, showing where an error might be thrown and caught.

At the top is your C++ program's main() function. The main() function calls LoadGameResources, a big function responsible for loading all of the art, music, and levels for the game. LoadGameResources, in turn, calls LoadSoundFile, which in turn calls LoadWaveFile for loading waves, and LoadMP3File for loading MP3s.

Now, suppose that LoadWaveFile performs an fopen command to open the wave file from disk, and fopen fails (suppose the file that it wants to open isn't actually on the drive). You are using exceptions, so you decide that if fopen fails, you should throw an error.

Cardinal Rule Number One says if you write the throw, you write the catch, so now you have to decide where the catch goes. You have several places you could catch that error:



    In the LoadWaveFile function

    In the LoadSoundFile function

    In the LoadGameResources function

    All the way up in the main function

    Start by thinking about LoadWaveFile. What could this function do in its catch handler? Unfortunately, it couldn't do much. LoadWaveFile doesn't really know why the file's being loaded, so it can't really decide what to do if something goes wrong. As a counterexample, say that LoadWaveFile catches the error and bails out by calling exit(-1). For a game this might be okay, but suppose you wanted to use LoadWaveFile in a sound-editing application. In this context, it wouldn't make much sense for your application to immediately exit and lose all of the user's work just because a file it was asked to load couldn't be found. It would be much better to just pop a "File not found" message and let the user continue using the application. The point is that LoadWaveFile is just a "slave function," knowing very little about the context of the program.

    So, moving up the call stack, how about LoadSoundFile? LoadSoundFile is closer, but still not good enough. It suffers from the same lack of context that LoadWaveFile does.

    Next up is the LoadGameResources function. This is the first point at which you have enough context to figure out what to do. LoadGameResources knows that if it can't get those bytes off the drive and into RAM, the game is not going to work as designed. In this function, you could write code to pop a message box asking your player to re-install the game, or you could attempt to recover from the situation (say, by seeing if the game CD is in the drive, and if so, reading the rest of the resources off of it).

    Finally, what about catching the error in main? You certainly could handle the error here, but I would argue that the main function is too high up the call stack. To some extent it's a judgment call; it certainly wouldn't be the end of the world if you caught the error here, but to me, main handling this is like Picard pushing Geordi out of the engine room and fixing the warp core himself. The main function shouldn't really need to think about the inner workings of resource loading. Now, maybe if LoadGameResources caught an error, tried to recover from it, but still had problems, main could catch its re-thrown error. That's the code equivalent of a chief operating officer telling the CEO of their company, "Look, we had this major problem, and I tried to deal with it, but this is too big for me, and I need your advice in order to go forward."

    Knowing the best place to catch an error is one of those skills that you can only learn through experience. There are guidelines, but nothing that works in all situations. Generally, it's much worse to catch an error "too low" (too close to where it was thrown) than "too high," especially for game programming. In game programming, errors are frequently show-stoppers, so catching them high up is reasonable. Even if all you do is catch everything in main, pop a message box saying "this game crashed," and clean up what you can, you're doing better than some of the professionally-made games I've played, which frequently just crash with an access violation and leave my system in a complete mess.

    Also at play is the law of diminishing returns—frequently, doing "exactly the right thing" with thrown errors requires lots of code and doesn't have a lot of benefit (unless you're making lifesupport software or shuttle guidance systems). For game programming, trust your gut, and don't worry about getting it exactly right.

/ 127