top | item 9473653

Porting a NES Emulator from Go to Nim

158 points| def- | 11 years ago |hookrace.net | reply

81 comments

order
[+] fogleman|11 years ago|reply
> As I really liked fogleman's NES emulator in Go I ended up mostly porting it to Nim. The source code is so clean that it's often easier to understand the internals of the NES by reading the source code than by reading documentation about it.

Win!

[+] AYBABTME|11 years ago|reply
Went to read your code after that comment, it is truly, very clean code! :+1:
[+] haberman|11 years ago|reply
Let me get this straight. We have an emulator for 1985 hardware that was written in a pretty new language (Go), ported to a language that isn't even 1.0 (Nim), compiled to C, then compiled to JavaScript? And the damn thing actually works? That's kind of amazing.
[+] derefr|11 years ago|reply
The amazing thing about computer software, relative to every other human endeavour: once you've got one component right, it's right; you can treat it like a simple little node in an even more complex design, and scale it up 1,000,000x, and it'll keep working every single time.

Once you've built a C-to-JS compiler, and a Nim-to-C compiler, you've got a perfectly-functioning Nim-to-JS compiler. There's no game of telephone gradually confusing things; the fidelity is perfect no matter how deep the pipeline goes.

(This is also what's amazing about layered packet-switched networks, come to think of it. The core switch transferring data from your phone to AWS doesn't need to speak 4G or Xen SDN; it's just coupled to them, eventually, by gateways somewhere else, and it all just keeps working, one quadrillion times per second, all over the world.)

[+] shurcooL|11 years ago|reply
What's that obligatory talk link where he describes that everything will be compiled to JavaScript and run in the browser over the next 50 years?
[+] madez|11 years ago|reply
Old hardware is not necessarily hard to emulate. Often it's the opposite.
[+] allendoerfer|11 years ago|reply
This title covers the essence of hacker news pretty well.
[+] nicklaf|11 years ago|reply
Also see: https://news.ycombinator.com/item?id=7745561

My favorites:

> I decided to re-implement Javascript in Javascript. It failed. Here is my story

> ReactOS running IE6 in a JavaScript x86 emulator: we put a browser in your browser so you can browse while you browse

> Introducing js.js: a JIT compiler from JavaScript to JavaScript

[+] shurcooL|11 years ago|reply
Did you port from Go to Nim by hand, or was it automated in any way?

I thought that Go would be the last language I'd write by hand. Previously I wrote C++, which was a dead end in that I could never use tools to parse it and translate to a new language. But with Go it should be much easier to do that if/when I ever decide to switch to something else.

The performance of the emulator in browser (compiled via emscrimpten) is very impressive! It felt like solid 60 FPS to me. I wonder how the Go version compiled via GopherJS would compare? Have you tried?

[+] def-|11 years ago|reply
I did it by hand, changing the code as I saw fit and learning as much about the NES as I could.

I haven't tried GopherJS, but I don't have much Go experience apart from reading it. On a related note, I read that porting the Go version to Android would be difficult and is still far in the future: https://github.com/fogleman/nes/issues/7

[+] conradev|11 years ago|reply
There is also a NES emulator written entirely in JavaScript called jsnes[1] that would be interesting to compare to. The "solid 60 FPS" doesn't always translate to mobile. For example, jsnes runs fine on an iPhone 5S (64-bit processor), but stutters on an iPhone 5.

[1] https://fir.sh/projects/jsnes/

[+] warmwaffles|11 years ago|reply
Well this is new. I've never seen Nim before. What does it offer that Rust, Haskell, Erlang, etc... do not?
[+] def-|11 years ago|reply
Two of my old posts may answer that question:

- http://hookrace.net/blog/what-is-special-about-nim/

- http://hookrace.net/blog/what-makes-nim-practical/

Summary by benhoyt: https://news.ycombinator.com/item?id=8822918

  * Run regular code at compile time
  * Extend the language (AST templates and macros);
    this can be used to add a form of list comprehensions to the language!
  * Add your own optimizations to the compiler!
  * Bind (easily) to your favorite C functions and libraries
  * Control when and for how long the garbage collector runs
  * Type safe sets and arrays of enums; this was cool:
    "Internally the set works as an efficient bitvector."
  * Unified Call Syntax, so mystr.len() is equivalent to len(mystr)
  * Good performance -- not placing too much emphasis on this,
    but it's faster than C++ in his benchmark
  * Compile to JavaScript
[+] jug|11 years ago|reply
I find the easiest way to think of Nim is "A Python-style language that compiles to C". Of course, the truth isn't as simple as that. But I like it, especially how pragmatic the language is. It doesn't try to "prove" a philosophy like Rust, but I think it gains other advantages in return.
[+] madez|11 years ago|reply
I'd say that it feels more like a scripting language in comparison to Rust and is more approachable than Haskell or Erlang while being fast since it's compiled to C.
[+] guelo|11 years ago|reply
It's closer to Go than Rust since it is garbage collected. It's statically typed, has OO features and first-class functions. It compiles to C making it pretty portable.
[+] mwcampbell|11 years ago|reply
The binary size difference is quite striking. Linux distro packagers are going to like Nim, I think.
[+] vezzy-fnord|11 years ago|reply
I couldn't tell if the Nim result was actually statically linked. If it was, then it almost certainly wasn't using glibc, because the binary would certainly be much fatter.
[+] beagle3|11 years ago|reply
Ubercool.

Question about nim: from looking at https://github.com/def-/nimes/blob/master/src/nes/cpu.nim , I wonder: is there way to give the no. of cycles and instruction encoding with the "op" template, so those 256 byte arrays get built automatically?

[+] def-|11 years ago|reply
The problem is that an operation can have variable number of cycles depending on what addressing mode it uses as each opcode.
[+] dom96|11 years ago|reply
I'm not the author but I don't see why not, you can just have an extra param to the "op" template.
[+] lqdc13|11 years ago|reply
Any plans to do let people call Nim functions from Python with Python standard objects like strings/dicts/lists as arguments? This would let people write the fast parts in Nim and slow parts in Python.
[+] jboy|11 years ago|reply
Hi yes, I'm a Nim community member who's working on that.

A simple version already exists and works (for Python primitive types and Numpy arrays, via the Python C-API), but it's embedded in my company's proprietary Python+Nim (mainly Python) codebase. I'm working in my spare time to extract the relevant code as a Nim library and release it as an open-source package on Github.

If you'd like to learn more about it, or you'd like to be notified when the first release is ready, please come and discuss it on the Nim forums! http://forum.nim-lang.org/

[+] progman|11 years ago|reply
What's the point? Why not write everything in Nim?

If you depend on certain Python libs that's understandable. But it should not be too hard to translate them to Nim since the syntax of Python and Nim are not very different.

[+] gcr|11 years ago|reply
When is it appropriate to use Nim instead of Cython to rewrite hot code?
[+] mhd|11 years ago|reply
I'm quite impressed about the small amount of code required for a NES emulator. I thought they'd have to do all kinds of special casing for cartridge-specific stuff…
[+] masklinn|11 years ago|reply
The more accurate the emulator is, the less special casing there's a need for: special cases/hacks are necessary when the emulator takes shortcuts and doesn't implement the hardware features the game relied on. If the hardware features are fully implemented all games ought work without hacks. Hence bsnes (as far as I know) not needing game-specific hacks but requiring a hefty config to run.
[+] doomrobo|11 years ago|reply
For anyone who was wondering what I was: porting from libSDL calls to drawing on an html canvas is done automatically by emscripten