Best Error Handling in Games

Started by
19 comments, last by SiCrane 18 years, 6 months ago
After a lot of searching, I was unable to come up with a definitive source for what is generally considered the best way to perform error handling in games. Due to varying overhead by different methods, I was curious as to what companies generally tend to use for error handling in games (for a C++ environment). I found articles saying use "try" blocks, some said use "asserts" since the overhead won't exist in the release, and others said to enumerate return values and use them for error handling. It seems from my reading that return value error handling would be sufficient and incur little overhead, but it also seems like I may be missing something valuable by ignoring try blocks and/or asserts. Thanks in advance for any info/opinions left on the subject.
Advertisement
Ok. First up, assertions. Assertions are not an error handling method. Assertions are use to assert that something should be true. That is to say, when you have an assertion you are making a statement that if that statement is not true then you have a severe programmer mistake rather than an error condition. They are a debugging tool, but not an error handling method. We recently had quite a thread about assertions which may be interesting. Or hopelessly confusing. But confusion is often the first step to enlightenment.

Error handling refers to dealing with situations while not normal or desired, nonetheless may happen during program execution. For example, running out of memory is an error condition. The C++ standard library throws a std::bad_alloc exception when that happens. So like it or not, you're most likely going to have to deal with exceptions if you're program is in C++. Exceptions, on the whole, are the prefered method of dealing with error conditions in C++.

Exception handling incurs a small cost in terms of increased executable size, and has some negative effect on cache misses for code fetching, but otherwise the runtime cost is minor as long as exceptions are not thrown. On the other hand, using exception handling removes error handling logic from the main flow of execution which is a maintenance and code clarity gain.

Using return values to signal function return status is useful when, a) unsuccessful execution of the function is not an actual error condition (such as attempting to establish a network connection over the internet) or b) when the code calling the function is not C++ code. Exceptions play very poorly with code from other languages, or even code compiled by other compilers, so they should not be allowed to propogate past module boundaries. Or even the same compiler with different flags.

Exceptions also have the advantage that you can't silently ignore an exception. If you have an exception thrown, you either have to deal with it or your program goes down. Exceptions also tend to be more friendly to the branch prediction mechanisms on modern processors.
I only use assertions for things that should never ever ever happen (i.e. the corpse you are carrying around suddenly becomes NULL). These are related to my programmer idiocy and are disabled in the final release build.

I would consider exceptions. And make sure you log EVERYTHING to a text file (if you can)! Nothing is worse than asking an end-user what the exact error message was and having them say "Uhhh, fatal exception, bunch of numbers, and an OK button. I clicked it"
I'd say use exceptions unless there is a reason not to - such as the ones cited by sicrane.

Just use exceptions.

Errors will happen in a production environment, for example:
- People try to run the game on unsupported hardware (or duff video driver)
- Data files going missing or becoming corrupted
- The disc becoming full while trying to save a game
- Etc

So you can't just compile them out and forget about them. The action required depends on what the game is doing at the time.

Exceptions during game startup or in the game itself will likely be caused by things that cannot be fixed, so they can fall out to a standard handler which can do some logging or something, clean up and quit.

Exceptions in some other places might happen for legitimate reasons - for example a network error or game save problem - in which case you'll want to give the user a suitable message rather than just bombing out.

Mark
Logging systems are a necessity, and I believe there was an article on an XML logging system. Yep, here it is. Anyway, another good idea in error handling is allowing the program to continue running even if everything didn't go as planned. (there's probably a word for that...) An example would be this texture manager system I wrote. When it could not find the correct texture file to load, it would just load a default texture file and log the error. That way, and incorrect file name or missing file did not have to crash my program. Futhermore, I would still be able to see that an error occured that needs to be fixed, and with my log file I could usually find that error easily. Anyway, the point is that not every error has to crash the system, the system can be made flexible to go around errors. Personally I think that only out memory errors, bad pointers, and incompatible hardware errors should force an application to shut down completely.
Programming since 1995.
I made a file manager in my engine, and when the program starts up it creates a log file and logs important things to that file. I have two modes of writing to it, one which will only write to it in debug mode, so I can write out to my file with useful debugging info. I wrap pretty much the whole main functions in a try/catch block and when an exception occurs I write it out to the log file, shutdown the engine, and then pop a message box up to let the user know what happened.
First consider which are possible sources for errors.

syntax errors:
I'm sure your compiler will tell you.

semantic errors:
For instance your algorithm will not behave as expected. In most cases the error will show up very early and will be reproduceable. This makes it easy to find.

