Tips and Limits

A few things that are good to know once your programs grow: what keeps them fast, what makes a preview freeze, and why a drawing can disappear.

Keep reads rare

Your drawing calls are sent to the preview in one batch per frame, which is what makes them fast. Reading a value back with await is different: the program has to wait for the preview to answer, which takes a whole frame.

  • Read once at the start, or when something happens (a click), not every frame.
  • Keep the numbers you need in Python. Track a sprite's position, a cube's angle or a score in your own variables, change them in update(), and set them on the preview. You then always know them without asking.
  • A value you set from Python (mesh.position.x = 2) is remembered, so reading it straight back needs no await. A value worked out in the preview (after mesh.position.x += 0.1, or anything three.js changes by itself) needs await.
  • Using a preview value as if it were a Python number, as in if mesh.position.x > 3:, stops with an error that tells you to read it with await.

Preview frozen? A loop must wait for the next frame

The preview draws between frames. A loop that runs forever without waiting never gives it the chance, and the preview looks frozen:

# The preview freezes: this loop never waits.
x = 0
while True:
    x += 1
    ctx.fill_rect(x % width, 50, 10, 10)

Put await frame() at the end of the loop, and each pass becomes one frame:

x = 0
while True:
    ctx.fill_style = "white"
    ctx.fill_rect(0, 0, width, height)
    ctx.fill_style = "black"
    ctx.fill_rect(x % width, 50, 10, 10)
    x += 2
    await frame()

Or use update() and draw(), which can never freeze the preview this way.

If a program does get stuck, the preview stays blank or stops changing (when draw() or update() is the one stuck, it also says so under the picture after three seconds). Press Stop:

  • On iPhone and iPad, Stop interrupts the stuck loop.
  • Elsewhere, Stop may not be able to interrupt a loop that never waits. If the preview stays stuck, reload the app to free it, then add the missing await frame().

Starting and stopping

A Python file that draws, animates or shows a chart starts by itself when its tab comes on screen. Ordinary Python, which only prints, still waits for Run. There is nothing to press.

  • Run in the footer starts it from the top, with the code as it is now. While it is running, the same button is a red Stop. Run always starts the file from the top, in a fresh preview, and clears the log. To start a running program over, press Stop and then Run.
  • Typing starts it over. About half a second after you stop typing, the program runs again from the top with the new code. A half-typed line leaves the running picture alone; if the code is still not right after four seconds, it is run anyway and the mistake is shown. After you press Stop, typing does not start it: press Run.
  • It starts by itself once. When you open the file or come to its tab, it starts. After Stop it stays as it is until you press Run.
  • Only the tab you are looking at runs. Switch to another tab and the scene there waits: it draws no frames and keeps its last picture, so it does not use your device's power in the background. Come back and it carries on from where it was.
  • After Stop, the picture stays as it was, and a small pause symbol shows in the top right corner of the preview. It goes when you press Run. If the frame rate is showing, its last reading stays too, dimmed, so you can still read it.

Making it wait for Run

For a program that does a lot of work, or one you do not want to start until you are ready, put this line near the top of the file:

# pyctures: manual
def draw():
    background("navy")

The preview then shows "Run to draw here" and waits for Run, and typing no longer starts the program over. Run and Stop work as usual. The line must be a comment on a line of its own, within the first 30 lines; capitals and spaces do not matter. # pymations: manual works too. The older line # pyctures: autorun is still accepted; it now says what already happens. Take the line out and the file starts by itself again.

When the preview changes size

The preview changes size when you drag the split, rotate a tablet or turn a phone, switch views, and when the output area under it opens or closes.

  • A drawing made once, at the top level, is kept. It stays the size it was drawn, so make it again (press Run) to fit the new size.
  • A program with draw() redraws every frame using the new width and height.
  • 3D scenes are refitted to the new size automatically, as long as you call renderer.render(...) in draw().

Make it look right in light and dark

The preview's own background is white in the light theme and near-black in the dark theme. Start draw() by painting your own background (ctx.fill_rect(0, 0, width, height), or scene.background in 3D), so your picture looks the same whichever theme is on.

Fit any screen

The preview can be a tall, narrow phone or a wide desktop pane. Work out positions from width and height (width / 2 for the middle, height - 50 for near the bottom) rather than fixed numbers, and read them inside draw() and update() so they follow a resize.

Speed

  • Most devices draw 60 frames a second, and some 120. Turn on Frame rate in the footer to see yours.
  • Move things by speed * dt in update(dt), so they move at the same speed whatever the frame rate.
  • Thousands of shapes a frame are fine. For tens of thousands, draw fewer, bigger things, or join lines into one path and stroke it once. In 3D, one Points object with thousands of positions is far quicker than thousands of separate meshes.
  • Do heavy Python work once, at the start, and keep the results.

