Elmboy, a Nintendo Game Boy Emulator

Hi folks!

I’m very excited to make my side-project public which I’ve been working on for some time now. It’s an emulator for the original Nintendo Game Boy! You can try it out here: https://malax.github.io/elmboy (Works best in Chrome) or read the source here: https://github.com/Malax/elmboy

It’s been a long ride to get to the point where all the basics are implemented and it’s in the bare minimal state to be shown to the public. It’s still full of bugs and dreadfully slow and I will try to fix all those issues in the next weeks/months.

I had to compromise on code beauty for performance reasons, but as I will continue to profile everything and hope to gain some in-depth knowledge about JIT compilers while doing so, I hope I can reintoduce nicer types and more functional code. The project was refactored completly a lot of times already.

Let me know what you think! :slight_smile:

37

32 Likes

Absolutely excellent and inspiring work! Going to be taking a deep look at this to learn more about elm performance and gameboy emulation! It is awesome that you decided to share this and a huge thanks from me for doing so!

3 Likes

This is fantastic! O_O
How did you deal with binary data?

1 Like

Thank you @xarvh!

Yeah, binary data was and still is one of my pain points.

At first, I had opaque Word8 an Word16 types that used an Int to store the data. Both the Word8 and Word16 module had all the basic operations like and, or, xor, add or sub as I obviously could not use the functions from Basics and Bitwise with my new custom types. Relativly close to the end, I removed all of that due to it’s clunkyness (as the codebase is heavy on bitwise and arithmetical operations) and performance, as any access to WordN types required unwrapping. I had some hope when 0.19 came out that --optimize would help with that case, but as the types where opaque, it didn’t, and they had to go away.

So, as for now, all binary data values are just Ints. I really dislike that as I could mix up 8 and 16 bit values and the situation gets even worse when you’re dealing with stuff like signed bytes where the binary encoding (two’s complement) is relevant for arithmetic.

I plan to at least to reintroduce a custom type for those cases again and unwrap them everywhere I need access to the value inside. This way, I can use Basics and Bitwise and --optimize should remove the wrapping from the compiled source.

Another problem is pixel data for the screen. As I have to draw the screen line-by-line, emulating the way a real Game Boy would do it (as many games depend on render timings to produce effects), I need to store each pixel separatly and draw them to a <canvas> later. First, I just serialized the List of pixels into json to hand them to a port, but that took way to long. Right now, I encode batches of 16 pixels into a single integer (which fit perfectly into a 32bit int as the color depth of the game boy is two bits) send those to JS and unpack them again, before pushing them into the canvas.

Summarized, it’s unwieldy and clunky. But Elm wasn’t supposed for tasks like this and I knew what I was getting into. :wink: Maybe sometime in the future we will have a way to encode raw binary data in Elm and much of the issues will go away.

BTW, while I was in the earlier stages of development I listend to the Elm Town episode where you talked about Herzog Drei. In the end, you said (paraphrased): “be ambitious, do something that is big, massive and complicated. It’s totally worth it and Elm can do it.”. I smiled when I heard that as it somehow validated my insane project I was working on. Thanks for that! :slight_smile:

7 Likes

Ow. My pleasure. O_O

Absolutely incredible. Would love to have a talk about that @ elm europe :wink:

2 Likes

This is horrifying and amazing and OH MY GOD YOU INCLUDED A SCREENSHOT OF KIRBY! It’s more often the Mario/Zelda/Pokemon trinity when it comes to Nintendo nostalgia, and Kirby’s always been my favourite by far.

1 Like

Amazing that you’ve done this, well done!

There’s some work happening on that but it’s not published on the package manager and there has been no indication when (or if) it will be. There were some other threads here about it recently.

I’m curious about the problem with the opaque types, what was the issue? The optimization happens during code generation and at that stage there’s no difference between things being opaque or not. It should still end up as an plain number in JS as long as it’s a single-constructor type. There might be a way to get it working properly for you.

1 Like

Hey @Brian_Carroll ! Thank you :slight_smile:

When I first saw this document I almost jumped out of my seat. :wink: But in the end it seems that it tries to solve a different problem. The way I understood it, it’s like Json.Encode/Json.Decode but the output format is binary.

That won’t help so much with my use-case I’m afraid. The document states:

This API is not intended to work like Int8Array or Uint16Array in JavaScript. If you have a concrete scenario in which you want to interpret bytes as densely packed arrays of integers or floats, please describe it on https://discourse.elm-lang.org/ in a friendly and high-level way.

I feel that my use-case is not very important in the grand scheme of things. It would be nice to have native binary types and operations built into Elm, but it’s applications are pretty niche in the world of webapps I assume. It might become more important when we gain access to JavaScript APIs that are very low level like canvas or webaudio.

I’m curious about the problem with the opaque types, what was the issue?

I did not describe my issue very well, my apologies. :slight_smile: You’re right, the fact that the types are opaque is not the issue itself. But I would have to wrap all Bitwise and arithmetic operations for each type in my own functions, possibly creating overhead (to be honest, I never benchmarked it - so the overhead might be very low) while doing to. The unwrapping/wrapping of the value itself should be optimised away, just as you said.

But even without any significant overhead, my main issue is it’s clunkyness. I often have literal byte values in my code, which I then have to wrap in my custom type before I can do math with them. So instead of writing value + 0x3F I would have to write Byte.add value (Byte.fromInt 0x3F) which is not so pleasant, especially when you have this all over the place.

But I’m very certain that I can find a solution that strikes a nice balance between performance and nice code. For the future, I plan to document the code very well and keep it as clean and organized as possible so that curious folks can dive into it, without knowing too much about emulators and/or Game Boy hardware internals.

How could I forget about the cute, tiny and fluffy Kirby @aidalgol? :slight_smile:

I would be open to dip my toes into speaking @tibastral, what would be the best way to contact you? :slight_smile:

2 Likes

thibaut@milesrock.com

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.