About The Project
Since I found out about system emulation, I have been super fascinated with it. I’m building this CHIP-8 emulator (interpreter…?) to get started since the EmuDev community tends to suggest it for first-timers. So what is the CHIP-8? The CHIP-8 is an abstract machine (not unlike the JVM) that was first used to play games on the COSMAC VIP in the 1970s. I thought while considering this project, “Why not implement an abstract virtual machine by using the implementation of a different abstract virtual machine?", and thus, this project came to be!
- Thorough Debug UI (panels to monitor registers, stack, timers, program counter, etc…)
- Save/Load from savestates
- Variable CPU clock speed
- Pause/Resume/Reset Emulation
- Load ROMs from file or from list
I haven’t used Java extensively, and this project hardly made me an expert, but I have become much more familiar with the language as a result. There were some pretty complex parts to making this emulator, the most challenging of which had to do with the Event dispatch thread and the opcode 0xFX0A. Anyone familiar enough with the CHIP-8 to recognize opcodes might see where this is going: any time the CPU interpreted 0xFX0A, it paused the thread, which meant that input couldn’t be read.
Opcode 0xFX0A is pretty simple. At least, my implementation is. It tells the CPU “hey, why don’t you
sleep() until a key is pressed?". This becomes a problem in a single threaded application since continuous calls of
sleep() end up blocking IO, so the program is never able to tell when a key is pressed. So in plainspeak, an infitite loop. I ended up fixing the issue by dispatching UI/IO events on the
Event Dispatch Thread provided by swing, while running other logic on a separate thread. This was a pretty nice solution, but I do wish I would’ve spent more time planning the implementation before jumping right into the code. This brings us to the biggest thing I learned:
The Importance of Planning
As a Physics graduate (now aspiring developer) I haven’t had the proper training in computer science fundamentals and best practices for Object-Oriented Programming (OOP). This isn’t to say I suck (I mean, I could be better), but that I didn’t even know what to consider while building this application. This lack of planning - in a Java project, mind you - resulted in a mess of everything being public. This wasn’t a huge issue for this small project, but in a larger project it could manifest as having pieces of code that are insanely complex.
For example, say there is a method in CPU called
initializeMemory() that does the heavy lifting of setting the memory, register, timers, etc… all to 0. Now we make the method public and call it from the CPU constructor upon startup AND call it from the GUI panel anytime someone presses reset. Seems like a win-win. Some time passes, and it is decided that the current implementation of reset is not what is needed; the application actually needs to preserve some memory upon resetting the system! now there are two options, neither of which are super enticing:
Expose a new method in CPU called
reset(). If the CPU and GUI panel are the only objects using
initializeMemory(), great! That means this was caught early, and the code isn’t all tangly and gross. But imagine every single one of your test cases calls
initializeMemory(). We can’t just make the old method private, or we can’t run the tests! Now there are two public methods that are confusingly similar. This branches into a subdecision of refactoring tests or deal with ambiguity. The former makes the code maintainable but takes a lot longer and the second is a quick fix that makes additional changes more complex, but a little bit of planning would have circumvented this entire fiasco. Needless to say, I have become acquainted with the concept of technical debt. Oh yeah, and option two…
I lied, sorry. The two options are really just one option in a trenchcoat that will either mug you or make you balance their checkbook. Just do things right and plan next time.