Finding Mistakes
When a drawing program does something you did not expect, you can stop it at a line and look at its variables, step through it a line at a time, read its errors and printed output, and watch its frame rate. This page covers all of them for a JavaScript scene with the preview on.
Errors: read them, then fix the line
When your program stops with an error, the preview shows it along its bottom edge, with the line it happened on:
Line 14: speed is not defined [Run again]
Fix the line. About half a second after you stop typing, the scene runs again. Run again on the message, or Run in the footer, starts it without a change. Copy on the message copies the file's name and the error, to paste into a chat; the text can also be selected.
A misspelt function name, such as ctx.filRect, is reported the same way, with the line in your file that made it:
Line 6: ctx.filRect is not a function [Run again]
Three kinds of mistake are quieter:
- A misspelt setting gives no error.
ctx.fillStlye = "red"is allowed in JavaScript: it makes a new, useless setting, and the colour does not change. If a colour, font or line width is ignored, check its spelling. - A mistake in the shape of the code, such as a missing bracket or brace, stops the scene before it starts. The message names the line where the browser noticed it, which may be the line after the mistake. A
{,(or[that is never closed is reported on the line that opens it:A { is opened here and never closed: its } is missing. You will see messages like this while you are half way through typing a line; they go when the line is finished. - An error inside a listener you added yourself, with
canvas.addEventListener(...), is not shown on the preview. It is in the log.
Printing
console.log() prints to the preview's log. Press LOG in the editor's footer to see it. The first time, Circuitry asks Capture console output?: say yes, and the scene starts again with the log open under the preview. On a phone the log fills the screen, with a close button.
The log has two filters, All and Problems (errors and warnings), a button to clear it, and one to stop capturing. A dot on the LOG button means something new has been logged while the log was closed; a red dot means an error.
let score = 0
function update(dt) {
score += 1
if (score % 60 === 0) console.log("score is", score)
}
function draw() {
ctx.fillStyle = "black"
ctx.fillRect(0, 0, width, height)
}
Print now and then, as above, rather than every frame: sixty lines a second is hard to read.
To see a value without opening the log, draw it: ctx.fillText(String(score), 12, 24) at the end of draw().
Breakpoints: stop at a line
A breakpoint stops the scene just before a line runs, so you can look at every variable at that moment.
- Add a breakpoint: tap or click just left of a line number. A red dot appears. On a touch screen you can also press and hold the line and choose Breakpoint. With the cursor on a line, F9 adds or removes one.
- The bug lights up. The Debugging button in the footer is the debug switch. Adding a breakpoint turns it on, and the scene starts again from the top, ready to stop.
- The scene stops at the line. The picture holds still, the line is highlighted, and the code is shown beside the preview if it was hidden. Run starts the scene again from the top, and it stops at the breakpoint again.
Debugging is off each time Circuitry opens, even if the file still has red dots from last time. Tap to turn it on. To run straight through without removing your breakpoints, tap the lit to turn debugging off. Tap a red dot to remove that breakpoint.
The debug bar
While the scene is being debugged, a bar is docked above the code:
| Button | What it does |
|---|---|
| Stop | Ends debugging and stops the scene |
| Continue | Runs on to the next breakpoint |
| Over | Runs this line, then stops on the next one |
| Into | Goes into the function this line calls, and stops on its first line |
| Out | Finishes the function you are in, and stops back where it was called |
| Line | The line it stopped on; it reads Running between stops |
Stop in the footer does the same as Stop on the bar. While the scene is stopped at a line, it does not hear keys, clicks or touches.
Looking at variables
Open the Debug tab in the sidebar. While the scene is stopped, Variables lists your variables and their values. Both the variables of the function it stopped in and the ones at the top of the file are shown.
- Tap a value to open it. A list, an object, something made from a class, a
Map, aSetor a three.js object opens to show what is inside. What is inside opens too, as deep as it goes. - A long list shows its real length, as in
[2000 items], and its first 100 rows. Tap Show more for the next 100. - A long number is shortened to six digits, so
0.16866666666666671reads0.168667. Tap it for the exact value. - Only an object's own fields are listed. A getter is shown as
(getter). It is not run, because running it could change your scene.
The Debug tab also lists your breakpoints, and has the same step and continue controls.
Watches
A watch keeps one value in view each time the scene stops. Under Watch in the Debug tab, type what to watch and press Enter.
A watch is a name, then dots and [indexes]:
score
flies.length
flies[0].x
Anything else is not worked out, such as a + 1 or a call like total(). The watch says so: only a name, then dots and [indexes], can be watched. To see a sum, give it a name in your code and watch the name.
The call stack
Call Stack in the Debug tab shows how the scene got to this line: the function it stopped in, the function that called it, and so on. Tap a row to see its line in the code.
The variables do not change when you tap a row. They are always the ones of the function the scene stopped in, the top row.
A breakpoint in draw() or update()
These functions run every frame, so a breakpoint inside one stops every frame: press Continue and it stops again on the next frame. That is useful for stepping through one frame at a time. To let the scene run on, turn debugging off with , or remove the breakpoint.
A breakpoint inside an if stops only when that if is true, which is a good way to catch a rare moment. For example, in the game tutorial, put one on the first things[i] = newThing() to stop at the instant something is caught. The whole line must be inside the if: a breakpoint on a one-line if (hit) lives -= 1 stops every time the if is checked.
What does not stop yet
Stopping at a breakpoint works on every device. A few places in a scene cannot stop yet. When a breakpoint is in one of them, a short message says so.
- A helper file. A breakpoint in a file loaded with
use()does not stop. Put it in the scene's own file, on the line that calls the helper. - A function called by
forEachorsort. The line is passed, and a message says so. Write the loop asfor (const fly of flies) { ... }and it stops. - An arrow function without braces, such as
x => x * 2. Give it braces:x => { return x * 2 }. - A class's
constructor, a getter or a setter, a function that usessuper, and a generator function (function*). - A line where nothing starts, such as a blank line or a comment. Put the breakpoint on the next line of code.
When an AI helper runs your scene, it does not stop at your breakpoints.
The frame rate
Frame rate in the footer shows the frames drawn each second in a corner of the preview, as in 120 fps. The number is held steady, so it does not flicker between two values. The choice applies to every file.
To time one part of a frame, wrap it in measure, and it gets a line of its own under the frame rate, as in physics 0.90 ms:
function update(dt) {
measure("physics", () => {
for (const ball of balls) ball.update(dt)
})
}
measure costs nothing while the frame rate is not showing.
To show the frame rate as part of your own picture, work it out. update(dt) is given the seconds since the last frame, so 1 / dt is the frame rate:
let fps = 60
function update(dt) {
fps += (1 / dt - fps) * 0.05 // smoothed, so the number can be read
}
function draw() {
ctx.fillStyle = "#101820"
ctx.fillRect(0, 0, width, height)
ctx.fillStyle = "lime"
ctx.font = "16px monospace"
ctx.fillText(`${Math.round(fps)} fps`, 12, 24)
}
Most screens show 60 frames a second, and some 120.
If the number drops, your program is doing more each frame than the device can keep up with. The usual causes:
- Very many separate drawing calls. Thousands of shapes a frame is fine; tens of thousands is slow. Draw fewer, bigger things, or join many lines into one path and
stroke()it once. - Heavy work every frame. Work things out once at the start and keep the results.
- Reading pixels every frame.
ctx.getImageDatacopies every pixel it is asked for. Other reads cost nothing (see Tips and limits). - Printing every frame. Sixty lines a second fill the log and slow the scene.
When the preview is frozen or blank
If the preview stays blank and Circuitry stops answering clicks, a loop is stuck. The usual cause is a while (true) loop that never waits: every loop that runs forever must await frame() (or await sleep(...)) inside it. After about four seconds the loop is stopped, and the preview shows an error that names the loop's line. See Tips and limits.
If the preview is blank but everything else still works, look for an error along the bottom of the preview, and check that the scene paints something: a 3D scene shows nothing until renderer.render(scene, camera) is called in draw().
Related
- How a program is shaped
- Tips and limits
- Debugging in Pyctures: the same tools for a Python scene
- Debugging code in workflows and files