Keyboard focus

Key presses go to the preview only after you click or tap it. If the arrow keys do nothing, click the preview first.

A key's name is what the key types, so with Shift held the A key is "A", not "a". A game that uses letter keys can check both: "a" in keys or "A" in keys. Or ask for the key by its place on the keyboard, which Shift and the keyboard's language do not change: "KeyA" in keys.codes.

To ask whether Shift, Control, Alt or Command is held, read keys.shift, keys.ctrl, keys.alt and keys.meta.

For something that should happen once for each press, such as a jump or a shot, use " " in pressed in update, or the function on_key_down. " " in keys stays true for as long as the key is held. See Once for each press.

Fingers, the wheel and zooming

  • More than one finger. mouse follows the first finger only. touches has every finger that is down, and a pressed mouse button too. See Every finger.
  • Zoom. Multiply by pinch.change every frame for two fingers or a trackpad, and read mouse.wheel for the mouse wheel. See Zooming.
  • Movement since the last frame. mouse.dx, mouse.dy, mouse.wheel, pinch.change, pressed and released are set at the start of a frame and are back to nothing at the start of the next. Read them every frame, in update or in your loop.
  • Reading them costs nothing. The preview tells Python what happened with each frame, so mouse.x or touches is an ordinary value: no await, and no frame spent.
  • You do not need add_event_listener for these. A listener of your own still works, for anything this list does not cover.

Libraries

You can import the usual Python libraries. On iPhone and iPad your file runs on Circuitry's built-in Python when the libraries it imports are included (numpy, matplotlib and Pillow are). In the desktop app, Circuit runs the same built-in Python, with pandas and scipy as well. Anything else runs on the Python inside the page, which has a large set of common libraries and loads them the first time a file imports them. Numbers from numpy can be passed straight to drawing calls.

For a library that is not included, put !pip install and its name at the top of the file. See Installing Python Packages.

Files

To read and save files next to your script, such as data, settings or a high score, put import files at the top. See Using your files.

What a program can reach

The preview a Python picture draws in is a page of its own, shut off from the rest of Circuitry. This section is about what a program does through that page: js, load_image, the three.js loaders and display(). Through it, a program cannot read what Circuitry keeps on your device, and it cannot call a website until you allow that site.

  • js is the preview's own page. js.document, js.Image, js.Path2D and the rest are there without an import, and import js at the top works too.
  • Storage is the program's own. save and load keep values for this program alone (Saved values). js.localStorage is also this program's own, not the browser's and not Circuitry's: another file does not see it, and renaming or moving the file starts it empty. Cookies and IndexedDB are not available.
  • Files and databases are kept by Circuitry, in the program's own folder. save_text, save_json and open_database reach that folder and nothing outside it: real files beside the program in the app on an iPhone, iPad or Android device and for a saved program in the desktop app, the browser's keeping on the web (Files and databases). A program writes data, not programs: a file ending in .py, .js or .html is not kept.
  • A website is asked about first. When the program reaches for a site, in js.fetch(...), in a web address given to load_image or a three.js loader, in a js.WebSocket and so on, a dialog asks Allow remote calls? and lists the sites. Choose Allow or Don't allow. Charts and maps shown with display() that load their drawing library from a website are asked about in the same way.
  • Allowed sites are remembered for that file on this device.
  • With Don't allow, the program still runs. Requests to those sites fail, and a line under the preview says remote calls are off, with an Allow button if you change your mind.
  • A file beside the program loads without asking: load_image("cat.png"), and anything opened with import files.
  • A path with no site in it goes nowhere. js.fetch("data.json") does not find a file: read files with import files (Using your files).
  • A server on your own computer can be allowed, one port at a time. The dialog shows it as this computer, port 8000 (with the port your code names).
  • Circuitry itself can never be allowed: not circuitry.dev, not the app, and not the ports a Circuit uses on your computer.

If a program is stopped as unsafe

Circuitry stops a program that tries to do something a preview does not allow. The picture goes, and in its place the preview says:

This program was stopped. It tried to do something that is not allowed here. If someone gave you this file, be careful with it.

  • It stays stopped. Typing, switching tabs, closing and opening Circuitry, and an AI helper's run do not start it. Only Run again, under the message, does.
  • If it tries the same thing again, it is stopped again.
  • Be careful with a file someone else gave you. If you do not know what the code does, do not press Run again: ask the person who gave it to you, or someone you trust, to look at it first.
  • A program you wrote yourself that is stopped has a line reaching for something it should not. Take the line out and press Run again.
  • No message does not mean a file is safe. Circuitry cannot see everything a program tries.