Prototype for DreamPotato connectivity#1989
Conversation
|
It looks like trying to use
Very likely there is a better way of accomplishing what I was trying to do. Essentially I just wanted to poll the socket regularly to check if the server has sent a reset message. I wasn't sure if the backslashes in the |
|
Forward slashes should be used everywhere since backslashes are only valid on windows. There are other issues but I need to look at the code more closely. |
|
Trying to make some progress with this change.
|
My work has been largely centered around the idea of using a network VMU, which is likely more-less what your "remote VMU" idea entails, it's still unfinished but if I can get it all working properly with dream potato it should serve that purpose. Naturally getting all of it working for libretro without dreamlink has required some workarounds and that's what I've primarily been chasing |
|
Because dreamlinks today currently work by editing the MapleDevices array, and replacing ordinary devices with "DreamLink devices", it seemed like adding a new maple device type was not the easiest way to go here. Instead what I have tried, is to add a checkbox per-port, to "enable network expansion devices". Each port which has this box enabled, will create a DreamLink upon game startup and try to connect via TCP. Also, any dreamlinks which are "owned" by specially recognized DreamConn or DreamPicoPort controllers, will "override" a dreamlink created by the checkbox. And, such dreamlinks will never be destroyed based on the checkbox setting. Basically it was a goal that users of those devices, do not need to think about this setting, and the right thing will happen for them regardless of whether they checked the box or not. I also tried to distinguish carefully "connecting/reconnecting" versus "refreshing". Refreshing is only done with dreamlinks which are already connected, and is consciously intended to be a "cheap" operation, so, it doesn't block on i.o., it only checks whether a particular message was already received in the socket buffer, telling us to refresh the expansion devices. There are more bugs to iron out with some manual testing and TODOs and such to address. But any feedback you may have on whether the general approach here seems OK would be much appreciated. dreamlink-setting.mp4 |
…k telling us it doesn't have the device
| if (ggpo::active()) | ||
| ggpo::nextFrame(); | ||
| } catch (...) { | ||
| } catch (const std::exception& e) { |
There was a problem hiding this comment.
I made this change because it simply made it a little easier to debug an issue from a few months back. Happy to revert if preferred.
| maple_io_connected = false; | ||
|
|
||
| #if !defined(_WIN32) | ||
| if (isForPhysicalController()) { |
There was a problem hiding this comment.
I didn't test the non-windows path where this is false yet. Have to get cross-platform builds of DreamPotato working first to be able to test it meaningfully.
There was a problem hiding this comment.
I did test this more recently with a local Mac build of DreamPotato and it works.
| #include <array> | ||
|
|
||
| #if (defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(TARGET_OS_MAC))) && !defined(TARGET_UWP) | ||
| #define USE_DREAMCASTCONTROLLER 1 |
There was a problem hiding this comment.
Moved to build.h because I wanted to use this as a general condition for whether to include DreamLink functions, import DreamLink related headers etc or not. The latter particularly required this, since the header I want to conditionally import, can't be the one that defines the condition.
| std::shared_ptr<maple_device> dev = MapleDevices[bus][i]; | ||
|
|
||
| if ((dreamlink->getFunctionCode(i + 1) & MFID_1_Storage) || (dev != nullptr && dev->get_device_type() == MDT_SegaVMU)) | ||
| if ((dreamlink->getFunctionCode(i + 1) & MFID_1_Storage)) |
There was a problem hiding this comment.
This fixes bug where we would create a DreamLink device despite the DreamLink telling us it doesn't have the device, because the user settings caused an ordinary Maple device to get created for this slot beforehand.
Without this change, there was a tendency for the DreamLink to report nothing in slot 1, but for flycast to put a DreamLinkVmu in that slot anyway. Then when the game started to try and communicate with that VMU, we would get bad behavior. IIRC, the flycast main thread would block waiting for a reply in the expected form, making the game unresponsive, which would continue until the emulated VMU was "plugged in".
There was a problem hiding this comment.
This certainly makes sense. That OR condition is a bit too heavy-handed, possibly a remnant of something I needed during testing.
|
It looks like CI is blocked on an apple homebrew issue. |
|
@flyinghead @Tails86 could you please take a look when you are available? |
Thanks! This looks super helpful. I don't think I understand yet how the real Dreamcast detects expansion device changes on the fly. Is the Dreamcast sending messages regularly, even when it isn't actually performing any operations on the expansion devices, and noticing changes to those sender address bits? |
Yes, the host (Dreamcast) always initiates each communication, so it is polling the controller on each frame for button data. Every packet has this header information in it, so the button data back from the controller also tells the Dreamcast what sub-peripherals are present on that controller. |
Co-authored-by: James <jmsmith86@gmail.com>
Work around brew change: Homebrew/brew#20414
…ice when a DreamLink position is vacant
|
Had some more time to work on this today. I tried implementing the behavior to choose a new "active DreamLink" for a "vacated port", when a controller's port is changed, based on #1989 (comment). I added a bit of UI to indicate which gamepads are "active" or not. e.g. in this scenario, both DreamLinkGamepads are on the same port and only one is "active": Then if you change the port on the active controller, both become active: There are still bugs to work out, but, I hope within another week or so I can have them ironed out. |
In absence of this change I noticed strange return values from 'recv()'. For example, 'len == -1' and 'errno == 0'. In some cases 'len == -1' would come back, and on the next attempt I would get 'len == 0'. I found it comes down to a need to synchronize when using the socket. 'refreshifNeeded()' has a similar issue which I had solved in the past by ensuring the lock is held. Not sure why it didn't occur to me as necessary here also, until now.
`refreshIfNeeded` was only tearing down DreamLinkDevices for the single DreamLink identified as needing refresh. But `maple_ReconnectDevices()` applies to all the devices. If we call `maple_ReconnectDevices()` without first calling `tearDownDreamLinkDevices()` for all active DreamLinks, we get into an inconsistent state, where a subsequent call to `createDreamLinkDevices()` sees an existing device in the `dreamLinkVmus` list, and therefore doesn't update `MapleDevices`. The solution I am going with, is to ensure that if *any* DreamLink needs devices refreshed, then `tearDownDreamLinkDevices()` is called for all the active DreamLinks, before `maple_ReconnectDevices()` is called. As a "side benefit" of this, we can delete `dreamLinkNeedsRefresh`. In the path where we used to check that, we will always create devices for all the active DreamLinks now. There's no way to reduce work by trying to skip refreshing the ones whose configuration didn't change. Also, `allDreamLinks` is renamed to `activeDreamLinks`, to indicate it stores only the active DreamLink for each port, and `DreamLink::refreshIfNeeded()` is now `DreamLink::needsRefresh()`, to reflect that while we do check for a changed device configuration, we don't actually change any of the MapleDevices.
|
Thank you for trying it out @Tails86. I have manually tested a bunch of behaviors and tried to iron out remaining bugs as much as possible. Could you please take another look when you have some time? |
|
Sweet, I will take a look On a side note, I was taking a look at what kind of features LibRetro has to implement bespoke interfaces like the maple bus, and I found this:
https://docs.libretro.com/development/cores/developing-cores/#environment-callback May possibly be a path towards getting these features in libretro? |
Tails86
left a comment
There was a problem hiding this comment.
I like these changes, and they do seem to work. I like the addition of connection status within settings.
Only a couple of minor comments.
core/sdl/dreamlink.h
Outdated
| // The active DreamLink, if any, for each port. | ||
| // Note that multiple gamepad DreamLinks may exist for a given port, in which case only one is active. | ||
| extern std::array<std::shared_ptr<DreamLink>, 4> activeDreamLinks; | ||
|
|
||
| // Creates and destroys DreamLinks according to config settings. | ||
| // Attempts to connect/reconnect DreamLinks which are not connected. | ||
| // Returns true if any new connection was established. | ||
| // Note that this doesn't createDreamLinkDevices for DreamLinks created by this function. Caller is responsible for that. | ||
| bool reconnectDreamLinks(); | ||
|
|
||
| void refreshDreamLinksIfNeeded(); | ||
| void createAllDreamLinkDevices(); | ||
| void createDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink, bool gameStart, bool stateLoaded); | ||
| void tearDownDreamLinkDevices(std::shared_ptr<DreamLink> dreamlink); |
There was a problem hiding this comment.
This seems to be moving into the right direction in connecting DreamLink to maple_devs. I assume these are declared here so that "dream link" functionality may be accessed from including dreamlink.h. They are then defined in maple_devs.cpp in order to access the internal data there.
What would you think about at least moving activeDreamLinks to a public, static member of DreamLink? The rest of these organizational issues could be solved down the line.
There was a problem hiding this comment.
Yes, the particular placement of the declarations is being done for the reasons you describe.
Moving to static members works for me. I think it's good for these to be contained "in something" rather than being in global scope. I am hoping to send a follow-up PR sometime down the line to clean that up, fill in some functionality gaps, and so on.
Co-authored-by: James <jmsmith86@gmail.com>
|
@flyinghead could you please take a look when you are available? |
|
I had a quick look and it looks good to me so far. I can't really test it though so I'll have to trust you on this. I'll try to spend more time on this PR later today. |




Related to #1988
See https://github.com/RikkiGibson/DreamPotato?tab=readme-ov-file#flycast-integration-prototype for setup instructions