mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
Core/HW: Support for BBA (IPC) in macOS.
Follow-up to #13870, which introduced a new **Broadband Adapter (IPC)** in the **SP1** slot in the GameCube section in the settings that allows local play with multiple Dolphin instances running in the same system. Back then, only support for Linux and Windows was introduced due to [cpp-ipc](https://github.com/mutouyun/cpp-ipc) only offering support in those platforms. Since #14208, the adapter is supported also on FreeBSD. This new work adds support for macOS using a separate implementation with [ZeroMQ](https://zeromq.org). Summary table: | System | Supported | Backend | | :-----: | :-------: | :--------------------- | | Linux | ✅ | cpp-ipc (since #13870) | | Windows | ✅ | cpp-ipc (since #13870) | | FreeBSD | ✅ | cpp-ipc (since #14208) | | macOS | ✅ | ZeroMQ | | Android | ❌ | | Although adding support for Android may be technically feasible, launching two Dolphin instances within the same Android system may be both challenging and impractical.
This commit is contained in:
parent
bd6ea9a9a1
commit
bf79b641a1
8
.gitmodules
vendored
8
.gitmodules
vendored
@ -14,6 +14,10 @@
|
||||
path = Externals/libusb/libusb
|
||||
url = https://github.com/libusb/libusb.git
|
||||
shallow = true
|
||||
[submodule "Externals/libzmq/libzmq"]
|
||||
path = Externals/libzmq/libzmq
|
||||
url = https://github.com/zeromq/libzmq.git
|
||||
shallow = true
|
||||
[submodule "Externals/spirv_cross/SPIRV-Cross"]
|
||||
path = Externals/spirv_cross/SPIRV-Cross
|
||||
url = https://github.com/KhronosGroup/SPIRV-Cross.git
|
||||
@ -34,6 +38,10 @@
|
||||
path = Externals/VulkanMemoryAllocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
shallow = true
|
||||
[submodule "Externals/Cross-Compatible-FileLock-Windows-and-Linux"]
|
||||
path = Externals/Cross-Compatible-FileLock-Windows-and-Linux
|
||||
url = https://github.com/KaganCanSit/Cross-Compatible-FileLock-Windows-and-Linux.git
|
||||
shallow = true
|
||||
[submodule "Externals/cubeb/cubeb"]
|
||||
path = Externals/cubeb/cubeb
|
||||
url = https://github.com/mozilla/cubeb.git
|
||||
|
||||
@ -788,6 +788,8 @@ add_subdirectory(Externals/watcher)
|
||||
|
||||
if(NOT ANDROID AND NOT APPLE)
|
||||
add_subdirectory(Externals/cpp-ipc)
|
||||
elseif(APPLE)
|
||||
add_subdirectory(Externals/libzmq)
|
||||
endif()
|
||||
|
||||
########################################
|
||||
|
||||
1
Externals/Cross-Compatible-FileLock-Windows-and-Linux
vendored
Submodule
1
Externals/Cross-Compatible-FileLock-Windows-and-Linux
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 42ffdc24b06a85383e1b7a20e09931d7849533e7
|
||||
17
Externals/libzmq/CMakeLists.txt
vendored
Normal file
17
Externals/libzmq/CMakeLists.txt
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
set(ENABLE_DRAFTS OFF)
|
||||
set(BUILD_TESTS OFF)
|
||||
set(WITH_DOCS OFF)
|
||||
set(BUILD_SHARED OFF)
|
||||
set(BUILD_STATIC ON)
|
||||
|
||||
add_subdirectory(libzmq)
|
||||
|
||||
dolphin_disable_warnings(objects)
|
||||
dolphin_disable_warnings(libzmq-static)
|
||||
|
||||
if (NOT MSVC)
|
||||
target_compile_options(objects PRIVATE "-fexceptions")
|
||||
target_compile_options(libzmq-static PRIVATE "-fexceptions")
|
||||
endif ()
|
||||
|
||||
add_library(libzmq::libzmq ALIAS libzmq-static)
|
||||
1
Externals/libzmq/libzmq
vendored
Submodule
1
Externals/libzmq/libzmq
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 7a7bfa10e6b0e99210ed9397369b59f9e69cef8e
|
||||
4
Externals/licenses.md
vendored
4
Externals/licenses.md
vendored
@ -10,6 +10,8 @@ Dolphin includes or links code of the following third-party software projects:
|
||||
[bzip2 license](https://www.sourceware.org/git/?p=bzip2.git;a=blob;f=LICENSE;hb=HEAD) (similar to 3-clause BSD)
|
||||
- [cpp-ipc](https://github.com/mutouyun/cpp-ipc):
|
||||
[MIT](https://github.com/mutouyun/cpp-ipc/blob/master/LICENSE)
|
||||
- [Cross-Compatible-FileLock-Windows-and-Linux](https://github.com/KaganCanSit/Cross-Compatible-FileLock-Windows-and-Linux):
|
||||
[MIT](https://github.com/KaganCanSit/Cross-Compatible-FileLock-Windows-and-Linux/blob/main/LICENSE)
|
||||
- [cubeb](https://github.com/kinetiknz/cubeb):
|
||||
[ISC](https://github.com/kinetiknz/cubeb/blob/master/LICENSE)
|
||||
- [Discord-RPC](https://github.com/discordapp/discord-rpc):
|
||||
@ -44,6 +46,8 @@ Dolphin includes or links code of the following third-party software projects:
|
||||
[University of Illinois/NCSA Open Source license](http://llvm.org/docs/DeveloperPolicy.html#license)
|
||||
- [LZO](http://www.oberhumer.com/opensource/lzo/):
|
||||
[GPLv2+](http://www.oberhumer.com/opensource/gpl.html)
|
||||
- [libzmq](https://github.com/zeromq/libzmq):
|
||||
[MPL 2.0](https://github.com/zeromq/libzmq/blob/master/LICENSE)
|
||||
- [mGBA](http://mgba.io)
|
||||
[MPL 2.0](https://github.com/mgba-emu/mgba/blob/master/LICENSE)
|
||||
- [MiniUPnPc](http://miniupnp.free.fr/):
|
||||
|
||||
@ -804,6 +804,10 @@ endif()
|
||||
if(NOT ANDROID AND NOT APPLE)
|
||||
target_sources(core PRIVATE HW/EXI/BBA/IPC.cpp)
|
||||
target_link_libraries(core PRIVATE cpp-ipc::ipc)
|
||||
elseif(APPLE)
|
||||
target_sources(core PRIVATE HW/EXI/BBA/IPC_zmq.cpp)
|
||||
target_link_libraries(core PRIVATE libzmq::libzmq)
|
||||
target_include_directories(core PRIVATE "${CMAKE_SOURCE_DIR}/Externals/Cross-Compatible-FileLock-Windows-and-Linux/include")
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
|
||||
209
Source/Core/Core/HW/EXI/BBA/IPC_zmq.cpp
Normal file
209
Source/Core/Core/HW/EXI/BBA/IPC_zmq.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
// Copyright 2008 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <bit>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
|
||||
#include <FileLockFactory.hpp>
|
||||
#include <zmq.h>
|
||||
|
||||
#include "Core/HW/EXI/EXI_DeviceEthernet.h"
|
||||
|
||||
namespace ExpansionInterface
|
||||
{
|
||||
|
||||
CEXIETHERNET::IPCBBAInterface::IPCBBAInterface(CEXIETHERNET* const eth_ref)
|
||||
: CEXIETHERNET::NetworkInterface(eth_ref), m_context(zmq_ctx_new()),
|
||||
m_proxy_thread(&CEXIETHERNET::IPCBBAInterface::ProxyThreadHandler, this)
|
||||
{
|
||||
}
|
||||
|
||||
CEXIETHERNET::IPCBBAInterface::~IPCBBAInterface()
|
||||
{
|
||||
m_proxy_thread_shutdown.Set();
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_proxy_mutex};
|
||||
|
||||
if (m_proxy_publisher)
|
||||
{
|
||||
zmq_close(m_proxy_publisher);
|
||||
m_proxy_publisher = nullptr;
|
||||
}
|
||||
if (m_proxy_subscriber)
|
||||
{
|
||||
zmq_close(m_proxy_subscriber);
|
||||
m_proxy_subscriber = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
zmq_ctx_term(m_context);
|
||||
m_context = nullptr;
|
||||
|
||||
if (m_proxy_thread.joinable())
|
||||
{
|
||||
m_proxy_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool CEXIETHERNET::IPCBBAInterface::Activate()
|
||||
{
|
||||
if (m_active)
|
||||
return false;
|
||||
|
||||
const u32 linger{0};
|
||||
|
||||
m_publisher = zmq_socket(m_context, ZMQ_PUB);
|
||||
zmq_setsockopt(m_publisher, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
zmq_connect(m_publisher, "ipc:///tmp/dolphin-bba-outbox");
|
||||
|
||||
m_subscriber = zmq_socket(m_context, ZMQ_SUB);
|
||||
zmq_setsockopt(m_subscriber, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
zmq_setsockopt(m_subscriber, ZMQ_SUBSCRIBE, "", 0);
|
||||
zmq_connect(m_subscriber, "ipc:///tmp/dolphin-bba-inbox");
|
||||
|
||||
m_read_enabled.Clear();
|
||||
m_read_thread_shutdown.Clear();
|
||||
|
||||
m_active = true;
|
||||
|
||||
return RecvInit();
|
||||
}
|
||||
|
||||
void CEXIETHERNET::IPCBBAInterface::Deactivate()
|
||||
{
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
m_read_enabled.Clear();
|
||||
m_read_thread_shutdown.Set();
|
||||
|
||||
if (m_read_thread.joinable())
|
||||
{
|
||||
m_read_thread.join();
|
||||
}
|
||||
|
||||
zmq_close(m_publisher);
|
||||
zmq_close(m_subscriber);
|
||||
m_publisher = nullptr;
|
||||
m_subscriber = nullptr;
|
||||
|
||||
m_active = false;
|
||||
}
|
||||
|
||||
bool CEXIETHERNET::IPCBBAInterface::IsActivated()
|
||||
{
|
||||
return m_active;
|
||||
}
|
||||
|
||||
bool CEXIETHERNET::IPCBBAInterface::SendFrame(const u8* const frame, const u32 size)
|
||||
{
|
||||
if (!m_active)
|
||||
return false;
|
||||
|
||||
std::vector<u8> message;
|
||||
const u64 self{std::bit_cast<u64>(this)};
|
||||
message.resize(sizeof(self) + static_cast<u64>(size));
|
||||
std::memcpy(message.data(), &self, sizeof(self));
|
||||
std::memcpy(message.data() + sizeof(self), frame, static_cast<u64>(size));
|
||||
|
||||
zmq_send(m_publisher, message.data(), message.size(), 0);
|
||||
m_eth_ref->SendComplete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CEXIETHERNET::IPCBBAInterface::RecvInit()
|
||||
{
|
||||
m_read_thread = std::thread(&CEXIETHERNET::IPCBBAInterface::ReadThreadHandler, this);
|
||||
return true;
|
||||
}
|
||||
|
||||
void CEXIETHERNET::IPCBBAInterface::RecvStart()
|
||||
{
|
||||
m_read_enabled.Set();
|
||||
}
|
||||
|
||||
void CEXIETHERNET::IPCBBAInterface::RecvStop()
|
||||
{
|
||||
m_read_enabled.Clear();
|
||||
}
|
||||
|
||||
void CEXIETHERNET::IPCBBAInterface::ReadThreadHandler()
|
||||
{
|
||||
const u64 self{std::bit_cast<u64>(this)};
|
||||
|
||||
std::vector<u8> buffer;
|
||||
buffer.resize(sizeof(self) + BBA_RECV_SIZE);
|
||||
|
||||
while (!m_read_thread_shutdown.IsSet())
|
||||
{
|
||||
zmq_pollitem_t pollitem{};
|
||||
pollitem.socket = m_subscriber;
|
||||
pollitem.events = ZMQ_POLLIN;
|
||||
|
||||
const int event_count{zmq_poll(&pollitem, 1, 50)};
|
||||
for (int i{0}; i < event_count; ++i)
|
||||
{
|
||||
const int read{zmq_recv(m_subscriber, buffer.data(), buffer.size(), 0)};
|
||||
if (read < static_cast<int>(sizeof(self)))
|
||||
continue;
|
||||
|
||||
u64 id{};
|
||||
std::memcpy(&id, buffer.data(), sizeof(self));
|
||||
|
||||
const bool self_message{id == self};
|
||||
if (self_message)
|
||||
continue;
|
||||
|
||||
if (!m_read_enabled.IsSet())
|
||||
continue;
|
||||
|
||||
const u8* const frame{buffer.data() + sizeof(self)};
|
||||
const u64 size{read - sizeof(self)};
|
||||
|
||||
std::memcpy(m_eth_ref->mRecvBuffer.get(), frame, size);
|
||||
m_eth_ref->mRecvBufferLength = static_cast<u32>(size);
|
||||
m_eth_ref->RecvHandlePacket();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CEXIETHERNET::IPCBBAInterface::ProxyThreadHandler()
|
||||
{
|
||||
while (!m_proxy_thread_shutdown.IsSet())
|
||||
{
|
||||
// The first instance that acquires the filelock becomes the proxy.
|
||||
const auto filelock = file_lock::FileLockFactory::CreateTimedLockContext(
|
||||
"/tmp/dolphin-bba-filelock", std::chrono::seconds(1));
|
||||
if (!filelock)
|
||||
continue;
|
||||
|
||||
void* proxy_subscriber;
|
||||
void* proxy_publisher;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock{m_proxy_mutex};
|
||||
|
||||
const u32 linger{0};
|
||||
|
||||
m_proxy_subscriber = zmq_socket(m_context, ZMQ_XSUB);
|
||||
zmq_setsockopt(m_proxy_subscriber, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
zmq_setsockopt(m_proxy_subscriber, ZMQ_SUBSCRIBE, "", 0);
|
||||
zmq_bind(m_proxy_subscriber, "ipc:///tmp/dolphin-bba-outbox");
|
||||
|
||||
m_proxy_publisher = zmq_socket(m_context, ZMQ_XPUB);
|
||||
zmq_setsockopt(m_proxy_publisher, ZMQ_LINGER, &linger, sizeof(linger));
|
||||
zmq_bind(m_proxy_publisher, "ipc:///tmp/dolphin-bba-inbox");
|
||||
|
||||
proxy_subscriber = m_proxy_subscriber;
|
||||
proxy_publisher = m_proxy_publisher;
|
||||
}
|
||||
|
||||
zmq_proxy(proxy_subscriber, proxy_publisher, nullptr);
|
||||
}
|
||||
}
|
||||
} // namespace ExpansionInterface
|
||||
@ -478,13 +478,13 @@ private:
|
||||
const Common::MACAddress& ResolveAddress(u32 inet_ip);
|
||||
};
|
||||
|
||||
#if !defined(__ANDROID__) && !defined(__APPLE__)
|
||||
|
||||
class IPCBBAInterface : public NetworkInterface
|
||||
{
|
||||
public:
|
||||
explicit IPCBBAInterface(CEXIETHERNET* const eth_ref) : NetworkInterface(eth_ref) {}
|
||||
|
||||
#if !defined(__ANDROID__) && !defined(__APPLE__)
|
||||
|
||||
bool Activate() override;
|
||||
void Deactivate() override;
|
||||
bool IsActivated() override;
|
||||
@ -501,9 +501,50 @@ private:
|
||||
std::thread m_read_thread;
|
||||
Common::Flag m_read_enabled;
|
||||
Common::Flag m_read_thread_shutdown;
|
||||
};
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
class IPCBBAInterface : public NetworkInterface
|
||||
{
|
||||
public:
|
||||
explicit IPCBBAInterface(CEXIETHERNET* eth_ref);
|
||||
~IPCBBAInterface() override;
|
||||
|
||||
bool Activate() override;
|
||||
void Deactivate() override;
|
||||
bool IsActivated() override;
|
||||
bool SendFrame(const u8* frame, u32 size) override;
|
||||
bool RecvInit() override;
|
||||
void RecvStart() override;
|
||||
void RecvStop() override;
|
||||
|
||||
private:
|
||||
void ReadThreadHandler();
|
||||
void ProxyThreadHandler();
|
||||
|
||||
bool m_active{};
|
||||
void* m_context{};
|
||||
void* m_publisher{};
|
||||
void* m_subscriber{};
|
||||
std::thread m_read_thread;
|
||||
Common::Flag m_read_enabled;
|
||||
Common::Flag m_read_thread_shutdown;
|
||||
|
||||
std::thread m_proxy_thread;
|
||||
Common::Flag m_proxy_thread_shutdown;
|
||||
std::mutex m_proxy_mutex;
|
||||
void* m_proxy_publisher{};
|
||||
void* m_proxy_subscriber{};
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
class IPCBBAInterface : public NetworkInterface
|
||||
{
|
||||
public:
|
||||
explicit IPCBBAInterface(CEXIETHERNET* const eth_ref) : NetworkInterface(eth_ref) {}
|
||||
|
||||
bool Activate() override { return false; }
|
||||
void Deactivate() override {}
|
||||
bool IsActivated() override { return false; }
|
||||
@ -511,9 +552,9 @@ private:
|
||||
bool RecvInit() override { return false; }
|
||||
void RecvStart() override {}
|
||||
void RecvStop() override {}
|
||||
};
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
std::unique_ptr<NetworkInterface> m_network_interface;
|
||||
|
||||
|
||||
@ -143,9 +143,7 @@ void GameCubePane::CreateWidgets()
|
||||
EXIDeviceType::EthernetXLink,
|
||||
EXIDeviceType::EthernetTapServer,
|
||||
EXIDeviceType::EthernetBuiltIn,
|
||||
#if !defined(__APPLE__)
|
||||
EXIDeviceType::EthernetIPC,
|
||||
#endif
|
||||
EXIDeviceType::ModemTapServer,
|
||||
})
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user