chester's blog

technology, travel, comics, books, math, web, software and random thoughts

Running Ruby2600 in a Browser With Opal

| Comments

Last year I challenged myself into writing an Atari 2600 emulator using Ruby in time to present it at RubyConfBr 2013 – thus ruby2600 was born. When I found Opal, a Ruby-to-JavaScript compiler, I felt it might be fun to run the emulator on a browser.

It runs even slower than in MRI and is far from polished, but works. To watch it, just click the button below and wait until the black lines get replaced by Pitfall Harry slooooowly running to the left (sorry, no key bindings for now).

Keep reading if you want the gory technical details!

Being browser-friendly

Ruby2600 is fairly portable across Ruby interpreters (JRuby, for example, doubles its speed) and graphic libraries. A typical port consists on a script that:

  • Calls the Bus#frame method and renders the resulting array of pixels into an image (translating Atari 2600 NTSC colors into native ones); and
  • Triggers actions accordingly (e.g., sets Bus#p0_joystick_right to true to move the Player 0’s joystick to the right).

HTML5 Canvas is the ideal place to render those frames, not only for its ability of addressing individual bits, but also for allowing horizontal stretch, which compensates Atari 2600’s rectangular pixels. So I wrote a minimal HTML, some rendering code and…

Generating a frame is CPU-intensive, and browsers don’t like functions that run for too long. The fix: expose a Bus#scanline method and render scanline-by-scanline chaining calls to setTimeout(..., 0), giving the browser some breathing space.

Patching Opal

On my first run, the emulated game crashed straight after a few hundred CPU instructions. It would be a nightmare to debug, if ruby2600 did not have extensive automated tests (which I ended up migrating to RSpec 3 to get rid of deprecated syntax that did not please opal-rspec).

The specs revealed Opal missed bit indexing and had issues with peculiar cases of array expansion and binary shift. Those operations are seldom found in typical Ruby code, but are essential to ruby2600.

Open-source to the rescue: a few patches to Opal fixed these problems (one of the tests even made its way into Rubyspec), leaving me with one last problem: integer division.

Opal translates all numeric classes to JavaScript native numbers, which is great for performance and simplicity, but also makes it unable to tell 3 / 2 (=1) apart from 3.0 / 2 (=1.5). Had to wrap such divisions in ( ).to_i – a bit of noise, but a small price for the awesomeness of cross-compilation.

Final observations

  • Another minor change: Cart now can read the binary data of a cartdrige from an array. Ideally, I’d make it use an uploaded ROM file (since I don’t intend to distribute those), but I’m not sure if that is feasible and just wanted to move on.

  • Firefox renders at least 50% faster than Chrome – at least it did when I watched both running at the same time. I know, I know, this is as scientific as Brazilian Bozo’s Horse Race (and not nearly as fun), but you can check by yourself.

(Chrome also crashed before the inevitable fall into the crocodile lake, while Firefox kept going. Your mileage may vary.)

UPDATE: Seven months later, Chrome reached Firefox in terms of speed, and both seem faster running the same code (which would likely also get improvements from newer Opal and fixes suggested on comments). It shows how both browsers are actively improving JavaScript execution performance – a great competition that benefits everyone!

  • The generated code is interestingly reminescent of the original (after the Opal payload). This relationship is part of what makes it easy to mix Ruby and Javascript – a great Opal asset if used with moderation (as in the current binding script), but easily abused (like I did on the first version).

  • Code for this proof-of-concept is (as of this writing) messy and depends on Opal merging #507, but is available as-is. If anything, it convinced me that Opal is an interesting option among the myriad of JavaScript alternatives – in particular for Rubyists. I’ll keep an eye on it.