RBoy: A Gameboy Emulator in Rust
github.comSee also https://github.com/Gekkio/mooneye-gb which is more famous for its exhaustive test suite than its emulation accuracy.
Oh that's a really cool approach. I'm not much into the world of emulators - people probably have done this a bunch before, but I guess I've never thought about it - nice to see it presented here so systematically!
Even just collecting that set of devices shows commitment.
I had a quick look at the big match expressions, and this does not look like this project has been through cargo fmt (it would add +2000 lines of code for the whole project). Which is probably a good choice, but as someone who's been looking at this, I wonder if the tradeoffs made in rustfmt are the right ones (note: I have no idea how I would make this better for the general case).
For those looking to see an example of the match expressions:
https://github.com/mvdnes/rboy/blob/9f6b3bc47311ba687326bfff...
This process of matching on opcode and doing a marginally different version of the same basic few operations on one of a set of registers is something that is _much_ easier to do when you're able to see all the opcodes and activities in a densely packed set of lines like this.
(The start of the opcodes that I linked are not the best example of this, but they get more regular the further down the file you go. See https://github.com/mvdnes/rboy/blob/9f6b3bc47311ba687326bfff... )
Beyond knowing that they exist, I haven't explored macros in rust, but I'm curious if they could be of help here. But using cargo fmt, and spreading each of those lines into 3-10 lines would be awful, and would definitely lead to me making mistakes and not noticing typos.
I can't say much about the use of macros, however there are some SM83-specific patterns you can take advantage of if you decode instructions using octals like what I do in my own gameboy emulator here [1]
[1] https://git.musuka.dev/paoda/gb/src/branch/main/src/instruct...
Rust has two flavours of macro. Declarative or "by example" macros, and Procedural ("proc" for short) macros.
Declarative macros might be useful here, at least to cut down on the mindless repetition somewhat. These are fairly hygienic (a technical term meaning that if variable names in the macro are the same as variable names in the code using the macro, this doesn't make them the same variable) and so pretty safe to use but limited.
Procedural macros can do almost anything, they're Rust code that runs inside the compiler when your program is compiled, so e.g. they can have arbitrarily complicated behaviour including completely breaking compilation. You could definitely fix this with proc macros, but, that's not necessarily a good idea.
I can’t speak for the author, but personally I hate tools like go fmt, es-prettify and cargo fmt. I’d never use this stuff by choice because they delete the work I do to make my code easier for me to read. For example, I use extra blank lines to separate different parts of a function, or separate groups of functions visually. These sort of tools like to delete vertical white space, which hurts readability. In javascript I’ve seen plenty of very readable ternary operators (possible with the correct layout) replaced with unreadable junk by the prettifier.
It’s soulless to make all code look like bland corporate cardboard. It appeals to our OCD perfectionist tendencies but in my experience provides very little real value. Software is like writing. It can’t help but express how the author thinks about their code. These tools try to iron that personality out - and for what reason? Who cares in javascript if some files use semicolons and some don’t? Who cares if my where clause is on the same line or the next? The compiler doesn’t care, and neither do I.
(I will grant that all files in a project should have consistent white space, but you don’t need cargo fmt for that.)
> Software is like writing. It can’t help but express how the author thinks about their code. These tools try to iron that personality out - and for what reason?
A single-author novel can have personality. A 100-author 30-volume encyclopedia, less so. Whenever I set up a new single-author codebase, I set up as little automatic code formatting as I please. For collaborating with other developers, I will enforce as much as possible as early as possible.
I enforce formatters wherever possible after working on codebases with 20+ years of people imposing their own unique opinion on where spaces and braces should go. Worse, arguing about what the spacing should be, and making noisy diffs because people change previous peoples styling.
The formatter has no opinion. It follows rules. It doesn't work perfectly everywhere. It works enough. I would argue caring about exact bespoke spacing of all code is the perfectionism you mention.
There is no reason why each of us can't have the formatted according to one's taste (=what works best), instead of this "communism". It's just a matter of IDE support that is lacking.
The IDE should apply user formatting preferences upon reading in the file, and apply the std formatting while writing it (with the aim of having diffs work well). That would make good use of auto-formatters.
Okay, but the IDE support is lacking so that workaround isn't available. If you want that functionality instead you'll have to (1) add it to your IDE (2) force your team to use your favourite IDE instead of their favourite IDE. Insisting on some alternate reality where you have different tools than you actually have isn't helpful
> communism
It's in precisely zero ways like communism and that really doesn't help the clarity of the rest of your post.
This is bad idea, so you want your code to not looks like what's in the repo?
I think it's bad without the rest of the supporting toolchain but it's not axiomatically bad. There are languages with structured editors (e.g. https://hazel.org/), people can use different tabstop settings and even just different editor colours and wrapping settings and I think we can agree that doesn't doesn't hurt anything. Enforcing an automatic formatter is more or less identical to this anyway: you can write your code using whatever style you want but what ends up in the repo is equivalent to the compiler but not identical to what you wrote. You can imagine running your own local formatter on the code before you edit it and running the global formatter afterwards and an observer being unable to tell that that's happened.
Who said that?
> for what reason?
Perhaps you do not remember reading through 10k lines of PHP source that has been formatted by someone who has entirely different ideas about what good formatting is than almost everyone else on earth (or perhaps you never have!), but if you did remember the advantage of a single formatting standard that is ubiquitous would be very clear.
while i agree with you on some parts (ternary operators for example), i love the autoformatters for two reasons:
1. it makes it just plain easier to read others code. it's hard enough to have to grasp the logic behind everything, if they have wildly different formatting on top of that it gets substantially worse (incidentally, this was given as one of the reasons why go was such a success in the open source world).
2. i've been able to get a couple of archaic linter rules dropped, i.e. the brackets around single statement conditionals.
now with an auto formatters indentation fixes, this mistake becomes instantly obvious and thus a non-issue.// for this reason brackets were mandatory if (a == b) doSomething(); doSomethingElse();Disappointed by the downvotes here. While formatters help in most instances, sometimes you should break convention for the sake of emphasis or readability. Anyone here can come up with a good example for themselves. Consistency is the hobgoblin of little minds. Patterns and rules help but no rule should be absolute, judging people's quality of code is like judging people for their dress or their vocabulary. While they are important they are certainly not sufficient for a judge of one's character.
Exactly, that's why e.g. C++ projects have some personality feel while Java codebases feel like all the same corporate bland. Go is meant to be Java replacement in corps, so...
rustfmt does not delete vertical whitespace in that way.
I think the core issue here is mainly that a giant match isn't the best way to express opcode tables, and jump tables are just a bit more idiomatic here as in other languages. I've also been working on an emulator for the gameboy in Rust (https://github.com/jawline/Mimic, I wanted to be able to draw gameboy games to the command line) and I think the use of a jump table made it a bunch simpler / easier to refactor (https://github.com/jawline/Mimic/blob/9463b658ebf006b2369d2b...).
You need formatters with options so you set the options to fit your preferences and then when new people come in to the project you just say 'The formatter has no opinions, it just formats'
Rust really shines on this kind of job. Code is clean, simple and modulated.
Where do I find games to test this out?
I'm working on one too! Just got to the milestone of being able to scroll the Nintendo® logo. I refer to rboy a lot, it's been helpful to see another implementation.
A rust port for the gameboy, that would have been impressive...
There's some ongoing work on a LLVM whole-program backend for the MOS 6502, which can already compile some Rust code (discussed https://news.ycombinator.com/item?id=28581812 ). If that's successful (it does require lots of new optimization passes to match the effectiveness of hand-written code), it would be quite easy to have a backend for the Z80 as well.
A Z80 backend could probably reuse our static stack allocation, but that's about it. It's definitely useful, but the time-consuming part of writing LLVM-MOS's code generator has ended up being... writing LLVM-MOS's code generator. The static stack stuff is maybe like 250 lines of code, and I haven't had to touch it in months.
There is a rust port for the game boy advance https://github.com/rust-console/gba
there's only a barely working, undocumented backend for LLVM on the z80.
You could probably do this with mrustc.
You could, but given that even C and Pascal hardly take advantage of Z80, and have to be used as some kind of fancy macro Assembler, better just deal with Z80 Assembly directly.
But then I am biased, having the Z80 opcodes burned in my brain due to the Speccy days.
I believe it also tosses everything on the stack which is certainly not optimal for the z80
The Game Boy doesn't use a Z80 though. It's based on a SM83 core, an obscure Sharp design.
It's essentially a stripped Z80 without the DD-, ED- and FD-prefix instruction ranges. The core instruction block and CB-prefix range is (nearly) the same.
See the other responses, but my comment was really suggesting that if LLVM barely supports Z80 then the GameBoy CPU is a hopeless lost cause.
True, but the instruction set is very close, so a Z80 implementation is a good starting point.