4 ms·
Rust is a nice language for emulators in my experience, although managing the large global state emulators require while making the borrow checker happy and not
by simias 7y ago
Rust is a nice language for emulators in my experience, although managing the large global state emulators require while making the borrow checker happy and not damaging performance requires a bit of planning (unless you're willing to litter your code with unsafes).
My strategy in the end is simply to stuff the entire state in a struct that I pass around everywhere, using a more functional style instead of class methods. This way I don't have to borrow anything and I have access to the full state everywhere in the code. It doesn't make for clean OOP-style encapsulation but I found that doing that was too complicated in an emulator, you simply have too many interactions between the various modules of most consoles. For instance the DMA can write data to the GPU which can trigger an IRQ in the interrupt controller which can change the CPU state which can modify the state of a coprocessor which can lead to registers being banked into RAM etc...
Besides, the architecture of an emulator is generally constrained by the underlying hardware, so it's rare that you have to do big refactors. Having leaky interfaces is not much of a problem in practice because you don't really have to worry about "but what if tomorrow I need to emulate a Game Boy Advance with a very different GPU?".
While we're showing off Rust emulator projects I've spent the last few days writing an emulator for the PlayStation's CD-ROM sub-CPU: https://gitlab.com/flio/psx_cd https://gitlab.com/flio/psx_cd . I hope to be able to integrate it into my PlayStation emulator when it's done, which would save me from having a super hacky high level CD interface like most other PSX emulators out there.
- flohofwoe 7y agoOne idea I had in my own modular emulators a while ago was to reduce the "state that's passed around" to the input/output pins of the microchip emulators the higher level system emulator is built from. This pin-state often fits into a single 64-bit integer (at least for typical 8-bit home computers), and another advantage is that the emulator's code structure can be kept very close to the schematics of the real hardware (the wiring between chips can be mapped 1:1 to bits in 64-bit integers). The emulators are written in C, not Rust, but I bet this approach would also work perfectly with Rust's borrow checker (since there's no shared state in memory the borrow checker shouldn't even "activate"). An emulator is essentially built from functions which take a 64-bit integer as input, and return another 64-bit integer. https://github.com/floooh/chips https://github.com/floooh/chips
- simias 7y agoIn my first attempt at a PlayStation emulator I did something similar to what you describe, with the shared state in a separate struct that's passed around and the rest of the code embedded in member function for the various GPU, CPU, SPU and other classes. In the end I found that it was more trouble that was worth. I didn't feel like it made the code significantly clearer and if you realized later on that something actually ought to be shared between two modules it required a ton of refactoring. Also having everything in a single struct that's passed everywhere can make debugging slightly easier. For instance if I encounter a situation where some bogus command is sent to the GPU I catch it in the GPU code and dump the CPU state right there instead of having to notify the condition upstream and add some hook in the higher layers to handle the CPU dumping for instance. It's probably not the most elegant solution but I find it simple and rather effective.
- rightbyte 7y agoYe I agree. Copying a state to not make the memory shared when the actual data is rw shared (all modules should have the same conception of what the state is) seems quite pointless. I guess a pointer to const get function and a pointer to non-const get function would suffice for making it clear which modules could manipulate it.
- AlchemistCamp 7y ago> using a more functional style instead of class methods Does Rust even have classes?
- steveklabnik 7y agoNo.
- timw4mail 7y ago`impl` methods with `self` parameters are basically class methods in feel.