Games and other graphical apps often contain infinite loops to sequentially render animation frames to the screen:
This infinite loop is a problem in the browser environment because control is never returned to the browser. There’s no opportunity to render graphics and the app will appear to hang - and after some time the browser will notify the user that the page is stuck and offer to halt or close it
Emscripten Loops
Emscripten’s solution is emscripten_set_main_loop
. This simulates an infinite loop, but in actuality just calls the loop function - in our case renderFrame()
- at a specified number of frames per second. After each call completes, control is returned to the browser for rendering and other housekeeping. The adjusted code will look like this:
Remember to never block inside the loop function - doing so will prevent your app from drawing to the screen. Also you can only have one Emscripten loop running - no nesting!
emscripten_set_main_loop arguments
The function signature is:
Where:
func
= the loop function to invokefps
= the number of times per second to invoke the loop function- It’s strongly recommended to set
fps
to0
. This will use the browser’s requestAnimatiomFrame mechanism to call the loop function - ensuring a smooth frame rate that lines up with the browser and monitor refresh rate
- It’s strongly recommended to set
simulate_infinite_loop
= the loop behaviour- If
simulate_infinite_loop
is set totrue
, the function will throw an exception to stop execution of the caller. This will lead to the main loop being entered instead of code after the call toemscripten_set_main_loop()
being run - which is the closest we can get to simulating an infinite loop - If
simulate_infinite_loop
is setfalse
, execution of the caller will continue after the call toemscripten_set_main_loop()
- If
At any point we can cancel the Emscripten main loop by issuing emscripten_cancel_main_loop()
. This will stop execution of the main loop function and prevent any further iterations
If you need to pass arguments to the loop function there is emscripten_set_main_loop_arg. You can find more info in the official Emscripten documentation
Emscripten loops are not real loops
Unlike traditional loops, Emscripten loops will not resume execution of code beneath the emscripten_set_main_loop
call when the loop is broken/cancelled. Here’s how it works:
Neither scenario works like a traditional loop, which means that code often requires significant refactoring to accomodate Emscripten loops. For a large code base the effort required might be prohibitive
Emterpreter to the rescue?
Because of the high effort required to rewrite traditional apps towards Emscripten loops, Emscripten provides Emterpreter
Emterpreter allows you to keep traditional infinite loops. You just need to replace your existing sleep/delay calls with emscripten_sleep
calls:
During emscripten_sleep
, your browser has the opportunity to render graphics to the screen and perform other actions it would normally be blocked from doing inside the infinite loop. Here’s a real, working example you can try yourself:
You can build with the following commands. Then serve <filename>.html
and open it in your browser
If the build fails, try downloading and activating the “fastcomp” version of the Emscripten SDK. There are plans to drop Emterpreter support from the default version of the SDK
When you run this sample, you’ll immediately see the main issue with Emterpreter - it’s incredibly slow. The stack has to be unwound and reassembled for every emscripten_sleep()
call - an expensive operation. The recommendation is to use Emterpreter sparingly and refactor towards Emscripten loops wherever you can
That’s the end of Lesson 3! Next up: FileIO
Other Posts in this Series
- Lesson 1: WebAssembly Hello World - 30 Nov 2019
- Lesson 2: Graphics with SDL - 01 Dec 2019
- Lesson 3: Emscripten Loops - 03 Dec 2019
- Lesson 4: File System Access - 08 Dec 2019
Comments