Friday, 19 July 2013

Handling Errors

What do you think will happen when you throw an exception anywhere in a MonoBehaviour's callback (e.g. Awake(), Start(), Update(), etc.) and never catch it? Well, nothing much. Your callback will return, so the code following after the exception's origin won't get executed, a message will be printed to the log, and that's it. Your application will keep running, all your MonoBehaviours will keep running, and all of your callbacks, including the one that caused the error, will be called again as normal.

If you are like me and prefer to address serious errors in a timely fashion instead of ignoring them, you'll be soon wondering how to change this strange behaviour and stop the execution of your program on a non-recoverable error. After some research, I've found no standard solution, only three workarounds:
  1. You can wrap every MonoBehaviour callback in a try-catch block and abort on catch.
  2. You can forgo the standard throwing mechanism altogether and call your own routine, something like ThrowException(new Exception()), which will terminate your application. 
  3. You can attach a listener to the log and react if an exception is logged. It will work even if the Player log is turned off in Player settings.
The problem with the solution #1 is that it slows down the performance and is cumbersome to implement and maintain – there are dozens of MonoBehaviour callbacks and more may appear in the future. #2, on the other hand, would be cumbersome to use, since it's essentially an error checking system. It would look like this:

class ErrorHandler extends MonoBehaviour
{
  static function ThrowException(_exception: Exception)
  { 

    if (exception == null) {
      exception = _exception;
    }
  }

  static function HasExceptionHappened(): boolean
  {
    return (exception != null);
  }

  static private var exception: Exception;


  function Update()
  {
    if (exception != null) {
      Debug.LogException(exception);
      exception = null;
      Application.Quit();
    }
  }
}

DoSomething();
if (ErrorHandler.HasExceptionHappened()) return;
DoSomethingElse();
if (ErrorHandler.HasExceptionHappened()) return;


Also, you couldn't force 3rd party scripts to use it anyway. The only sound solution seems to be the #3 (see [1] for more details).



What if you wanted to abort? Application.Quit() is great for exiting under normal conditions, but how do you indicate to the outside world that your application didn't finish peacefully? System.Environment.Exit() doesn't work, neither do System.Environment.ExitCode, System.Diagnostics.Debug or System.Diagnostics.Trace methods. The best bet would be to store the return value into a file or a system registry, where it could be acted upon by an error reporter or whatever running in parallel.

Considering the System.Diagnostics.Debug and System.Diagnostics.Trace don't work, asserts are also not available. And the System.Diagnostics.Contracts.Contract.Assert() is too new to be supported. It's easy to implement your own basic asserts though:

[Conditional("DEBUG")]
static public void Assert(bool _condition, string _message = "")
{
  if (! _condition) {
    Debug.LogError("Assertion failed: " + _message);
    Application.Quit();
  }
}


Notice the “DEBUG” constant. It is not set by default, and in Unity you cannot use #define to make pre-processor defines. They are simply ignored. However, you can specify them in “Edit → Project Settings → Player” under “Scripting Define Symbols” for each platform. It is a little clumsy to write and delete them by hand, but the process could be possibly automated by a script (see PlayerSettings.SetScriptingDefineSymbolsForGroup).

No comments:

Post a Comment