Sign in to follow this  
Antrim

Best Error Handling in Games

Recommended Posts

Antrim    122
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.

Share this post


Link to post
Share on other sites
SiCrane    11839
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.

Share this post


Link to post
Share on other sites
Ravuya    135
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"

Share this post


Link to post
Share on other sites
markr    1692
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

Share this post


Link to post
Share on other sites
T1Oracle    100
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.

Share this post


Link to post
Share on other sites
njpaul    367
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.

Share this post


Link to post
Share on other sites
nmi    978
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.

Share this post


Link to post
Share on other sites
SiCrane    11839
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.

Share this post


Link to post
Share on other sites
Raghar    96
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.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
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.

Share this post


Link to post
Share on other sites
HellRiZZer    308
Yes, it is a good idea to have a logger class that is avaliable to even the lowest classes in the hierarchy, so that they can report the errors. For that, I have an error handler class that any other class can announce their errors to. Basically, it has the logger and the additional functionality to somehow notify end user of the errors.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
When I was in college we used the Borland CPP compiler and I have stuck with that company ever since. However, when running/debuggin etc a program using the Borland CPP Builder 5 IDE, everytime a exception is thrown, the program crashes and reverts back to the IDE, even though the program does handle the exception. With that problem, I tend to stay away from throwing exceptions as much as I can since it is impossible to trace a program in the IDE after that exception has been thrown.

Share this post


Link to post
Share on other sites
Guest Anonymous Poster   
Guest Anonymous Poster
@AP over me:

In Delphi at least you can tell the debugger which exceptions to ignore so the debugger won't stop your program. I guess you can do the same in BCB.

Share this post


Link to post
Share on other sites
Paradoxish    118
edit-
Quote:
Original post by SiCrane
You should throw exceptions by value and catch by reference. Throwing by reference is bad mojo since it creates lifetime issues on the exception object.


Er, you're right. That's what I get for posting right after I wake up, I guess.

Still, the only overhead should be the object's construction since no copies are being made for the catch blocks.

[Edited by - Paradoxish on October 12, 2005 8:54:05 AM]

Share this post


Link to post
Share on other sites
SiCrane    11839
You should throw exceptions by value and catch by reference. Throwing by reference is bad mojo since it creates lifetime issues on the exception object.

Share this post


Link to post
Share on other sites
d000hg    1199
As far as plain C++ goes, exceptions would be the language's answer to error-handling. And it is a pretty good approach to take.

However the game company which is about to offfer me a job ban exception-handling in their code because they claim that on consoles, exception handling is implemented extremely badly and it's slow. I'm not sure if this is true or they are just doing it like that because once upon a time they were told it...?

Share this post


Link to post
Share on other sites
SiCrane    11839
From what I understand, that's true for the PS2, but not really true on the XBox. Second hand information here, your milage may vary.

Share this post


Link to post
Share on other sites
SiCrane    11839
Quote:
Original post by Paradoxish
Still, the only overhead should be the object's construction since no copies are being made for the catch blocks.


No, throwing exceptions is a fairly expensive operation, and has more overhead than just construction of the exception, especially if you use asynchronous exception handling.

Share this post


Link to post
Share on other sites
Basiror    241
I may add one thing I read in the "99 cpp goatchas" book

Don t check for successful memory allocation with new
Why?

New should throw a std::bad_alloc exception in the latest version of the stl
and thus will never reach the position when you check of NULL and do your own error handling and in 99% of the cases that new failes you won t be able to succeed anymore

They suggested to catch a bad_alloc exception somewhere at the top of the program so you can stop it and save data to avoid loss of information and work
however this can be ignored in FPS games, on strategy games however you should dea with it, nothing is more annoying than playing hours and hours on a multiplayer match and suddenly it crashes and all the game stats are lost :)

Share this post


Link to post
Share on other sites
SiCrane    11839
That isn't completely accurate. The nothrow version of new can return a null pointer. And it's not the latest version of the STL, the behaviour of operator new is defined by the C++ Standard and is not linked to STL versions (Not that the STL really has versions). Also, the throwing behvaior of the standard operator new is not new to the latest version of the C++ Standard (ISO/IEC 14882:2003), being defined in the first version (ISO/IEC 14882:1998).

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this