This is a blog post. To read the original post, please click here »
t was surprisingly easy finishing up my RetroArch port to the web, thanks to Emscripten and all the hard work in that. And making it easier for programs to get on the web is a good thing. However, during my journey, I ran into more than a couple issues and learned more than a couple discouraging facts that make me think twice about the great future of full-blown programs on the web. A lot of the tech is still very very young
Most of the big tech in web apps are new. Stuff like WebGL, Web Audio, and even Emscripten itself are still in their growing stages, and it shows. You would never expect to run into a bug with a platform’s standard C library, but that was exactly an issue I had to debug and fix. (Note to future C library developers: while isprint and isgraph sound similar, they are not the same.) Even when they do work, they don’t always work “right” according to the other browsers. Right now my port has to resort to a hack to get Firefox working, because currentTime only updates when the Javascript event loop is idle. Mozilla claims this is the correct behavior, yet Chrome goes ahead and updates continuously. Who is right? Mozilla claim they are, but Chrome says otherwise. The spec doesn’t say with certainty which way is correct, so until this is expanded upon, you will run into these issues. (BTW, If anyone from Mozilla happens to read this, I would really like you guys to adapt the Chrome behavior.)
Doing things the “not-Web” way is bad, but it works better
One thing I learned very quickly: blocking the event loop is bad. Very very very VERY bad. It’s so bad. Never do it. No reason to ever do it. But it’s the only way to do some things.
RetroArch, being an emulator, has very different audio/video sync requirements than something like a game or a video. The audio and video the emulator spit out must get played immediately, or you get horrible stuff like input lag or audio/video desync. One way to combat this is to “block” on audio: make a buffer for audio and fill it up. If it’s full, wait until the audio API empties it a bit and fill it up and go on your way. If you’re on native Linux and using something like ALSA, that functionality is built into the API and works just fine, and for something like OpenAL that doesn’t, you can simulate it with busy loops. However, every time I brought that up in a bug report I was immediately shot down. “Busy-waiting in Javascript is a big no-no.” And it is when you let it get in an infinite loop. However, to do blocking audio with any sort of good performance, it is required with the Web Audio API. Web Audio has no blocking features, so you have to use busy-loops. I tried using some of the other interfaces I had to avoid this, like some callback-based ones, but this leads to…
You are at the mercy of the event loop
The event loop is 100% unpredictable. It also must be hit to do anything involving input/output. Video needs it to display, input needs it to capture events, and audio needs it to play. I tried to implement an audio callback method to avoid blocking on audio, but the callback fired at intervals that were in no way predictable, from one time up to five times every 20 milliseconds. That and trying to put calls to “requestAnimationFrames” is spotty at best. Hoping to get a stable 60FPS off of it is an exercise in futility. Most of these issues are not issues for something like a video or a normal game: Audio can be queued and you can play individual samples instead of streaming. For emulators, there is no alternative, and the tools the web platform has right now do not help. They have to be worked around to get a program that performs well.
What can be done?
The first thing that can be done is to make the browsers behave similarly. Right now Firefox doesn’t have the constantly updating currentTime for Web Audio, and the precision on performance timers on Chrome is really odd. These can be fixed (or a consensus agreed upon).
For more performance-heavy things, there needs to be a way to either get around the event loop or control it better. The yield keyword in ES6 is a nice first step, but there needs to be a way to guarantee (or at least make a better attempt) a callback or timer to happen when it should. Even better would be a way to start a thread separate from the main loop. Can Web Workers be used for this? Not sure, but probably not with any audio/video output which makes it a no-go for us. Maybe some new tech can allow this in browsers. A pipe dream perhaps, but hey, it’ll probably be better than NaCl in the long run.