runtime errors:
If data enters your game unchecked it may produce crashes of your game. So always make sure you check the validity of data before using it.
Other runtime errors occur if you run out of memory or things like that. If this happens at the enduser's computer, be sure that you logged everything that will help you to find the reason for the crash.
Another option would be to provide a fallback mechanism, i.e. the wrong CD of your game was inserted, so show a window to let the user change the disc.

synchronisation errors:
These errors are hard to track because they are not reproducible. When using multiple threads you need to identify and eliminate every possible race condition, or you will end up synchronisation errors. Using a monitor synchronisation and then try to identify parts of your application that will not produce race conditions (and can unlock the monitor) will help you here.


The next thing is error handling. Here you have several options.

UNIX-style error handling:
For instance a file handle is always a positive number, an error value a negative one. A memory location is returned or null. Sometimes however the return value will use all possible combinations, which led to different styles. In the end you will have to look up the functions in the manual very often.

Windows-style error handling:
Every function returns an error code. If a function wants to return a value, it is an out-parameter (i.e. pointer or reference). This makes it easy to remember, which in turn prevents errors by using the wrong interpretation of the error code.

Exception handling:
The drawback of using error codes is, that you must pass up the error to the point where it can be handled. Exceptions do this by design. However in C++ exceptions are very expensive, while other languages like Java or D provide a very cheap exception handling mechanism.

error handling with goto:
Although goto is not recommended for common usage, you will find lots of gotos if you take a look into the linux source code (especially driver sources). Since C does not provide all the features of C++, goto (for forward jumps only) provides a common error handling mechanism. Nevertheless, don't use gotos until you really need them.


Another important thing to remember is:
Handle an error at the place where it occurs if possible, since here you have all information to do that. If you handle it somewhere else, you will have to pass that information that is required for handling the error, which is unnecessary overhead.
Also try to be conservative. Check arguments that enter your function. Assertions are a nice way to do that. They basically come for free, as they will not be found in the release code. However they cannot handle undetermistic or runtime-errors that may happen at the enduser's computer.
Quote:Original post by nmi
The drawback of using error codes is, that you must pass up the error to the point where it can be handled. Exceptions do this by design. However in C++ exceptions are very expensive, while other languages like Java or D provide a very cheap exception handling mechanism.

Modern C++ compilers implement exceptions in such a way that they are very cheap as long as they are not thrown frequently. And if they are being thrown frequently they aren't really exceptional situations.
This thread is close to becoming a monster like that 500+ post thread on java.sun forums about try catch finally. Of course the posters generally agreed, however they did it in a way that wording of an one poster, looked like complete nonsense to the other poster, even if they ment in effect nearly same thing. http://forum.java.sun.com/thread.jsp?forum=31&thread=252665
However it seems it was deleted for abuse. (I think Abuse posted here, but... It was best explanation of why use try catch finally, and they explained it in 3 EXTREMELY detailed ways with examples... it was just partially effective however.)

Exceptions in games are large topic. Especially because there is no single answer. Different situations, and types of games needs different handling, often in way that would be considered outrageous in banking, or other normal software. Like pregenerating an exception for faster throwing of such exception, or nerfing an exception by disalowing a stack trace generation. If you expect 12000 exception per second, and there is no other "don't push deadline later" way, you need to do something to confortable live with them. ^_^
Sometimes you need to change an exception handling between two versions of a game.

Best way how to perform an error handling in games is to make them stable.
Asserts are bad, because they are not a solution, they are just a helper that acts in C++ test builds. I consider unit tests as a better alternative, because thay are forcing you to make your code working in some encapsulated units.

Of course, there are some catches like: No error handling would help you when you encounter bad hardware driver. It could give you completely misleading behaviour, or black screen of death.




BTW when I searched for that try catch finally thread I found this pearl.
Quote:> Why can't we get the forum moderators to be this
> thorough with homework questions that ask for full
> code answers?

Because they're Java-related... :p

Anyway, I was just telling _nasch in the Canadians Thread that its relatively common in South America or Africa that youngsters try to escape poverty by climbing the well of a plane going to a blessed country. And the cost for such a motion detector...

The above mentioned thread was significantly worse, but very educational.
and something else - if case of resources (ie missing textures/models/sounds)
have a just return a fallback resource (texture with big red cross or sth,
big 3d box, "beep"-sound)...

Better to do that then return NULL and have crashes at some places
where you forgot the check the return type.

This topic is closed to new replies.

Advertisement