Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Advanced Error Handling

Advanced Error Handling
Version: 1.2

Introduction

Ever run into a stupid error? Maybe when beta-testing, or even in a final product? Lost your save file? That's what this is for. Advanced Error Checking enables the game to skip small errors per-frame, for when the scripter failed to check for the errors, or when something really weird happens that nobody accounted for. It also writes all errors to a log file, which makes it much easier for testers to explain their errors, and for scripters to fix them (it also writes the entire backtrace of the error).

The error skipping only occurs when not running a test play from within RMXP (controlled by the internal variable $DEBUG). During test plays, it won't skip any errors; this is useful for scripters trying to catch their errors since the editor only points to that point in the scripts if the game closes to that error.

This script was inspired by the functionality provided by Zeriab's code snippet, on which this is based. It's really a bit of a small script, based mostly around skipping small errors (those that don't occur every frame) and avoiding nasty unintentional recursion (method Exception.to_s calls the method write_to_log, which calls full_message, which calls message_with_backtrace, which calls an aliased to_s...complicated, eh?).

Screenshots

(this script can't be well described with screenshots)

Script

[rgss]class Exception
  # prevents infinite loops by aliasing the original message
  alias to_string :to_s
  #--------------------------------------------------------------------------
  # * Only called from internal error checking; writes log and adds backtrace
  #--------------------------------------------------------------------------
  def to_s
    write_to_log # Note that the log calls :full_message, which calls to_string
    return message_with_backtrace
  end
  #--------------------------------------------------------------------------
  # * Returns the error message with accompanying backtrace
  #--------------------------------------------------------------------------
  def message_with_backtrace
    return to_string + backtrace_string
  end
  #--------------------------------------------------------------------------
  # * Generate the backtrace string from a range of its values
  #--------------------------------------------------------------------------
  def backtrace_string(range = nil)
    return "" if backtrace == nil or backtrace.length <= 0
    range = 0...backtrace.length unless range.is_a?(Range)
    output = "\n\nBacktrace:\n"
    for i in range
      output += backtrace_item(i, true) + "\n"
    end
    return output
  end
  #--------------------------------------------------------------------------
  # * Generate a string from an individual item in the backtrace
  #--------------------------------------------------------------------------
  def backtrace_item(i, show_method = false)
    return "" if backtrace == nil
    method = backtrace.gsub(/Section(\d*):(\d*)/) {}
    output = "'%s' line %d" % [$RGSS_SCRIPTS[$1.to_i][1], $2]
    output += method.sub(':', ' ') if show_method
    return output
  end
  #--------------------------------------------------------------------------
  # * Returns what raising an error displays
  #--------------------------------------------------------------------------
  def full_message
    # Finds script and line number
    error = "Script " + backtrace_item(0) + ": #{self.class} occured.\n\n"
    return error + self.message_with_backtrace
  end
  #--------------------------------------------------------------------------
  # * Write the error to the log file
  #--------------------------------------------------------------------------
  def write_to_log
    return if @written # Only write log once
    file = File.open('ErrorLog.log', File::CREAT|File::APPEND|File::WRONLY)
    file.print Time.now
    file.print "\n--------------------------------------------------------\n"
    file.print full_message
    file.print "\n========================================================\n"
    file.close
    @written = true
  end
  #--------------------------------------------------------------------------
  # * Is this error fatal? (redefine for other exceptions)
  #--------------------------------------------------------------------------
  def fatal?
    return true
  end
end
 
def StandardError.fatal? ; return false ; end
 
class NameError < StandardError
  alias to_string :to_s
  def to_s ;super ; end
end
 
class SystemExit < Exception
  def to_s ; return message_with_backtrace ; end
end
 
class Object
  #--------------------------------------------------------------------------
  # * Advanced error checking for methods - provide method as a symbol
  #--------------------------------------------------------------------------
  def error_wrapper(symbol)
    # Create the record of whether there's been an error last time checked
    if @_error == nil
      @_error = {}
    end
    # If in debug mode, or there was an error last time we ran this method
    if $DEBUG or @_error[symbol]
      # Run the method and, after getting no errors, reset error check
      send symbol
      @_error[symbol] = false
    else # If there was no error last time we ran this method
      begin
        send symbol
      rescue
        # Catch errors and provide feedback; set checker for each-frame errors
        print $!.full_message if $DEBUG or $!.fatal?
        $!.write_to_log
        exit(1) if $!.fatal?
        @_error[symbol] = true
      end
    end
  end
end
[/rgss]

Instructions

First, install the script above. Then, to activate the script, you need to modify your Scene classes.

For XP, with the SDK installed with part 3 (SDK::Scene_Base), find where it says:
[rgss]    loop do                       # Scene Loop
      main_loop                   # Main Loop           <========This line, replace with error_wrapper:)main_loop)
      break if main_break?        # Break If Breakloop Test
    end                           # End Scene Loop
[/rgss]
Without SDK installed, you need to manually go through each Scene class you want it installed in, and change this:
[rgss]    # Main loop
    loop do
      # Update game screen
      Graphics.update
      # Update input information
      Input.update
      # Frame update
      update              #<======== Change this to error_wrapper:)update)
      # Abort loop if screen is changed
      if $scene != self
        break
      end
    end
[/rgss]

For RPG Maker VX, change this in Scene_Base:
[rgss]    loop do
      Graphics.update             # Update game screen
      Input.update                # Update input information
      update                      # Update frame    <======== Change this to error_wrapper:)update)
      break if $scene != self     # When screen is switched, interrupt loop
    end
 
[/rgss]

Compatibility

This should work with anything and everything, although it may be disabled by some other non-SDK RMXP scripts, or interfere with anything that messes with the Exception class.

Credits and Thanks

The inspiration and foundation for this script was provided by Zeriab in the Scripter's Corner thread. I'm assuming he's OK with this going out, since I'm not trying to repackage his script, nor am I trying to become a big celebrity for this. Just another useful tool provided by the various scripters on RMXP.org working together.
 

cairn

Member

very nice idea if you ask me o.o. This will make things just so much easier for groups that can only keep in touch via the boards or can't describe exactly what happened to the scripter(s). A+ for you guys. :thumb:
 
I'll note that there was an error in the code, having to do with using String.%, which I have fixed (it's a pain in the ass to find errors when, being in the Exception class, they cause infinite loops which crash the program without explanation). If you've already installed the script, I suggest you re-install.
 

Zeriab

Sponsor

I am fine with you taking my research and presenting it in an easily usable format. In fact I am happy and proud of the product of our combined efforts ^_^

I think you should note that you can also use it in the main:
[rgss]#==============================================================================
# ** Main
#------------------------------------------------------------------------------
#  After defining each class, actual processing begins here.
#==============================================================================
 
begin
  # Prepare for transition
  Graphics.freeze
  # Make scene object (title screen)
  $scene = Scene_Title.new
  # Call main method as long as $scene is effective
  while $scene != nil
    $scene.error_wrapper:)main)   # Was $scene.main
  end
  # Fade out
  Graphics.transition(20)
