Strange Eons includes a source-level debugger to help you find and solve bugs in your script code. It allows you to run scripts line-by-line and observe how the values of script variables change, what values your functions are returning, and what decisions are being made at branch points like if statements. This lets you verify whether your code is doing what you think it is doing, and if not, why not. This page will show you how to get the debugger running and provide some strategies you can use to fix (and prevent!) bugs.
The debugging system has two parts: a client and a server. The server runs inside Strange Eons. The client is a separate app that talks to the the server to get information about the state of the script system and pass on your debugging commands. It takes the information that it gets from the server and presents it to you in a sensible way.
To enable debugging in Strange Eons, follow the steps below. You only need to do this once.
Once you change these settings, you can continue using Strange Eons normally. You only need to start the separate debugger app (the client) when you actually want to use it.
Once debugging is enabled as described above, you can launch the client app to start a debugging session. There are a few different ways to start the debugger client: from inside Strange Eons, by launching it separately, and manually from the command line.
To start the debugger from Strange Eons, click on the little bug icon that appears when debugging is enabled. In the majority of cases, this will “just work.”
If clicking the bug doesn’t work, you can fix how it launches the client by setting the
script-debug-client-launch
key to an appropriate command.
To start the debugger by launching it as a separate app, just double click its app icon as usual:
Depending on how you installed Strange Eons, you might not have an app icon. In this case, start the debugger manually from the command line.
To start the debugger manually from the command line, use a command similar to the following in the Strange Eons installation directory. (The command should be similar to a command you might use to start Strange Eons itself, but with debugger
replacing strangeeons
.)
java -Xms64m -cp strange-eons.jar debugger
When starting from the command line, you can append the options
--host
and/or--port
to specify the address and port of the debug server you wish to connect to (as described below). If you are not sure where the server is located, you can use the--search
option, which will scan the local host (or the specified--host
) for debug servers and list any that it finds. This is similar to using the Scan button described below, except that results are printed to the console.
A soon as the client starts it will try to connect with the server running in Strange Eons. This happens over a local network connection. If you are running Strange Eons on the same device and you have enabled debugging using the default settings, it will normally connect automatically within a few seconds.
(If the client connected automatically for you, you can skip the rest of this section.)
Enabling the debugger does not expose Strange Eons to the entire Internet. In fact, you don’t even need to be able to connect to the Internet for debugging to work. Using the default settings, it will only be visible to apps on the same device. There is, however, a preference option to Enable remote debugging that will let you debug Strange Eons using a copy of the debugger running on another computer. This can be convenient (for example, each app can have its own screen), but if you use this option you should also set up an appropriate firewall.
Rarely, you will need to connect to Strange Eons manually. For example, if a different app is already using the port that Strange Eons uses by default, then the debugger won’t be able to find Strange Eons where it expects to. In that case you will see a screen like the one below. You will have to give the debugger a hand, but don’t panic! It’s easy:
To connect you just need to know the host and port to connect to. Fill these in, choose Connect, and the client will complete the connection.
The host is the device where the server is running. If you are using the default settings, this is just localhost
. If you are allowing connections from other devices, then this is your device’s IP address. The port is a specific address on your device where the debug server is listening for connections from a client. You can think of the host as being like the telephone number for a business, and the port being the particular extension where Strange Eons will pick up the phone. You can’t easily guess the port number, but you can find it:
Switch to Strange Eons and look for the bug icon near the upper right corner. Hover over the icon and observe the note that pops up. It will say something like “Listening at localhost:8888”. The part after the last colon is the port number (8888 in this case). The part before that colon (and after Listening at) is the host name. It could be a name or a string of numbers. In either case, enter this information in the appropriate fields on the connection screen in the debugger app, then choose Connect.
Sometimes you can’t use the bug icon. For example, the Strange Eons user interface may be frozen if a running script has already been interrupted. Or, the main window might not be available yet if a plug-in script has been interrupted during startup. And sometimes you may have multiple copies of Strange Eons open, each with its own debug server running (for example, when testing a plug-in). You can search your device for debug servers from the connection screen by choosing Scan. A small dialog box will open and the client will start scanning. The scan consists of checking the ports on your device for a debug server, which can take several minutes to complete. However, you do not usually have to wait for the entire scan. As each debug server is discovered, it is added to the list. You can connect to a listed server at any time by choosing its Connect button.
Each discovered server will be listed with:
Important: During scanning, the progress bar may appear to stall for long periods of time. This is normal. It usually indicates that the scanner has found a port that is connected to some other kind of server. The client is asking it if it is a debug server, but it is not saying anything back so the client waits a while for an answer before moving on.
If you have multiple instances of Strange Eons running, each will have its own debug server. To switch between multiple servers, choose the back arrow from the debugging screen. This will take you back to the connection screen. From here, enter the host and port for the target instance and press Connect to switch servers.
Once the debugger has connected to Strange Eons, you can use it to inspect and step through scripts. To step through a script, the script must first be interrupted. There are several ways to do this, depending on where you are when you start out. Here are some:
When a script is interrupted, in most cases Strange Eons will appear be frozen. It isn’t really, it is just waiting for the script, and since the script is paused Strange Eons is effectively paused, too. As soon as you let the script finish from the debugger, Strange Eons will “thaw” again and run normally.
To interrupt a script at the next opportunity from the debugger, click the Pause button in the tool bar along the top of the window.
To debug a script from the script editor, choose the Debug command instead of Run. The script will start to run, and it will be interrupted at the first opportunity.
To debug the script for a game component in an installed plug-in, from the New Game Component dialog, choose the desired component and then Shift+click the Create button.
To debug the current Quickscript, click Debug Script in the Quickscript window.
The following tool bar buttons let you run the script one step at a time so you can watch everything that happens:
Step Over
Run just the highlighted line (the point of the interruption), then interrupt the script again.
Step In
Run the highlighted line like Step Over, but if the line calls any functions, also step through those.
Step Out
Finish running the current function, then interrupt the script again as soon as it is done.
Once you are done stepping through the script, use one of the following tool bar buttons to resume:
Continue
Continues running the script normally until it completes or it is interrupted again.
Walk
Continues running the script, but runs it slowly, pausing briefly and highlighting each line as it goes. This can be useful for novice programmers to better understand the flow of execution.
The debugger can interrupt scripts automatically when certain events occur. The most common such event is whenever a certain line in the script is reached. Such lines are called breakpoints, and you can set as many as you like. Suppose you are stepping through some code and you reach a loop that will run 1000 times. It would be tedious to step through all 1000 runs of the loop, so you set a breakpoint just after the end of the loop and then click Continue. Strange Eons will zip through the loop, then stop again at the breakpoint.
To set a breakpoint, first, choose the script from the Scripts panel in the debugger if necessary. Next, scroll down to the line you want to stop on in the Source panel. The line number of suitable lines will appear as a hyperlink. Click the link to set a breakpoint. The background of the line number will turn pink.
If your script does not appear in the Scripts panel, you may need to start it once to make Strange Eons “aware” of it. For example, use the Debug or Run commands from the script editor.
To clear a previously set breakpoint, click the line number again. It will turn back from pink to grey.
Besides breakpoints, the tool bar has toggle buttons that let you interrupt scripts on some other common events:
Break on Function
Interrupts a script whenever any function is called.
Break on Return
Interrupts a script whenever any function returns.
Break on Throw
Interrupts a script whenever an Error is thrown using the throw
keyword.
Break on Debugger
Interrupts a script whenever a debugger;
statement is reached. This is the only break type that is turned on by default.
When a script is interrupted, you can examine the current value of variables and expressions.
To examine the current value of a variable in the Source panel, hover over the identifier with the mouse pointer.
To track how a variable or other expression changes over time, click New watch in the Watch Expressions panel, then enter an expression to track, such as x
or diy.name
or even (pixels[x][y] & 0xff)/255
. Each time you take another Step or otherwise interrupt the script, the expressions will be re-evaluated and their string values displayed in the table.
To edit an existing watch expression, click the expression, edit it, and press Enter.
To stop tracking a watch expression, click on its Value column and press Delete.
A scope is the set of variables and other identifiers that can be “seen” from a given point in the script. For example, suppose you write a function that declares a variable named horsies. That variable is only visible to other code defined inside of that function, and only after it is declared. If you refer to it outside of the function you will get an error.
function equestrian() {
let horsies = "neighbours";
// OK
println(horsies.substring(0,5))
}
// ERROR, no "horsies" variable is visible (in scope) here
println(horsies.length);
Scopes in JavaScript can get weird, but generally, if you avoid the var
keyword then whenever you see braces {
… }
a new scope has been created. The code inside the braces can refer to the variables declared outside of the braces, but not the other way around.
let a = 1;
{
let b = 2;
// this will work fine
println(a+b);
}
// this will not; b effectively ceased to exist at the }
println(a+b);
To explore the graph of identifiers that are in scope (and their values), open either the <scope>
or <this>
branch of the Local Scope panel. To explore the children of the value assigned to any of the revealed identifiers, open that branch in turn. For example, if a variable has an object assigned to it, you can open that variable’s branch to see a list of the names of that object’s properties.
To go to specific location in the graph, click the Property field and enter the path using dot notation, for example, bindings.component
would locate bindings
under the <scope>
branch, and if found, open that branch and then locate component
.
Continuing from the example above, if the script was interrupted inside the equestrian()
function, you would find horsies
listed under the <scope>
branch of the tree once the let horsies ...
line completed.
It is common for one function to call another function, and that function to call another function, and so on. And it is often the case that while a problem is discovered deep inside such a chain of function calls, the actual source of the problem is higher up the chain: function A calls function B with bad data, function B passes the bad data on to function C, and function C tries to use the bad the data and goes haywire. It is therefore useful to be able to go back up this chain of calls to see where in the code a function is being called from. The Call Stack panel lets you do this.
To understand this panel, it helps to know the term call site. A call site is simply a location from which a function is called:
function binco(n,k) {
// Each of these let statements is a call site
// for the factorial function
let nFact = factorial(n);
let kFact = factorial(k);
let dFact = factorial(n-k);
return nFact / (kFact * dFact);
}
// This is the factorial function
function factorial(n) {
if( n > 1 ) {
// This is also a call site for
// the factorial function!
return factorial(n-1) * n;
}
return 1;
}
// And this is a call site for the binco function
// (and also for the println function)
println(binco(4,2));
When a script is interrupted, the Call Stack panel will show a list of one or more script locations. The first entry is the point of interruption: the line that will be run if Step Over is clicked. The second entry in the list (if any) is the call site of the function that contains the point of interruption. That is, the location where the function that is currently interrupted was called from. The third entry is the call site for the function that contains the call site listed as the second entry, and so on. (If this seems confusing, take a moment to play with the call stack when you have a script interruption and there are 4 or 5 entries in the list!)
To examine a call site in the call stack and its scope, click the relevant entry in the Call Stack. The Source panel will switch to the call site, the Watch Expressions will be re-evaluated using the scope of the call site, and the Local Scope panel will be changed to reflect the contents of that scope.
To return to the point of interruption and its scope, click the top entry in the Call Stack. Or, click any of the Step buttons in the tool bar.
Let’s try putting this together with some practice:
binco()
function, because that is the function we stepped into.let nFact = factorial(n);
.let kFact = factorial(k);
. Unlike with Step In, we did not step through the factorial(n)
call line by line. We just called the function normally and got the result.factorial(n)
to the variable nFact
, that variable has a value now. Hover the mouse pointer over the nFact
variable in the Source window. A note pops up showing that the value of this variable is now 24
, the result of the function call.kFact
variable on the next line. This variable is undefined
. The line is about to run, but it hasn’t run yet, so kFact
has not been assigned a value yet.factorial()
function, which was called from the previous line.factorial()
function was called from (inside the binco()
function). The last entry is the location that the binco()
function was called from, the last line of the script. Click on each entry in turn and the Source window will show that location.n
. An new row is added that shows the expression n
and its current value, 2
. (Recall that this function was called with the value of k
, which was 2
.)return factorial(n-1) * n;
. Notice that this line calls the factorial()
function from inside the factorial()
function.factorial()
, but notice that now there is another entry in the Call Stack and the value of n
in the Watch Expressions is now 1
.if
statement. Now click Step Over again and you will see that the test in the if
statement evaluated to false
. You know this since, if the expression was true
, the debugger would have stopped at the statement inside the braces {
… }
.if()
statement (n > 1
).binco()
function, the watch expression shows that n
is 4
. This is a different n
(in a different scope) than the one inside the factorial()
function. Namely, it is the value passed to the function as the argument n
. You can click the bottom entry of the Call Stack to verify that this was called as binco(4,2)
, so indeed n
should be 4
and k
should be 2
.println(binco(4,2));
. Because we stepped into the call to binco()
, this line isn’t done yet: it still has to call the println
function and pass it the result of the call to the binco(4,2)
function. But suppose we have learned what we wanted to learn, and we want to finish up. Click Continue to let the script complete normally.Let’s say you have a plug-in that isn’t behaving as you expect. How do can you fix it? To answer that, let’s first describe the situation more clearly. When you have a bug in your script, you don’t know right away what the cause of the bug is. Instead, you have signs or symptoms that the script is not behaving as intended. You have to work backwards from the symptoms to the cause. This may not be easy: there might be more than one cause, and the symptoms may be far removed from the root cause. Here are some basic strategies that can help you find that root cause:
To fix a bug you will need to engage the logical and creative parts of your mind. Both of these get “deactivated” when you are upset, which makes debugging that much harder. Take a deep breath. Try to approach the process like you are solving a puzzle: solving problems is an inseparable part of the programming process. When you find that you are upset, research shows that naming what you are feeling can help re-engage the rational part of your mind: “I’m frustrated because I keep stepping through this and I can’t find where the problem is.”
Sometimes a bug manifests intermittently. In such cases, a good strategy is to experiment with the program to look for cases where the symptoms do and do not appear, until you know the exact combination of conditions needed to reproduce the bug. This will not only help you to form hypotheses about the underlying cause, but it also gives you a reliable way to test whether the problem still occurs after you think you have fixed it.
The source of a bug often proves elusive. In the end it may come down to a single character hidden somewhere in hundreds or thousands of lines. It is therefore vital to systematically narrow down the code that it may be hiding in. The debugger is useful here, as you can step around line by line or function by function until you see the necessary conditions to produce the bug. Keep an eye on the values of suspect variables by adding them to the watch list. Other tips for identifying the problem code:
As you explore and experiment during debugging, different hypotheses about the root cause may occur to you. You’ll need to test these hypotheses in order to narrow down the possible causes. Sometimes this is simple, such as using the debugger to change the value of variable. Sometimes it is more involved, such as writing a little script in the Quickscript window to test your assumptions.
Once you think you have fixed the root cause, always verify that the bug no longer manifests (and that your fix didn’t add new bugs of its own!).
It is easier to find and fix problems while you write new code than it is to hunt down bugs after the fact. Try to break your program into small functional units that can be reused, and make them into functions. (This is a skill that you’ll get better at with practice.) Then, thoroughly test each function to ensure that it behaves as expected. Make sure that you test the function with typical cases, edge cases, and invalid cases. An edge case is one that uses an extreme value for the possible inputs: if your function expects a non-negative number, what happens if you pass 0
? Or Infinity
? An invalid case is one that shouldn’t work. What happens if you pass -1
? Or null
? Or a string
? If you detect illegal inputs and throw a descriptive Error, it can help you track down the source of buggy code. Here is an example:
function setId(id) {
// use a regular expression to verify that id matches the pattern we expect
if(!/(\d|\_)+/.test(id)) {
throw new Error("id must consist of digits and _: " + id);
}
this.id = id;
}
When an error (or other exception) is thrown but not caught by your script, a script trace will appear in the script output window. This will show the chain of function calls that led to the error being thrown, along with source file names and line numbers of those calls. You can click on these lines in the source output window to open the source file at the indicated line. (Depending on where the source file is located, it may be opened read-only so that you cannot edit it.) If you use the technique above to throw your own errors, the second element in the script trace will be the function that passed you the the invalid argument, so you can start your investigation there.
A rogue script can cause Strange Eons to stop responding. For example, it is easy to accidentally create an infinite loop that will run forever. However, things might not actually be stuck. It may be that your script reached a breakpoint or a debugger;
statement and it is waiting for you to resume the script from the client app. Try connecting the client first to check the situation out (use Scan if needed). Choose the Pause button and wait to see if a script gets interrupted. You may be able to simply continue the script or modify the state of some variables to work around the cause of the issue. Failing that, you can force the connected Strange Eons instance to close.
If the debugger is not running, you can forcibly quit Strange Eons. Use Ctrl+Alt+Delete on Windows, Command+Option+Escape on macOS, or the System Monitor on Linux.
If the issue was caused by a plug-in, you can prevent Strange Eons from trying to start it by opening the user folder, and either deleting or temporarily renaming the problem plug-in (for example, add a .x
extension).
To debug an extension script that fails while the app is starting, you can follow these steps:
debugger;
at the top of the extension plug-in script.debugger;
line, the script will be interrupted. The process of starting the app will stop until the script completes.WARNING: default debug server port in use; switched to port 50,429
. Enter the port number on the connection screen and Connect.