· 4 years ago · Jul 16, 2021, 07:48 AM
1#--
2# Backtrace v1.5 by Solistra
3# =============================================================================
4#
5# Summary
6# -----------------------------------------------------------------------------
7# This script provides the missing full error backtrace for RGSS3 as well as
8# a number of features related to exception handling for debugging.
9#
10# Normally, the error message given when an exception is encountered provides
11# only the exception information without providing the backtrace; this script
12# rectifies that by displaying the backtrace within the RGSS Console when
13# applicable, potentially logging the backtrace to a file, and allowing script
14# developers to test games with critical bugs without causing the entire engine
15# to crash by optionally swallowing all exceptions.
16#
17# License
18# -----------------------------------------------------------------------------
19# This script is made available under the terms of the MIT Expat license.
20# View [this page](http://sesvxace.wordpress.com/license/) for more detailed
21# information.
22#
23# Installation
24# -----------------------------------------------------------------------------
25# Place this script anywhere below the SES Core (v2.0 or higher) script (if
26# you are using it) or the Materials header, but above Main. This script does
27# not require the SES Core, but it is highly recommended.
28#
29# Place this script below any script which aliases or overwrites the
30# `Game_Interpreter#update` method for maximum compatibility.
31#
32#++
33
34# SES
35# =============================================================================
36# The top-level namespace for all SES scripts.
37module SES
38 # Backtrace
39 # ===========================================================================
40 # Provides methods and configuration options for the full error backtrace.
41 module Backtrace
42 # =========================================================================
43 # BEGIN CONFIGURATION
44 # =========================================================================
45
46 # Whether or not to explicitly raise exceptions after exception handling
47 # has taken place; by default, this is set to `true`.
48 #
49 # This script will attempt to work around any exceptions raised if this
50 # constant is set to a `false` value; this may be useful for script authors
51 # or game developers for debugging purposes, but may cause game instability
52 # (or other odd behaviors).
53 #
54 # **It is highly recommended that you set this constant to `true` before
55 # releasing any version of your project.**
56 RAISE_EXCEPTIONS = true
57
58 # Whether or not to log exception information and the backtrace to a log
59 # file; may be either `true` or `false`.
60 LOG_EXCEPTIONS = false
61
62 # Whether to append to the log file or overwrite previous contents whenever
63 # an exception is handled; set to `true` to append to the log file, `false`
64 # to overwrite its contents. The value of this constant only applies if
65 # {LOG_EXCEPTIONS} has been set to a `true` value.
66 APPEND_LOG = true
67
68 # The log file exception information is written to if the {LOG_EXCEPTIONS}
69 # constant is set to a `true` value. The placement of this file is relative
70 # to your project's root directory.
71 LOG_FILE = 'Backtrace.log'
72
73 # Whether or not to remove the log file created by a previous play testing
74 # session whenever the game is started; this may be useful for developers
75 # or play testers to ensure that the generated log files do not grow too
76 # large. May be either `true` or `false`.
77 RESET_LOG = false
78
79 # Creates an alert box containing information about a caught exception when
80 # one is handled if {RAISE_EXCEPTIONS} is set to a `false` value. This may
81 # be useful to alert developers or play testers at the exact moment an
82 # exception occurs.
83 ALERT = false
84
85 # =========================================================================
86 # END CONFIGURATION
87 # =========================================================================
88
89 # Remove the log file if {RESET_LOG} is set to a `true` value and the log
90 # file exists.
91 File.delete(LOG_FILE) if RESET_LOG && File.exist?(LOG_FILE)
92
93 # Runs the given block, handling all encountered exceptions. The exact form
94 # of the exception handling is largely configured via the constants defined
95 # in the {SES::Backtrace} module.
96 #
97 # @return [Object] the return value of the given block
98 def self.with_exception_handling
99 yield
100 rescue RGSSReset
101 reset
102 rescue SystemExit
103 exit
104 rescue Exception => ex
105 print_caught(ex) if $TEST
106 log_caught(ex) if LOG_EXCEPTIONS
107 if RAISE_EXCEPTIONS
108 raise(ex)
109 else
110 alert_caught(ex) if ALERT
111 retry
112 end
113 end
114
115 # Resets the game in a manner similar to the way the default `rgss_main`
116 # loop handles resetting.
117 #
118 # @return [void]
119 # @see #reset_block=
120 def self.reset
121 [Audio, Graphics].each(&:__reset__)
122 @reset ? TOPLEVEL_BINDING.instance_exec(&@reset) : SceneManager.run
123 end
124
125 # Assigns the reset block for this module to the given `Proc` object.
126 #
127 # @note This method is automatically called by `rgss_main` -- only call
128 # this manually if you are not using the standard `rgss_main` loop and
129 # wish to customize the behavior of an F12 reset.
130 #
131 # @param proc [Proc] the `Proc` to execute when an F12 reset is handled
132 # @return [Proc]
133 def self.reset_block=(proc)
134 @reset = proc
135 end
136
137 # Returns cleaned backtrace information for a given exception by replacing
138 # the file information given by Ace with actual script names rather than
139 # their numeric placement in the Ace script editor.
140 #
141 # @param exception [Exception] the exception to clean the backtrace for
142 # @return [Array<String>] the cleaned backtrace
143 def self.clean_backtrace_from(exception)
144 exception.backtrace.map do |line|
145 line.sub(/^{(\d+)}/) { $RGSS_SCRIPTS[$1.to_i][1] }
146 end
147 end
148
149 # Prints exception information and a full backtrace to a specified stream
150 # (standard error by default).
151 #
152 # @param exception [Exception] the caught exception
153 # @param stream [#puts] the stream to write exception information to
154 # @return [void]
155 def self.print_caught(exception, stream = STDERR)
156 msg = "#{Time.now} >> EXCEPTION CAUGHT <<"
157 msg << "\n#{exception.class}: #{exception}.\nBacktrace:\n\t"
158 stream.puts msg << clean_backtrace_from(exception).join("\n\t")
159 end
160
161 # Logs exception information and a full backtrace to the log file specified
162 # by {LOG_FILE}. Exceptions are appended to the log if the {APPEND_LOG}
163 # constant is set to a `true` value, otherwise the log will only contain a
164 # record of the last encountered exception.
165 #
166 # @note The actual logging of information is done via the {.print_caught}
167 # method.
168 #
169 # @param exception [Exception] the caught exception
170 # @return [void]
171 # @see .print_caught
172 def self.log_caught(exception)
173 File.open(LOG_FILE, APPEND_LOG ? 'a' : 'w') do |file|
174 print_caught(exception, file)
175 end
176 end
177
178 # Provides a message box containing information about a handled exception.
179 #
180 # @param exception [Exception] the caught exception
181 # @return [void]
182 def self.alert_caught(exception)
183 msg = "EXCEPTION CAUGHT:\n#{exception}\n\n"
184 msg << 'Check the RGSS Console for more information.' if $TEST
185 msgbox(msg)
186 end
187
188 # Register this script with the SES Core if it exists.
189 if SES.const_defined?(:Register)
190 # Script metadata.
191 Description = Script.new(:Backtrace, 1.5, :Solistra)
192 Register.enter(Description)
193 end
194 end
195end
196# Game_Interpreter
197# =============================================================================
198# The worst class in the entire RGSS3 API. Actually, all of the RGSS APIs.
199class Game_Interpreter
200 # Aliased to provide a workaround for a potential stack overflow after
201 # handling an exception.
202 #
203 # @see #update
204 alias_method :ses_backtrace_gi_upd, :update
205
206 # Updates the execution of this instance of the interpreter each frame.
207 #
208 # @note The original method did not take the possibility of a dead fiber into
209 # account; as such, calling `@fiber.resume` on a dead fiber after handling
210 # an exception would cause a new `FiberError` to be raised, eventually
211 # resulting in a stack overflow.
212 #
213 # @return [void]
214 def update(*args, &block)
215 ses_backtrace_gi_upd(*args, &block)
216 rescue FiberError
217 # The fiber is most likely dead due to exception handling -- increment the
218 # index for the interpreter and create a new fiber to interpret event
219 # commands.
220 @index += 1
221 create_fiber
222 end
223end
224# Object
225# =============================================================================
226# Superclass of all objects except `BasicObject`.
227class Object
228 # Aliased to automatically assign the given block as the reset block for the
229 # {SES::Backtrace} module via {SES::Backtrace.reset_block=}.
230 #
231 # @see #rgss_main
232 alias_method :ses_backtrace_obj_main, :rgss_main
233
234 # Evaluates the provided block one time only.
235 #
236 # Detects a reset within a block with a press of the F12 key and returns to
237 # the beginning if reset.
238 #
239 # @return [void]
240 def rgss_main(*args, &block)
241 SES::Backtrace.reset_block = block
242 SES::Backtrace.with_exception_handling do
243 ses_backtrace_obj_main(*args, &block)
244 end
245 end
246end
247