|Home | Downloads | Screenshots | Forums | Source code | RSS | Donate
|Register | Log in
|< LAN multiplayer: getting there!The RTC rework is done >
RTC and ambitious plans
Oct 26th 2023, by Arisotura
Two main things this post will talk about.
The RTC, aka Real-Time Clock. The DS uses a RTC chip to keep track of the current date and time. melonDS has had some RTC emulation since the early days, because it was a requirement to get the firmware to boot, but since then, that RTC emulation has been pretty much barebones. Basically, all it does is return the current system time from the host. While it's good enough for the most basic purposes, it comes with a number of downsides.
For example, we've had several requests from end users about changing the time inside melonDS. Currently, the only way to do that is to change your computer's time. melonDS offers no UI for setting its own time. It is possible to boot into the firmware and use the settings menu there, but melonDS ignores writes to the RTC date/time registers, so it just reverts back to the host system time. Less than ideal.
This is also a problem for netplay. The basic idea behind netplay is that all players run the same emulation, and inputs are synced across them. For this to work, you need to ensure that for all players the emulation starts from the exact same state, and that the inputs are the exact same for everybody. Since games use the RTC to initialize their random number generators, all sides need to start from the exact same time, or they will desync. So the basic, "let's just return the system time" RTC won't do here.
Then there's also the fact that this RTC emulation we have is incomplete. The RTC does more than just keep the current time: it features several 'alarm' settings that can trigger interrupts, for example. libnds uses that feature to do its time counting, so if you run any libnds homebrew in melonDS, it will seem stuck at the same time.
The DS firmware also has an alarm clock feature. While I doubt anybody out there is going to use melonDS as their alarm clock, emulating it would be a nice touch.
Regardless, I started working on the RTC yesterday. The first part is to have the RTC in melonDS do its own time counting. The RTC in the DS is likely controlled by a 32768 Hz clock, as is typical for these things, so I did something similar in melonDS, as it will be a requirement for emulating the alarm interrupt features.
I used the system event scheduler in melonDS for this purpose. It is technically inaccurate, because the scheduler runs on the DS system clock (33.51 MHz), but it provides a reliable way to keep the emulated RTC in sync with the rest of the system. I had to get a bit fancy to correct for the inaccuracy from using the system clock as a base, but nothing bad.
From there, I laid down the basic idea of a clock. Have a bunch of registers for the current date and time, count one second every 32768 RTC ticks, count one minute whenever the second register reaches 60, count one hour whenever the minute register reaches 60, etc... You get the idea. As there's currently no way to tell the melonDS core which time to use on startup, it starts at 01/01/2000 00:00:00, but it does count up properly so far.
Next step will be to do some hardware tests to figure out some details. For example, the date/time registers are BCD, which means that for example a value of 24 is represented as 0x24 (decimal 36, 2*16+4). Since they are writable, I need to determine what happens if invalid BCD values are written. For example, say the register's value is 0x29, when it will be incremented, the next value will be 0x30 and not 0x2A.
0x2A wouldn't be a valid BCD value, so what happens if I try to write that to the register? It could be rounded down to 0x29, or rounded up to 0x30, or the write could be ignored entirely, or it could actually accept a value of 0x2A and behave weirdly because of it, so I need to figure this out. This is an off-the-shelf RTC chip, so chances are it's well-behaved, but it's not exactly like there are no precedents of DS hardware accepting whatever garbage inputs and doing weird shit.
Then I will have to implement all the alarm interrupt features and all, and provide a way to set the time in melonDS, and this should be good.
Then the ambitious plans.
This was an idea that we put off before due to the amount of work required, but JesseTG is giving us a much welcome boost in ambition. Basically, he is making a Retroarch port of melonDS, and a lot of the work he's putting into it is going to be very useful for netplay.
For example: when starting a netplay game, we want each side to start from the exact same state. I figured that using the existing savestate system for this purpose would be adequate: just save the initial state on the host, then broadcast that to the clients and they just have to load it, and boom, most of the work is magically done.
The problem I originally ran into was that said savestate system was designed to interact with files directly, so getting around it was hacky. JesseTG modified it to work on memory buffers, which is exactly what we need here. This change will also be welcome for Generic's Switch port, where savestates turn out to be slow due to the sheer amount of file I/O calls.
Anyway. The plans.
In its current state, melonDS is designed to run exactly one DS per instance. This proved to be a bit of an issue for local multiplayer. In the early days, the strategy was to just run multiple melonDS instances, have them toss around packets over UDP, and pray that it would work. But we do things in a more refined way now.
The ideal way to do local multiplayer on one machine would be to run multiple DS's inside the same melonDS instance. Thus, there would be little to no losses in the multiplayer data exchange. But the main issue with this is that, well, the melonDS codebase doesn't lend itself well to this. At all.
So instead, we went for the technically easier way. We just run multiple instances of melonDS and sync them via IPC mechanisms. Essentially, they use shared memory buffers for data exchange and shared semaphores for synchronization.
There are multiple issues with this design, though. First, it is not ideal from a performance standpoint. Inter-thread communication from within the same process is easier and faster than inter-process communication. The shared semaphores we need to use are named semaphores, which are a special type of semaphore and aren't really optimal for performance.
Another issue is making this work in netplay. With having to deal with multiple processes across multiple machines and keep all of it in sync, there are many moving parts and many ways things can go wrong. If we only had to deal with one melonDS instance per machine, it would make things a lot simpler.
So you can see where this is headed.
Adapting melonDS to support multiple DS's per instance.
In the current state of things, the melonDS core is largely a collection of namespaces that roughly represent the DS hardware components. Certain of the components are already classes, because they need to be instanced multiple times: the ARM CPUs, the 2D GPUs, the SD/MMC controllers for the DSi... But for the rest, it can only be instanced once, because it's just a namespace.
For what we want, all of these components need to be converted to classes. Which you guess isn't going to be a walk in the park. This is the kind of change that will conflict with every existing branch, pull request, etc. Existing ports of melonDS will need to be updated accordingly. And so on.
We're going to plan this out and take it easy. I'm realizing that after nearly 7 years of ongoing development, melonDS has become quite a big and complex project.
|11 comments have been posted.
|< LAN multiplayer: getting there!The RTC rework is done >