Challenges with time.sleep and input#

Archived

This page is archived. It describes the state from 2023, when WebTigerPython was started, and is kept for reference.

One of the big challenges with educational environments like turtle or robotics is that the programs often need to pause, either to get input from the user with the input function or to show an animation step.

Note

WebTigerPython combines two of the approaches described below. Blocking calls such as input and sleep use pyodide-worker-runner and sync-message, which work with SharedArrayBuffer when the page is cross-origin isolated and fall back to a service worker otherwise (src/pyodide/serviceWorker.ts). Programs that use GPanel, GameGrid, Pygame or soundsystem are additionally rewritten into asynchronous code (src/python/runner/async_parser.py). This page documents the options that were considered in 2023.

Note

The upcoming Javascript Promise Integration API of WASM will probably allow Pyodide to provide synchronous sleep and input functions working natively. This may well greatly simplify everything.

https://v8.dev/blog/jspi

Pausing execution#

The TigerJython environment typically provides a delay(ms) command that pauses the program execution for ms milliseconds. This is super easy to do in standard Python with the time.sleep(s) synchronous function. It is another story in the browser which has a completely different execution model and does not support blocking a program by default.

There are different ways to handle this issue, but none is simple and they all require some kind of understanding of the browser inner workings.

Asyncify the code#

One way of implementing a delay function is to use Python coroutines, that is to adapt the code to the asynchronous nature of the browser. This can be easily done with the asyncio module integrated in Pyodide and allows pyodide to wait for the delay.

import asyncio

async def delay(ms):
    await asyncio.sleep(ms / 1000)

async def greet(name):
    print('Hello ')
    await delay(1000)
    print(name)

async def main():
    await greet('Dennis')

asyncio.run(main())

The issue with this code is that it is absolutely inappropriate for beginners. Even professional developers often struggle understanding async programming. One solution is to take a student written synchronous program and to generate a corresponding asynchronous program run by Pyodide. This is feasible using the ast module and working on the abstract syntax tree.

However, this trick adds another layer of complexity for the environment and complicates debugging as Python reports errors relative to the asynchronous program and not the original program written by the students.

Service Workers#

Another way of implementing the delay(ms) function is to use a service worker. Originally, service workers are used to make web applications work offline by implementing advanced caching strategies where HTTP requests are intercepted by the service worker before leaving the browser and responded according to the caching strategy without even reaching the server.

WebAssembly stack switching#