rescue Errno::ENOENT
  # Supplement Errno::ENOENT exception
  # If unable to open file, display message and end
  filename = $!.message.sub("No such file or directory - ", "")
  print("Unable to find file #{filename}.")
end
[/rgss]

You can use this for a more coarse-grained granularity generic error handling. You can also use the error wrapper to provide more fine-grained generic error handling if you want. It is situation specific although the granularity suggested by meustrus is a good standard choice.

*hugs*
- Zeriab
 
I'm glad you like it, since I feel embarrassed for never actually asking you. Speaking of your research, I've found it very interesting that Exception.message calls Exception.to_s and not the other way around, and despite what the help file says, Exception.exception(string) really does nothing (or at least that's not how exceptions gets raised). Very, very stupid that it says that in the help file. And good job finding $RGSS_SCRIPTS, I never would have looked for something like that myself.
 

Zeriab

Sponsor

You are forgiven ^_^
That is indeed interesting. $RGSS_SCRIPTS was something I found ages ago. I don't remember how I found them anymore.
Anyway that's why combined efforts can lead to great results ^_^

Btw. I just though that time tagging error messages when writing to the log file would be a good idea.
Here is the changed script:
[rgss]class Exception
  # prevents infinite loops by aliasing the original message
  alias to_string :to_s
  #--------------------------------------------------------------------------
  # * Only called from internal error checking; writes log and adds backtrace
  #--------------------------------------------------------------------------
  def to_s
    write_to_log # Note that the log calls :full_message, which calls to_string
    return message_with_backtrace
  end
  #--------------------------------------------------------------------------
  # * Returns the error message with accompanying backtrace
  #--------------------------------------------------------------------------
  def message_with_backtrace
    return to_string + backtrace_string
  end
  #--------------------------------------------------------------------------
  # * Generate the backtrace string from a range of its values
  #--------------------------------------------------------------------------
  def backtrace_string(range = nil)
    return "" if backtrace == nil or backtrace.length <= 0
    range = 0...backtrace.length unless range.is_a?(Range)
    output = "\n\nBacktrace:\n"
    for i in range
      output += backtrace_item(i, true) + "\n"
    end
    return output
  end
  #--------------------------------------------------------------------------
  # * Generate a string from an individual item in the backtrace
  #--------------------------------------------------------------------------
  def backtrace_item(i, show_method = false)
    return "" if backtrace == nil
    method = backtrace.gsub(/Section(\d*):(\d*)/) {}
    output = "'%s' line %d" % [$RGSS_SCRIPTS[$1.to_i][1], $2]
    output += method.sub(':', ' ') if show_method
    return output
  end
  #--------------------------------------------------------------------------
  # * Returns what raising an error displays
  #--------------------------------------------------------------------------
  def full_message
    # Finds script and line number
    error = "Script " + backtrace_item(0) + ": #{self.class} occured.\n\n"
    return error + self.message_with_backtrace
  end
  #--------------------------------------------------------------------------
  # * Write the error to the log file
  #--------------------------------------------------------------------------
  def write_to_log
    return if @written # Only write log once
    file = File.open('ErrorLog.log', File::CREAT|File::APPEND|File::WRONLY)
    file.print Time.now
    file.print "\n--------------------------------------------------------\n"
    file.print full_message
    file.print "\n========================================================\n"
    file.close
    @written = true
  end
  #--------------------------------------------------------------------------
  # * Is this error fatal? (redefine for other exceptions)
  #--------------------------------------------------------------------------
  def fatal?
    return true
  end
end
 
def StandardError.fatal? ; return false ; end
 
class NameError < StandardError
  alias to_string :to_s
  def to_s ;super ; end
end
 
class SystemExit < Exception
  def to_s ; return message_with_backtrace ; end
end
 
class Object
  #--------------------------------------------------------------------------
  # * Advanced error checking for methods - provide method as a symbol
  #--------------------------------------------------------------------------
  def error_wrapper(symbol)
    # Create the record of whether there's been an error last time checked
    if @_error == nil
      @_error = {}
    end
    # If in debug mode, or there was an error last time we ran this method
    if $DEBUG or @_error[symbol]
      # Run the method and, after getting no errors, reset error check
      send symbol
      @_error[symbol] = false
    else # If there was no error last time we ran this method
      begin
        send symbol
      rescue
        # Catch errors and provide feedback; set checker for each-frame errors
        print $!.full_message if $DEBUG or $!.fatal?
        $!.write_to_log
        exit(1) if $!.fatal?
        @_error[symbol] = true
      end
    end
  end
end
[/rgss]

*hugs*
- Zeriab
 
Sweet. That thought occurred to me as well, but I never actually looked for an easy way to write a time stamp. Time.now sounds good, I'll put that in right now...
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top