From 68e7fe02091bda31b49408d6a622227b8b55a243 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 7 May 2023 17:48:50 +1000 Subject: [PATCH] Netplay: Use enet for connections --- dep/ggpo-x/ggpo-x.vcxproj | 7 +- dep/ggpo-x/ggpo-x.vcxproj.filters | 10 - dep/ggpo-x/include/ggponet.h | 58 +--- dep/ggpo-x/src/backends/backend.h | 5 +- dep/ggpo-x/src/backends/p2p.cpp | 159 ++-------- dep/ggpo-x/src/backends/p2p.h | 24 +- dep/ggpo-x/src/backends/spectator.cpp | 62 ++-- dep/ggpo-x/src/backends/spectator.h | 14 +- dep/ggpo-x/src/backends/synctest.cpp | 6 + dep/ggpo-x/src/backends/synctest.h | 7 +- dep/ggpo-x/src/main.cpp | 46 +-- dep/ggpo-x/src/network/udp.cpp | 125 -------- dep/ggpo-x/src/network/udp.h | 61 ---- dep/ggpo-x/src/network/udp_msg.h | 15 +- dep/ggpo-x/src/network/udp_proto.cpp | 223 ++------------ dep/ggpo-x/src/network/udp_proto.h | 45 +-- dep/ggpo-x/src/poll.cpp | 72 ----- dep/ggpo-x/src/poll.h | 56 ---- src/core/core.props | 4 +- src/core/netplay.cpp | 417 +++++++++++++++++++++++--- src/core/netplay.h | 10 +- src/duckstation-qt/qthost.cpp | 6 + src/frontend-common/common_host.cpp | 4 +- 23 files changed, 524 insertions(+), 912 deletions(-) delete mode 100644 dep/ggpo-x/src/network/udp.cpp delete mode 100644 dep/ggpo-x/src/network/udp.h delete mode 100644 dep/ggpo-x/src/poll.cpp delete mode 100644 dep/ggpo-x/src/poll.h diff --git a/dep/ggpo-x/ggpo-x.vcxproj b/dep/ggpo-x/ggpo-x.vcxproj index cf78e9bdf..0db68f9b3 100644 --- a/dep/ggpo-x/ggpo-x.vcxproj +++ b/dep/ggpo-x/ggpo-x.vcxproj @@ -14,11 +14,9 @@ - - @@ -34,10 +32,8 @@ - - @@ -46,7 +42,8 @@ TurnOffAllWarnings _WINDOWS;%(PreprocessorDefinitions) - $(ProjectDir)src;$(ProjectDir)include;%(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories);$(ProjectDir)src;$(ProjectDir)include + %(AdditionalIncludeDirectories);$(SolutionDir)dep\enet\include diff --git a/dep/ggpo-x/ggpo-x.vcxproj.filters b/dep/ggpo-x/ggpo-x.vcxproj.filters index 6f2e6dfd4..9fd65a704 100644 --- a/dep/ggpo-x/ggpo-x.vcxproj.filters +++ b/dep/ggpo-x/ggpo-x.vcxproj.filters @@ -6,9 +6,7 @@ - - @@ -32,9 +30,6 @@ network - - network - @@ -42,9 +37,7 @@ - - @@ -59,9 +52,6 @@ network - - network - diff --git a/dep/ggpo-x/include/ggponet.h b/dep/ggpo-x/include/ggponet.h index 8888d3372..cfce0b07e 100644 --- a/dep/ggpo-x/include/ggponet.h +++ b/dep/ggpo-x/include/ggponet.h @@ -8,6 +8,8 @@ #ifndef _GGPONET_H_ #define _GGPONET_H_ +#include "enet/enet.h" + #ifdef __cplusplus extern "C" { #endif @@ -78,8 +80,7 @@ typedef struct GGPOPlayer { struct { } local; struct { - char ip_address[32]; - unsigned short port; + ENetPeer* peer; } remote; } u; } GGPOPlayer; @@ -149,12 +150,8 @@ typedef enum { GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER = 1001, GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER = 1002, GGPO_EVENTCODE_RUNNING = 1003, - GGPO_EVENTCODE_DISCONNECTED_FROM_PEER = 1004, - GGPO_EVENTCODE_TIMESYNC = 1005, - GGPO_EVENTCODE_CONNECTION_INTERRUPTED = 1006, - GGPO_EVENTCODE_CONNECTION_RESUMED = 1007, - GGPO_EVENTCODE_CHAT = 1008, - GGPO_EVENTCODE_DESYNC = 1009 + GGPO_EVENTCODE_TIMESYNC = 1004, + GGPO_EVENTCODE_DESYNC = 1006 } GGPOEventCode; /* @@ -190,10 +187,6 @@ typedef struct { struct { GGPOPlayerHandle player; } connection_resumed; - struct { - int senderID; - const char* msg; - } chat; struct { int nFrameOfDesync; uint32_t ourCheckSum; @@ -460,6 +453,7 @@ GGPO_API GGPOErrorCode __cdecl ggpo_set_frame_delay(GGPOSession *, * in ggpo_idle. */ GGPO_API GGPOErrorCode __cdecl ggpo_idle(GGPOSession *); +GGPO_API GGPOErrorCode __cdecl ggpo_network_idle(GGPOSession *); /* * ggpo_add_local_input -- @@ -480,25 +474,6 @@ GGPO_API GGPOErrorCode __cdecl ggpo_add_local_input(GGPOSession *, GGPOPlayerHandle player, void *values, int size); -/* - * ggpo_add_local_input -- - * - * Used to notify GGPO.net of inputs that should be trasmitted to remote - * players. ggpo_add_local_input must be called once every frame for - * all player of type GGPO_PLAYERTYPE_LOCAL. - * - * player - The player handle returned for this player when you called - * ggpo_add_local_player. - * - * values - The controller inputs for this player. - * - * size - The size of the controller inputs. This must be exactly equal to the - * size passed into ggpo_start_session. - */ - -GGPO_API GGPOErrorCode __cdecl ggpo_client_chat(GGPOSession *, - const char* message); - /* * ggpo_synchronize_input -- * @@ -575,15 +550,6 @@ GGPO_API GGPOErrorCode __cdecl ggpo_get_network_stats(GGPOSession *, GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_timeout(GGPOSession *, int timeout); /* - * ggpo_enable_manual_network_polling -- - * - * disables polling done by ggpo and it's expected that ggpo_poll_network will be used instead. - * - */ - -GGPO_API GGPOErrorCode __cdecl ggpo_set_manual_network_polling(GGPOSession*, - bool value); - /* * ggpo_set_disconnect_notify_start -- * * The time to wait before the first GGPO_EVENTCODE_NETWORK_INTERRUPTED timeout @@ -594,14 +560,12 @@ GGPO_API GGPOErrorCode __cdecl ggpo_set_manual_network_polling(GGPOSession*, */ GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_notify_start(GGPOSession *, int timeout); -/* -* ggpo_poll_network -- -* -* polls the network socket for any messages to be sent and recieved. -* -*/ -GGPO_API GGPOErrorCode __cdecl ggpo_poll_network(GGPOSession*); +/* + * ENet packet processing + */ +GGPO_API GGPOErrorCode __cdecl ggpo_handle_packet(GGPOSession*, + ENetPeer* peer, const ENetPacket* pkt); /* * ggpo_log -- diff --git a/dep/ggpo-x/src/backends/backend.h b/dep/ggpo-x/src/backends/backend.h index 7b747e0ae..6fc9cc081 100644 --- a/dep/ggpo-x/src/backends/backend.h +++ b/dep/ggpo-x/src/backends/backend.h @@ -15,21 +15,20 @@ class GGPOSession { public: virtual ~GGPOSession() { } virtual GGPOErrorCode DoPoll() = 0; + virtual GGPOErrorCode NetworkIdle() = 0; virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) = 0; virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) = 0; virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags) = 0; virtual GGPOErrorCode IncrementFrame(uint16_t checksum) = 0; virtual GGPOErrorCode CurrentFrame(int& current) =0; - virtual GGPOErrorCode Chat(const char* text) = 0;// { return GGPO_OK; } virtual GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) = 0;// { return GGPO_OK; } - virtual GGPOErrorCode PollNetwork() = 0; virtual GGPOErrorCode GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle handle) { return GGPO_OK; } virtual GGPOErrorCode Logv(const char *fmt, va_list list) { ::Logv(fmt, list); return GGPO_OK; } virtual GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode SetDisconnectTimeout(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode SetDisconnectNotifyStart(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; } - virtual GGPOErrorCode SetManualNetworkPolling(bool value) = 0; + virtual GGPOErrorCode OnPacket(ENetPeer* peer, const ENetPacket* pkt) = 0; }; diff --git a/dep/ggpo-x/src/backends/p2p.cpp b/dep/ggpo-x/src/backends/p2p.cpp index ce3fdcc58..cfd1a3e1e 100644 --- a/dep/ggpo-x/src/backends/p2p.cpp +++ b/dep/ggpo-x/src/backends/p2p.cpp @@ -8,8 +8,6 @@ #include "p2p.h" static const int RECOMMENDATION_INTERVAL = 120; -static const int DEFAULT_DISCONNECT_TIMEOUT = 5000; -static const int DEFAULT_DISCONNECT_NOTIFY_START = 750; Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb, const char *gamename, @@ -19,15 +17,12 @@ Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb, _num_players(num_players), _input_size(input_size), _sync(_local_connect_status, nframes), - _disconnect_timeout(DEFAULT_DISCONNECT_TIMEOUT), - _disconnect_notify_start(DEFAULT_DISCONNECT_NOTIFY_START), _num_spectators(0), _next_spectator_frame(0) { _callbacks = *cb; _synchronizing = true; _next_recommended_sleep = 0; - _manual_network_polling = false; /* * Initialize the synchronziation layer @@ -39,11 +34,6 @@ Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb, config.num_prediction_frames = nframes; _sync.Init(config); - /* - * Initialize the UDP port - */ - _udp.Init(localport, &_poll, this); - _endpoints.resize(_num_players); memset(_local_connect_status, 0, sizeof(_local_connect_status)); for (int i = 0; i < ARRAY_SIZE(_local_connect_status); i++) { @@ -61,23 +51,18 @@ Peer2PeerBackend::~Peer2PeerBackend() } void -Peer2PeerBackend::AddRemotePlayer(char *ip, - uint16 port, - int queue) +Peer2PeerBackend::AddRemotePlayer(ENetPeer* peer, int queue) { /* * Start the state machine (xxx: no) */ _synchronizing = true; - _endpoints[queue].Init(&_udp, _poll, queue, ip, port, _local_connect_status); - _endpoints[queue].SetDisconnectTimeout(_disconnect_timeout); - _endpoints[queue].SetDisconnectNotifyStart(_disconnect_notify_start); + _endpoints[queue].Init(peer, queue, _local_connect_status); _endpoints[queue].Synchronize(); } -GGPOErrorCode Peer2PeerBackend::AddSpectator(char *ip, - uint16 port) +GGPOErrorCode Peer2PeerBackend::AddSpectator(ENetPeer* peer) { if (_num_spectators == GGPO_MAX_SPECTATORS) { return GGPO_ERRORCODE_TOO_MANY_SPECTATORS; @@ -90,9 +75,7 @@ GGPOErrorCode Peer2PeerBackend::AddSpectator(char *ip, } int queue = _num_spectators++; - _spectators[queue].Init(&_udp, _poll, queue + 1000, ip, port, _local_connect_status); - _spectators[queue].SetDisconnectTimeout(_disconnect_timeout); - _spectators[queue].SetDisconnectNotifyStart(_disconnect_notify_start); + _spectators[queue].Init(peer, queue + 1000, _local_connect_status); _spectators[queue].Synchronize(); return GGPO_OK; @@ -155,21 +138,8 @@ void Peer2PeerBackend::CheckDesync() GGPOErrorCode Peer2PeerBackend::DoPoll() { - // Pass on chat - for (int i = 0; i < _num_players; i++) { - _endpoints[i].ConsumeChat([&](const char* msg) { - GGPOEvent info; - info.u.chat.senderID = i; - info.code = GGPO_EVENTCODE_CHAT; - info.u.chat.msg = msg; - _callbacks.on_event(_callbacks.context, &info); - }); - } - - if (!_sync.InRollback()) { - if (!_manual_network_polling) - _poll.Pump(0); - + if (!_sync.InRollback()) + { PollUdpProtocolEvents(); CheckDesync(); if (!_synchronizing) { @@ -232,6 +202,14 @@ Peer2PeerBackend::DoPoll() return GGPO_OK; } +GGPOErrorCode Peer2PeerBackend::NetworkIdle() +{ + for (UdpProtocol& udp : _endpoints) + udp.NetworkIdle(); + + return GGPO_OK; +} + int Peer2PeerBackend::Poll2Players(int current_frame) { int i; @@ -309,7 +287,7 @@ Peer2PeerBackend::AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) { if (player->type == GGPO_PLAYERTYPE_SPECTATOR) { - return AddSpectator(player->u.remote.ip_address, player->u.remote.port); + return AddSpectator(player->u.remote.peer); } int queue = player->player_num - 1; @@ -319,7 +297,7 @@ Peer2PeerBackend::AddPlayer(GGPOPlayer *player, *handle = QueueToPlayerHandle(queue); if (player->type == GGPO_PLAYERTYPE_REMOTE) { - AddRemotePlayer(player->u.remote.ip_address, player->u.remote.port, queue); + AddRemotePlayer(player->u.remote.peer, queue); } return GGPO_OK; } @@ -413,19 +391,6 @@ Peer2PeerBackend::CurrentFrame(int& current) return GGPO_OK; } -GGPOErrorCode -Peer2PeerBackend::PollNetwork() -{ - _poll.Pump(0); - return GGPO_OK; -} - -GGPOErrorCode Peer2PeerBackend::SetManualNetworkPolling(bool value) -{ - _manual_network_polling = value; - return GGPO_OK; -} - GGPOErrorCode Peer2PeerBackend::IncrementFrame(uint16_t checksum1) { @@ -570,10 +535,6 @@ Peer2PeerBackend::OnUdpProtocolPeerEvent(UdpProtocol::Event &evt, int queue) } } break; - - case UdpProtocol::Event::Disconnected: - DisconnectPlayer(QueueToPlayerHandle(queue)); - break; } } @@ -583,19 +544,6 @@ Peer2PeerBackend::OnUdpProtocolSpectatorEvent(UdpProtocol::Event &evt, int queue { GGPOPlayerHandle handle = QueueToSpectatorHandle(queue); OnUdpProtocolEvent(evt, handle); - - GGPOEvent info; - - switch (evt.type) { - case UdpProtocol::Event::Disconnected: - _spectators[queue].Disconnect(); - - info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER; - info.u.disconnected.player = handle; - _callbacks.on_event(_callbacks.context, &info); - - break; - } } void @@ -623,19 +571,6 @@ Peer2PeerBackend::OnUdpProtocolEvent(UdpProtocol::Event &evt, GGPOPlayerHandle h CheckInitialSync(); break; - - case UdpProtocol::Event::NetworkInterrupted: - info.code = GGPO_EVENTCODE_CONNECTION_INTERRUPTED; - info.u.connection_interrupted.player = handle; - info.u.connection_interrupted.disconnect_timeout = evt.u.network_interrupted.disconnect_timeout; - _callbacks.on_event(_callbacks.context, &info); - break; - - case UdpProtocol::Event::NetworkResumed: - info.code = GGPO_EVENTCODE_CONNECTION_RESUMED; - info.u.connection_resumed.player = handle; - _callbacks.on_event(_callbacks.context, &info); - break; } } @@ -682,6 +617,7 @@ Peer2PeerBackend::DisconnectPlayerQueue(int queue, int syncto) GGPOEvent info; int framecount = _sync.GetFrameCount(); + // TODO: Where does the endpoint actually get removed? I can't see it anywhere... _endpoints[queue].Disconnect(); Log("Changing queue %d local connect status for last frame from %d to %d on disconnect request (current: %d).\n", @@ -696,10 +632,6 @@ Peer2PeerBackend::DisconnectPlayerQueue(int queue, int syncto) Log("finished adjusting simulation.\n"); }*/ - info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER; - info.u.disconnected.player = QueueToPlayerHandle(queue); - _callbacks.on_event(_callbacks.context, &info); - CheckInitialSync(); } @@ -742,45 +674,6 @@ Peer2PeerBackend::SetFrameDelay(GGPOPlayerHandle player, int delay) return GGPO_OK; } -GGPOErrorCode -Peer2PeerBackend::SetDisconnectTimeout(int timeout) -{ - _disconnect_timeout = timeout; - for (int i = 0; i < _num_players; i++) { - if (_endpoints[i].IsInitialized()) { - _endpoints[i].SetDisconnectTimeout(_disconnect_timeout); - } - } - return GGPO_OK; -} - -GGPOErrorCode -Peer2PeerBackend::SetDisconnectNotifyStart(int timeout) -{ - _disconnect_notify_start = timeout; - for (int i = 0; i < _num_players; i++) { - if (_endpoints[i].IsInitialized()) { - _endpoints[i].SetDisconnectNotifyStart(_disconnect_notify_start); - } - } - return GGPO_OK; -} - -GGPOErrorCode -Peer2PeerBackend::Chat(const char* text) -{ - if (strlen(text) >= MAX_CHAT_LENGTH) - return GGPO_CHAT_MESSAGE_TOO_LONG; - - // Send the input to all the remote players. - for (int i = 0; i < _num_players; i++) { - if (_endpoints[i].IsInitialized()) { - _endpoints[i].SendChat(text); - } - } - return GGPO_OK; -} - GGPOErrorCode Peer2PeerBackend::PlayerHandleToQueue(GGPOPlayerHandle player, int *queue) { @@ -793,21 +686,27 @@ Peer2PeerBackend::PlayerHandleToQueue(GGPOPlayerHandle player, int *queue) } -void -Peer2PeerBackend::OnMsg(sockaddr_in &from, UdpMsg *msg, int len) +GGPOErrorCode +Peer2PeerBackend::OnPacket(ENetPeer* peer, const ENetPacket* pkt) { + // ugh why is const so hard for some people... + UdpMsg* msg = const_cast(reinterpret_cast(pkt->data)); + const int len = static_cast(pkt->dataLength); + for (int i = 0; i < _num_players; i++) { - if (_endpoints[i].HandlesMsg(from, msg)) { + if (_endpoints[i].GetENetPeer() == peer) { _endpoints[i].OnMsg(msg, len); - return; + return GGPO_OK; } } for (int i = 0; i < _num_spectators; i++) { - if (_spectators[i].HandlesMsg(from, msg)) { + if (_spectators[i].GetENetPeer() == peer) { _spectators[i].OnMsg(msg, len); - return; + return GGPO_OK; } } + + return GGPO_ERRORCODE_INVALID_PLAYER_HANDLE; } void diff --git a/dep/ggpo-x/src/backends/p2p.h b/dep/ggpo-x/src/backends/p2p.h index 7835a0cb9..d11ad7094 100644 --- a/dep/ggpo-x/src/backends/p2p.h +++ b/dep/ggpo-x/src/backends/p2p.h @@ -9,13 +9,13 @@ #define _P2P_H #include "types.h" -#include "poll.h" #include "sync.h" #include "backend.h" #include "timesync.h" #include "network/udp_proto.h" #include -class Peer2PeerBackend : public GGPOSession, Udp::Callbacks { +class Peer2PeerBackend final : public GGPOSession +{ public: Peer2PeerBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size, int nframes); virtual ~Peer2PeerBackend(); @@ -23,6 +23,7 @@ public: public: virtual GGPOErrorCode DoPoll() override; + virtual GGPOErrorCode NetworkIdle() override; virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) override; virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) override; virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags) override; @@ -30,15 +31,8 @@ public: virtual GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) override; virtual GGPOErrorCode GetNetworkStats(GGPONetworkStats *stats, GGPOPlayerHandle handle) override; virtual GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) override; - virtual GGPOErrorCode SetDisconnectTimeout(int timeout) override; - virtual GGPOErrorCode SetDisconnectNotifyStart(int timeout) override; - virtual GGPOErrorCode Chat(const char* text) override; virtual GGPOErrorCode CurrentFrame(int& current) override; - virtual GGPOErrorCode PollNetwork() override; - virtual GGPOErrorCode SetManualNetworkPolling(bool value) override; - - public: - virtual void OnMsg(sockaddr_in &from, UdpMsg *msg, int len); + virtual GGPOErrorCode OnPacket(ENetPeer* peer, const ENetPacket* pkt) override; protected: GGPOErrorCode PlayerHandleToQueue(GGPOPlayerHandle player, int *queue); @@ -50,8 +44,8 @@ protected: void CheckInitialSync(void); int Poll2Players(int current_frame); int PollNPlayers(int current_frame); - void AddRemotePlayer(char *remoteip, uint16 reportport, int queue); - GGPOErrorCode AddSpectator(char *remoteip, uint16 reportport); + void AddRemotePlayer(ENetPeer* peer, int queue); + GGPOErrorCode AddSpectator(ENetPeer* peer); virtual void OnSyncEvent(Sync::Event &e) { } virtual void OnUdpProtocolEvent(UdpProtocol::Event &e, GGPOPlayerHandle handle); virtual void OnUdpProtocolPeerEvent(UdpProtocol::Event &e, int queue); @@ -59,9 +53,7 @@ protected: protected: GGPOSessionCallbacks _callbacks; - Poll _poll; Sync _sync; - Udp _udp; std::vector _endpoints; UdpProtocol _spectators[GGPO_MAX_SPECTATORS]; int _num_spectators; @@ -72,10 +64,6 @@ protected: int _next_recommended_sleep; int _next_spectator_frame; - int _disconnect_timeout; - int _disconnect_notify_start; - - bool _manual_network_polling; UdpMsg::connect_status _local_connect_status[UDP_MSG_MAX_PLAYERS]; struct ChecksumEntry { diff --git a/dep/ggpo-x/src/backends/spectator.cpp b/dep/ggpo-x/src/backends/spectator.cpp index 71ca42519..ebf7c6955 100644 --- a/dep/ggpo-x/src/backends/spectator.cpp +++ b/dep/ggpo-x/src/backends/spectator.cpp @@ -20,7 +20,6 @@ SpectatorBackend::SpectatorBackend(GGPOSessionCallbacks *cb, { _callbacks = *cb; _synchronizing = true; - _manual_network_polling = false; for (int i = 0; i < ARRAY_SIZE(_inputs); i++) { _inputs[i].frame = -1; @@ -29,12 +28,14 @@ SpectatorBackend::SpectatorBackend(GGPOSessionCallbacks *cb, /* * Initialize the UDP port */ - _udp.Init(localport, &_poll, this); + // FIXME + abort(); + //_udp.Init(localport, &_poll, this); /* * Init the host endpoint */ - _host.Init(&_udp, _poll, 0, hostip, hostport, NULL); + //_host.Init(&_udp, _poll, 0, hostip, hostport, NULL); _host.Synchronize(); /* @@ -50,13 +51,16 @@ SpectatorBackend::~SpectatorBackend() GGPOErrorCode SpectatorBackend::DoPoll() { - if (!_manual_network_polling) - _poll.Pump(0); - PollUdpProtocolEvents(); return GGPO_OK; } +GGPOErrorCode SpectatorBackend::NetworkIdle() +{ + _host.NetworkIdle(); + return GGPO_OK; +} + GGPOErrorCode SpectatorBackend::SyncInput(void *values, int size, @@ -95,20 +99,6 @@ SpectatorBackend::CurrentFrame(int& current) return GGPO_OK; } -GGPOErrorCode -SpectatorBackend::PollNetwork() -{ - _poll.Pump(0); - return GGPO_OK; -} - -GGPOErrorCode -SpectatorBackend::SetManualNetworkPolling(bool value) -{ - _manual_network_polling = value; - return GGPO_OK; -} - GGPOErrorCode SpectatorBackend::IncrementFrame(uint16_t checksum) { @@ -159,25 +149,6 @@ SpectatorBackend::OnUdpProtocolEvent(UdpProtocol::Event &evt) } break; - case UdpProtocol::Event::NetworkInterrupted: - info.code = GGPO_EVENTCODE_CONNECTION_INTERRUPTED; - info.u.connection_interrupted.player = 0; - info.u.connection_interrupted.disconnect_timeout = evt.u.network_interrupted.disconnect_timeout; - _callbacks.on_event(_callbacks.context, &info); - break; - - case UdpProtocol::Event::NetworkResumed: - info.code = GGPO_EVENTCODE_CONNECTION_RESUMED; - info.u.connection_resumed.player = 0; - _callbacks.on_event(_callbacks.context, &info); - break; - - case UdpProtocol::Event::Disconnected: - info.code = GGPO_EVENTCODE_DISCONNECTED_FROM_PEER; - info.u.disconnected.player = 0; - _callbacks.on_event(_callbacks.context, &info); - break; - case UdpProtocol::Event::Input: GameInput& input = evt.u.input.input; @@ -188,11 +159,14 @@ SpectatorBackend::OnUdpProtocolEvent(UdpProtocol::Event &evt) } } -void -SpectatorBackend::OnMsg(sockaddr_in &from, UdpMsg *msg, int len) +GGPOErrorCode SpectatorBackend::OnPacket(ENetPeer* peer, const ENetPacket* pkt) { - if (_host.HandlesMsg(from, msg)) { - _host.OnMsg(msg, len); - } + if (_host.GetENetPeer() != peer) + return GGPO_ERRORCODE_INVALID_PLAYER_HANDLE; + + UdpMsg* msg = const_cast(reinterpret_cast(pkt->data)); + const int len = static_cast(pkt->dataLength); + _host.OnMsg(msg, len); + return GGPO_OK; } diff --git a/dep/ggpo-x/src/backends/spectator.h b/dep/ggpo-x/src/backends/spectator.h index 255fb4231..a0e39c2ef 100644 --- a/dep/ggpo-x/src/backends/spectator.h +++ b/dep/ggpo-x/src/backends/spectator.h @@ -9,7 +9,6 @@ #define _SPECTATOR_H #include "types.h" -#include "poll.h" #include "sync.h" #include "backend.h" #include "timesync.h" @@ -17,7 +16,7 @@ #define SPECTATOR_FRAME_BUFFER_SIZE 64 -class SpectatorBackend : public GGPOSession, Udp::Callbacks { +class SpectatorBackend final : public GGPOSession { public: SpectatorBackend(GGPOSessionCallbacks *cb, const char *gamename, uint16 localport, int num_players, int input_size, char *hostip, u_short hostport); virtual ~SpectatorBackend(); @@ -25,6 +24,7 @@ public: public: virtual GGPOErrorCode DoPoll(); + virtual GGPOErrorCode NetworkIdle(); virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size) { return GGPO_OK; } virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags); @@ -34,13 +34,8 @@ public: virtual GGPOErrorCode SetFrameDelay(GGPOPlayerHandle player, int delay) { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode SetDisconnectTimeout(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode SetDisconnectNotifyStart(int timeout) { return GGPO_ERRORCODE_UNSUPPORTED; } - virtual GGPOErrorCode Chat(const char* text) override { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode CurrentFrame(int& current) override; - virtual GGPOErrorCode PollNetwork() override; - virtual GGPOErrorCode SetManualNetworkPolling(bool value) override; - -public: - virtual void OnMsg(sockaddr_in &from, UdpMsg *msg, int len); + virtual GGPOErrorCode OnPacket(ENetPeer* peer, const ENetPacket* pkt) override; protected: void PollUdpProtocolEvents(void); @@ -50,15 +45,12 @@ protected: protected: GGPOSessionCallbacks _callbacks; - Poll _poll; - Udp _udp; UdpProtocol _host; bool _synchronizing; int _input_size; int _num_players; int _next_input_to_send; GameInput _inputs[SPECTATOR_FRAME_BUFFER_SIZE]; - bool _manual_network_polling; }; #endif diff --git a/dep/ggpo-x/src/backends/synctest.cpp b/dep/ggpo-x/src/backends/synctest.cpp index a8fbe0884..39c858aef 100644 --- a/dep/ggpo-x/src/backends/synctest.cpp +++ b/dep/ggpo-x/src/backends/synctest.cpp @@ -54,6 +54,12 @@ SyncTestBackend::DoPoll() return GGPO_OK; } +GGPOErrorCode +SyncTestBackend::NetworkIdle() +{ + return GGPO_OK; +} + GGPOErrorCode SyncTestBackend::AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle) { diff --git a/dep/ggpo-x/src/backends/synctest.h b/dep/ggpo-x/src/backends/synctest.h index 8339dadf4..e425d9c5f 100644 --- a/dep/ggpo-x/src/backends/synctest.h +++ b/dep/ggpo-x/src/backends/synctest.h @@ -13,22 +13,21 @@ #include "sync.h" #include "ring_buffer.h" -class SyncTestBackend : public GGPOSession { +class SyncTestBackend final : public GGPOSession { public: SyncTestBackend(GGPOSessionCallbacks *cb, char *gamename, int frames, int num_players); virtual ~SyncTestBackend(); virtual GGPOErrorCode DoPoll(); + virtual GGPOErrorCode NetworkIdle(); virtual GGPOErrorCode AddPlayer(GGPOPlayer *player, GGPOPlayerHandle *handle); virtual GGPOErrorCode AddLocalInput(GGPOPlayerHandle player, void *values, int size); virtual GGPOErrorCode SyncInput(void *values, int size, int *disconnect_flags); virtual GGPOErrorCode IncrementFrame(uint16_t checksum); virtual GGPOErrorCode Logv(char *fmt, va_list list); virtual GGPOErrorCode DisconnectPlayer(GGPOPlayerHandle handle) { return GGPO_OK; } - virtual GGPOErrorCode Chat(const char* text) override { return GGPO_ERRORCODE_UNSUPPORTED; } virtual GGPOErrorCode CurrentFrame(int& current) override; - virtual GGPOErrorCode PollNetwork() override { return GGPO_OK; }; - virtual GGPOErrorCode SetManualNetworkPolling(bool value) override { return GGPO_OK; }; + virtual GGPOErrorCode OnPacket(ENetPeer* peer, const ENetPacket* pkt) override { return GGPO_ERRORCODE_UNSUPPORTED; } protected: struct SavedInfo { diff --git a/dep/ggpo-x/src/main.cpp b/dep/ggpo-x/src/main.cpp index 0ed0b9ad2..e1ac2ce47 100644 --- a/dep/ggpo-x/src/main.cpp +++ b/dep/ggpo-x/src/main.cpp @@ -100,6 +100,15 @@ ggpo_idle(GGPOSession *ggpo) return ggpo->DoPoll(); } +GGPOErrorCode +ggpo_network_idle(GGPOSession* ggpo) +{ + if (!ggpo) { + return GGPO_ERRORCODE_INVALID_SESSION; + } + return ggpo->NetworkIdle(); +} + GGPOErrorCode ggpo_add_local_input(GGPOSession *ggpo, GGPOPlayerHandle player, @@ -151,15 +160,6 @@ ggpo_get_current_frame(GGPOSession *ggpo, int& nFrame) return ggpo->CurrentFrame(nFrame); } -GGPOErrorCode -ggpo_client_chat(GGPOSession *ggpo, const char *text) -{ - if (!ggpo) { - return GGPO_ERRORCODE_INVALID_SESSION; - } - return ggpo->Chat(text); -} - GGPOErrorCode ggpo_get_network_stats(GGPOSession *ggpo, GGPOPlayerHandle player, @@ -172,6 +172,13 @@ ggpo_get_network_stats(GGPOSession *ggpo, } +GGPOErrorCode ggpo_handle_packet(GGPOSession* ggpo, ENetPeer* peer, const ENetPacket* pkt) +{ + if (!ggpo) + return GGPO_ERRORCODE_INVALID_SESSION; + return ggpo->OnPacket(peer, pkt); +} + GGPOErrorCode ggpo_close_session(GGPOSession *ggpo) { @@ -191,16 +198,6 @@ ggpo_set_disconnect_timeout(GGPOSession *ggpo, int timeout) return ggpo->SetDisconnectTimeout(timeout); } -GGPOErrorCode -ggpo_set_manual_network_polling(GGPOSession *ggpo, bool value) -{ - if (!ggpo) - { - return GGPO_ERRORCODE_INVALID_SESSION; - } - return ggpo->SetManualNetworkPolling(value); -} - GGPOErrorCode ggpo_set_disconnect_notify_start(GGPOSession *ggpo, int timeout) { @@ -210,16 +207,6 @@ ggpo_set_disconnect_notify_start(GGPOSession *ggpo, int timeout) return ggpo->SetDisconnectNotifyStart(timeout); } -GGPOErrorCode -ggpo_poll_network(GGPOSession* ggpo) -{ - if (!ggpo) - { - return GGPO_ERRORCODE_INVALID_SESSION; - } - return ggpo->PollNetwork(); -} - GGPOErrorCode ggpo_start_spectating(GGPOSession **session, GGPOSessionCallbacks *cb, const char *game, @@ -238,4 +225,3 @@ GGPOErrorCode ggpo_start_spectating(GGPOSession **session, host_port); return GGPO_OK; } - diff --git a/dep/ggpo-x/src/network/udp.cpp b/dep/ggpo-x/src/network/udp.cpp deleted file mode 100644 index 5c2250470..000000000 --- a/dep/ggpo-x/src/network/udp.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* ----------------------------------------------------------------------- - * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. - * - * Use of this software is governed by the MIT license that can be found - * in the LICENSE file. - */ - -#include "types.h" -#include "udp.h" - -SOCKET -CreateSocket(uint16 bind_port, int retries) -{ - SOCKET s; - sockaddr_in sin; - uint16 port; - int optval = 1; - - s = socket(AF_INET, SOCK_DGRAM, 0); - setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&optval, sizeof optval); - setsockopt(s, SOL_SOCKET, SO_DONTLINGER, (const char *)&optval, sizeof optval); - - // non-blocking... - u_long iMode = 1; - ioctlsocket(s, FIONBIO, &iMode); - - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = htonl(INADDR_ANY); - for (port = bind_port; port <= bind_port + retries; port++) { - sin.sin_port = htons(port); - if (bind(s, (sockaddr *)&sin, sizeof sin) != SOCKET_ERROR) { - Log("Udp bound to port: %d.\n", port); - return s; - } - } - closesocket(s); - return INVALID_SOCKET; -} - -Udp::Udp() : - _socket(INVALID_SOCKET), - _callbacks(NULL) -{ -} - -Udp::~Udp(void) -{ - if (_socket != INVALID_SOCKET) { - closesocket(_socket); - _socket = INVALID_SOCKET; - } -} - -void -Udp::Init(uint16 port, Poll *poll, Callbacks *callbacks) -{ - _callbacks = callbacks; - - _poll = poll; - _poll->RegisterLoop(this); - - Log("binding udp socket to port %d.\n", port); - _socket = CreateSocket(port, 0); -} - -void -Udp::SendTo(char *buffer, int len, int flags, struct sockaddr *dst, int destlen) -{ - struct sockaddr_in *to = (struct sockaddr_in *)dst; - - int res = sendto(_socket, buffer, len, flags, dst, destlen); - if (res == SOCKET_ERROR) { - DWORD err = WSAGetLastError(); - Log("unknown error in sendto (erro: %d wsaerr: %d).\n", res, err); - ASSERT(FALSE && "Unknown error in sendto"); - } - char dst_ip[1024]; - Log("sent packet length %d to %s:%d (ret:%d).\n", len, inet_ntop(AF_INET, (void *)&to->sin_addr, dst_ip, ARRAY_SIZE(dst_ip)), ntohs(to->sin_port), res); -} - -bool -Udp::OnLoopPoll(void *cookie) -{ - uint8 recv_buf[MAX_UDP_PACKET_SIZE]; - sockaddr_in recv_addr; - int recv_addr_len; - - for (;;) { - recv_addr_len = sizeof(recv_addr); - int len = recvfrom(_socket, (char *)recv_buf, MAX_UDP_PACKET_SIZE, 0, (struct sockaddr *)&recv_addr, &recv_addr_len); - - // TODO: handle len == 0... indicates a disconnect. - - if (len == -1) { - int error = WSAGetLastError(); - if (error != WSAEWOULDBLOCK) { - Log("recvfrom WSAGetLastError returned %d (%x).\n", error, error); - } - break; - } else if (len > 0) { - char src_ip[1024]; - Log("recvfrom returned (len:%d from:%s:%d).\n", len, inet_ntop(AF_INET, (void*)&recv_addr.sin_addr, src_ip, ARRAY_SIZE(src_ip)), ntohs(recv_addr.sin_port) ); - UdpMsg *msg = (UdpMsg *)recv_buf; - _callbacks->OnMsg(recv_addr, msg, len); - } - } - return true; -} - - -void -Udp::Log(const char *fmt, ...) -{ - char buf[1024]; - size_t offset; - va_list args; - - strcpy_s(buf, "udp | "); - offset = strlen(buf); - va_start(args, fmt); - vsnprintf(buf + offset, ARRAY_SIZE(buf) - offset - 1, fmt, args); - buf[ARRAY_SIZE(buf)-1] = '\0'; - ::Log(buf); - va_end(args); -} diff --git a/dep/ggpo-x/src/network/udp.h b/dep/ggpo-x/src/network/udp.h deleted file mode 100644 index c725150c1..000000000 --- a/dep/ggpo-x/src/network/udp.h +++ /dev/null @@ -1,61 +0,0 @@ -/* ----------------------------------------------------------------------- - * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. - * - * Use of this software is governed by the MIT license that can be found - * in the LICENSE file. - */ - -#ifndef _UDP_H -#define _UDP_H - -#include "poll.h" -#include "udp_msg.h" -#include "ggponet.h" -#include "ring_buffer.h" - -#define MAX_UDP_ENDPOINTS 16 - -static const int MAX_UDP_PACKET_SIZE = 4096; - -class Udp : public IPollSink -{ -public: - struct Stats { - int bytes_sent; - int packets_sent; - float kbps_sent; - }; - - struct Callbacks { - virtual ~Callbacks() { } - virtual void OnMsg(sockaddr_in &from, UdpMsg *msg, int len) = 0; - }; - - -protected: - void Log(const char *fmt, ...); - -public: - Udp(); - - void Init(uint16 port, Poll *p, Callbacks *callbacks); - - void SendTo(char *buffer, int len, int flags, struct sockaddr *dst, int destlen); - - bool OnLoopPoll(void *cookie) override; - -public: - ~Udp(void); - -protected: - // Network transmission information - SOCKET _socket; - - // state management - Callbacks *_callbacks; - Poll *_poll; - - -}; - -#endif diff --git a/dep/ggpo-x/src/network/udp_msg.h b/dep/ggpo-x/src/network/udp_msg.h index 1a4b6e4ff..ad01d6a9d 100644 --- a/dep/ggpo-x/src/network/udp_msg.h +++ b/dep/ggpo-x/src/network/udp_msg.h @@ -10,7 +10,6 @@ #define MAX_COMPRESSED_BITS 4096 #define UDP_MSG_MAX_PLAYERS 4 -#define MAX_CHAT_LENGTH 120 #pragma pack(push, 1) struct UdpMsg @@ -22,9 +21,7 @@ struct UdpMsg Input = 3, QualityReport = 4, QualityReply = 5, - KeepAlive = 6, - InputAck = 7, - Chat = 8, + InputAck = 6, }; struct connect_status { @@ -63,8 +60,7 @@ struct UdpMsg uint32 start_frame; - int disconnect_requested:1; - int ack_frame:31; + int ack_frame; uint16 num_bits; uint32 checksum32; @@ -73,11 +69,8 @@ struct UdpMsg } input; struct { - int ack_frame:31; + int ack_frame; } input_ack; - struct { - char msg[MAX_CHAT_LENGTH]; - } chat; } u; public: @@ -94,8 +87,6 @@ public: case QualityReport: return sizeof(u.quality_report); case QualityReply: return sizeof(u.quality_reply); case InputAck: return sizeof(u.input_ack); - case Chat: return MAX_CHAT_LENGTH; - case KeepAlive: return 0; case Input: size = (int)((char *)&u.input.bits - (char *)&u.input); size += (u.input.num_bits + 7) / 8; diff --git a/dep/ggpo-x/src/network/udp_proto.cpp b/dep/ggpo-x/src/network/udp_proto.cpp index 1197ddccb..5b8d4543c 100644 --- a/dep/ggpo-x/src/network/udp_proto.cpp +++ b/dep/ggpo-x/src/network/udp_proto.cpp @@ -15,12 +15,12 @@ static const int NUM_SYNC_PACKETS = 5; static const int SYNC_RETRY_INTERVAL = 2000; static const int SYNC_FIRST_RETRY_INTERVAL = 500; static const int RUNNING_RETRY_INTERVAL = 200; -static const int KEEP_ALIVE_INTERVAL = 200; static const int QUALITY_REPORT_INTERVAL = 333; static const int NETWORK_STATS_INTERVAL = 500; -static const int UDP_SHUTDOWN_TIMER = 5000; static const int MAX_SEQ_DISTANCE = (1 << 15); +static const uint8_t ENET_CHANNEL_ID = 1; + UdpProtocol::UdpProtocol() : _local_frame_advantage(0), _remote_frame_advantage(0), @@ -31,15 +31,10 @@ UdpProtocol::UdpProtocol() : _bytes_sent(0), _stats_start_time(0), _last_send_time(0), - _shutdown_timeout(0), - _disconnect_timeout(0), - _disconnect_notify_start(0), - _disconnect_notify_sent(false), - _disconnect_event_sent(false), _connected(false), _next_send_seq(0), _next_recv_seq(0), - _udp(NULL) + _peer(nullptr) { _last_sent_input.init(-1, NULL, 1); _last_received_input.init(-1, NULL, 1); @@ -50,17 +45,12 @@ UdpProtocol::UdpProtocol() : for (int i = 0; i < ARRAY_SIZE(_peer_connect_status); i++) { _peer_connect_status[i].last_frame = -1; } - memset(&_peer_addr, 0, sizeof _peer_addr); - _oo_packet.msg = NULL; - - _send_latency = Platform::GetConfigInt("ggpo.network.delay"); - _oop_percent = Platform::GetConfigInt("ggpo.oop.percent"); } UdpProtocol::~UdpProtocol() { - ClearSendQueue(); } + void UdpProtocol::SetFrameDelay(int delay) { _timesync.SetFrameDelay(delay); @@ -70,32 +60,22 @@ int UdpProtocol::RemoteFrameDelay()const { return _timesync._remoteFrameDelay; } -void -UdpProtocol::Init(Udp *udp, - Poll &poll, - int queue, - char *ip, - u_short port, - UdpMsg::connect_status *status) + +void UdpProtocol::Init(ENetPeer* peer, int queue, UdpMsg::connect_status *status) { - _udp = udp; + _peer = peer; _queue = queue; _local_connect_status = status; - _peer_addr.sin_family = AF_INET; - _peer_addr.sin_port = htons(port); - inet_pton(AF_INET, ip, &_peer_addr.sin_addr.s_addr); - do { _magic_number = (uint16)rand(); } while (_magic_number == 0); - poll.RegisterLoop(this); } void UdpProtocol::SendInput(GameInput &input) { - if (_udp) { + if (_peer) { if (_current_state == Running) { /* * Check to see if this is a good time to adjust for the rift... @@ -156,7 +136,6 @@ UdpProtocol::SendPendingOutput() msg->u.input.ack_frame = _last_received_input.frame; msg->u.input.num_bits = (uint16)offset; - msg->u.input.disconnect_requested = _current_state == Disconnected; if (_local_connect_status) { memcpy(msg->u.input.peer_connect_status, _local_connect_status, sizeof(UdpMsg::connect_status) * UDP_MSG_MAX_PLAYERS); } else { @@ -205,24 +184,17 @@ void UdpProtocol::EndPollLoop() if (_remoteCheckSumsThisFrame.size()) _remoteCheckSums.emplace(*_remoteCheckSumsThisFrame.rbegin()); } -void UdpProtocol::SendChat(const char* message) -{ - UdpMsg* msg = new UdpMsg(UdpMsg::Chat); - strcpy_s(msg->u.chat.msg, message); - SendMsg(msg); -} bool -UdpProtocol::OnLoopPoll(void *cookie) +UdpProtocol::NetworkIdle() { - if (!_udp) { + if (!_peer) { return true; } unsigned int now = Platform::GetCurrentTimeMS(); unsigned int next_interval; - PumpSendQueue(); switch (_current_state) { case Syncing: next_interval = (_state.sync.roundtrips_remaining == NUM_SYNC_PACKETS) ? SYNC_FIRST_RETRY_INTERVAL : SYNC_RETRY_INTERVAL; @@ -255,35 +227,10 @@ UdpProtocol::OnLoopPoll(void *cookie) _state.running.last_network_stats_interval = now; } - if (_last_send_time && _last_send_time + KEEP_ALIVE_INTERVAL < now) { - Log("Sending keep alive packet\n"); - SendMsg(new UdpMsg(UdpMsg::KeepAlive)); - } - - if (_disconnect_timeout && _disconnect_notify_start && - !_disconnect_notify_sent && (_last_recv_time + _disconnect_notify_start < now)) { - Log("Endpoint has stopped receiving packets for %d ms. Sending notification.\n", _disconnect_notify_start); - Event e(Event::NetworkInterrupted); - e.u.network_interrupted.disconnect_timeout = _disconnect_timeout - _disconnect_notify_start; - QueueEvent(e); - _disconnect_notify_sent = true; - } - - if (_disconnect_timeout && (_last_recv_time + _disconnect_timeout < now)) { - if (!_disconnect_event_sent) { - Log("Endpoint has stopped receiving packets for %d ms. Disconnecting.\n", _disconnect_timeout); - QueueEvent(Event(Event::Disconnected)); - _disconnect_event_sent = true; - } - } break; case Disconnected: - if (_shutdown_timeout < now) { - Log("Shutting down udp connection.\n"); - _udp = NULL; - _shutdown_timeout = 0; - } + break; } @@ -295,7 +242,7 @@ void UdpProtocol::Disconnect() { _current_state = Disconnected; - _shutdown_timeout = Platform::GetCurrentTimeMS() + UDP_SHUTDOWN_TIMER; + _peer = nullptr; } void @@ -311,28 +258,21 @@ UdpProtocol::SendSyncRequest() void UdpProtocol::SendMsg(UdpMsg *msg) { + const int size = msg->PacketSize(); LogMsg("send", msg); _packets_sent++; _last_send_time = Platform::GetCurrentTimeMS(); - _bytes_sent += msg->PacketSize(); + _bytes_sent += size; msg->hdr.magic = _magic_number; msg->hdr.sequence_number = _next_send_seq++; - _send_queue.push(QueueEntry(Platform::GetCurrentTimeMS(), _peer_addr, msg)); - PumpSendQueue(); -} - -bool -UdpProtocol::HandlesMsg(sockaddr_in &from, - UdpMsg *msg) -{ - if (!_udp) { - return false; - } - return _peer_addr.sin_addr.S_un.S_addr == from.sin_addr.S_un.S_addr && - _peer_addr.sin_port == from.sin_port; + ENetPacket* pkt = enet_packet_create(msg, size, 0); + enet_peer_send(_peer, ENET_CHANNEL_ID, pkt); + // TODO: flush packets? + // TODO: get rid of the extra heap allocation here... + delete msg; } void @@ -347,9 +287,7 @@ UdpProtocol::OnMsg(UdpMsg *msg, int len) &UdpProtocol::OnInput, /* Input */ &UdpProtocol::OnQualityReport, /* QualityReport */ &UdpProtocol::OnQualityReply, /* QualityReply */ - &UdpProtocol::OnKeepAlive, /* KeepAlive */ &UdpProtocol::OnInputAck, /* InputAck */ - &UdpProtocol::OnChat, /* InputAck */ }; // filter out messages that don't match what we expect @@ -379,10 +317,6 @@ UdpProtocol::OnMsg(UdpMsg *msg, int len) } if (handled) { _last_recv_time = Platform::GetCurrentTimeMS(); - if (_disconnect_notify_sent && _current_state == Running) { - QueueEvent(Event(Event::NetworkResumed)); - _disconnect_notify_sent = false; - } } } @@ -422,7 +356,7 @@ UdpProtocol::QueueEvent(const UdpProtocol::Event &evt) void UdpProtocol::Synchronize() { - if (_udp) { + if (_peer) { _current_state = Syncing; _state.sync.roundtrips_remaining = NUM_SYNC_PACKETS; SendSyncRequest(); @@ -470,18 +404,12 @@ UdpProtocol::LogMsg(const char *prefix, UdpMsg *msg) case UdpMsg::QualityReply: Log("%s quality reply.\n", prefix); break; - case UdpMsg::KeepAlive: - Log("%s keep alive.\n", prefix); - break; case UdpMsg::Input: Log("%s game-compressed-input %d (+ %d bits).\n", prefix, msg->u.input.start_frame, msg->u.input.num_bits); break; case UdpMsg::InputAck: Log("%s input ack.\n", prefix); break; - case UdpMsg::Chat: - Log("%s chat.\n", prefix); - break; default: Log("Unknown UdpMsg type."); } @@ -559,28 +487,16 @@ UdpProtocol::OnSyncReply(UdpMsg *msg, int len) bool UdpProtocol::OnInput(UdpMsg *msg, int len) { - /* - * If a disconnect is requested, go ahead and disconnect now. - */ - bool disconnect_requested = msg->u.input.disconnect_requested; - if (disconnect_requested) { - if (_current_state != Disconnected && !_disconnect_event_sent) { - Log("Disconnecting endpoint on remote request.\n"); - QueueEvent(Event(Event::Disconnected)); - _disconnect_event_sent = true; - } - } else { - /* - * Update the peer connection status if this peer is still considered to be part - * of the network. - */ - UdpMsg::connect_status* remote_status = msg->u.input.peer_connect_status; - for (int i = 0; i < ARRAY_SIZE(_peer_connect_status); i++) { - ASSERT(remote_status[i].last_frame >= _peer_connect_status[i].last_frame); - _peer_connect_status[i].disconnected = _peer_connect_status[i].disconnected || remote_status[i].disconnected; - _peer_connect_status[i].last_frame = MAX(_peer_connect_status[i].last_frame, remote_status[i].last_frame); - } - } + /* + * Update the peer connection status if this peer is still considered to be part + * of the network. + */ + UdpMsg::connect_status* remote_status = msg->u.input.peer_connect_status; + for (int i = 0; i < ARRAY_SIZE(_peer_connect_status); i++) { + ASSERT(remote_status[i].last_frame >= _peer_connect_status[i].last_frame); + _peer_connect_status[i].disconnected = _peer_connect_status[i].disconnected || remote_status[i].disconnected; + _peer_connect_status[i].last_frame = MAX(_peer_connect_status[i].last_frame, remote_status[i].last_frame); + } /* * Decompress the input. @@ -701,23 +617,6 @@ UdpProtocol::OnQualityReply(UdpMsg *msg, int len) return true; } -bool -UdpProtocol::OnKeepAlive(UdpMsg *msg, int len) -{ - return true; -} -void UdpProtocol::ConsumeChat(std::function onChat) -{ - for (const auto& msg : _chatMessages) - onChat(msg.c_str()); - _chatMessages.clear(); -} -bool UdpProtocol::OnChat(UdpMsg* msg, int len) -{ - _chatMessages.push_back(msg->u.chat.msg); - return true; -} - void UdpProtocol::GetNetworkStats(struct GGPONetworkStats *s) { @@ -755,65 +654,3 @@ UdpProtocol::RecommendFrameDelay() // XXX: require idle input should be a configuration parameter return _timesync.recommend_frame_wait_duration(false); } - - -void -UdpProtocol::SetDisconnectTimeout(int timeout) -{ - _disconnect_timeout = timeout; -} - -void -UdpProtocol::SetDisconnectNotifyStart(int timeout) -{ - _disconnect_notify_start = timeout; -} - -void -UdpProtocol::PumpSendQueue() -{ - while (!_send_queue.empty()) { - QueueEntry &entry = _send_queue.front(); - - if (_send_latency) { - // should really come up with a gaussian distributation based on the configured - // value, but this will do for now. - int jitter = (_send_latency * 2 / 3) + ((rand() % _send_latency) / 3); - if (Platform::GetCurrentTimeMS() < _send_queue.front().queue_time + jitter) { - break; - } - } - if (_oop_percent && !_oo_packet.msg && ((rand() % 100) < _oop_percent)) { - int delay = rand() % (_send_latency * 10 + 1000); - Log("creating rogue oop (seq: %d delay: %d)\n", entry.msg->hdr.sequence_number, delay); - _oo_packet.send_time = Platform::GetCurrentTimeMS() + delay; - _oo_packet.msg = entry.msg; - _oo_packet.dest_addr = entry.dest_addr; - } else { - ASSERT(entry.dest_addr.sin_addr.s_addr); - - _udp->SendTo((char *)entry.msg, entry.msg->PacketSize(), 0, - (struct sockaddr *)&entry.dest_addr, sizeof entry.dest_addr); - - delete entry.msg; - } - _send_queue.pop(); - } - if (_oo_packet.msg && _oo_packet.send_time < Platform::GetCurrentTimeMS()) { - Log("sending rogue oop!"); - _udp->SendTo((char *)_oo_packet.msg, _oo_packet.msg->PacketSize(), 0, - (struct sockaddr *)&_oo_packet.dest_addr, sizeof _oo_packet.dest_addr); - - delete _oo_packet.msg; - _oo_packet.msg = NULL; - } -} - -void -UdpProtocol::ClearSendQueue() -{ - while (!_send_queue.empty()) { - delete _send_queue.front().msg; - _send_queue.pop(); - } -} diff --git a/dep/ggpo-x/src/network/udp_proto.h b/dep/ggpo-x/src/network/udp_proto.h index 086beda1c..e04719485 100644 --- a/dep/ggpo-x/src/network/udp_proto.h +++ b/dep/ggpo-x/src/network/udp_proto.h @@ -8,8 +8,7 @@ #ifndef _UDP_PROTO_H_ #define _UDP_PROTO_H_ -#include "poll.h" -#include "udp.h" +#include "enet/enet.h" #include "udp_msg.h" #include "game_input.h" #include "timesync.h" @@ -19,7 +18,7 @@ #include #include #include -class UdpProtocol : public IPollSink +class UdpProtocol { public: struct Stats { @@ -29,7 +28,6 @@ public: float av_remote_frame_advantage; float av_local_frame_advantage; int send_queue_len; - Udp::Stats udp; }; struct Event { @@ -39,9 +37,6 @@ public: Synchronizing, Synchronzied, Input, - Disconnected, - NetworkInterrupted, - NetworkResumed, }; Type type; @@ -62,23 +57,23 @@ public: }; public: - virtual bool OnLoopPoll(void *cookie); + bool NetworkIdle(); public: UdpProtocol(); - virtual ~UdpProtocol(); + ~UdpProtocol(); - void Init(Udp *udp, Poll &p, int queue, char *ip, u_short port, UdpMsg::connect_status *status); + ENetPeer* GetENetPeer() const { return _peer; } + + void Init(ENetPeer* peer, int queue, UdpMsg::connect_status *status); void Synchronize(); bool GetPeerConnectStatus(int id, int *frame); - bool IsInitialized() { return _udp != NULL; } + bool IsInitialized() { return _peer != nullptr; } bool IsSynchronized() { return _current_state == Running; } bool IsRunning() { return _current_state == Running; } void SendInput(GameInput &input); - void SendChat(const char* message); void SendInputAck(); - bool HandlesMsg(sockaddr_in &from, UdpMsg *msg); void OnMsg(UdpMsg *msg, int len); void Disconnect(); @@ -87,10 +82,7 @@ public: void SetLocalFrameNumber(int num); float RecommendFrameDelay(); int RemoteFrameDelay()const; - void SetDisconnectTimeout(int timeout); - void SetDisconnectNotifyStart(int timeout); void SetFrameDelay(int delay); - void ConsumeChat(std::function onChat); void ApplyToEvents(std::function cb); void StartPollLoop(); void EndPollLoop(); @@ -114,13 +106,11 @@ protected: void UpdateNetworkStats(void); void QueueEvent(const UdpProtocol::Event &evt); - void ClearSendQueue(void); void Log(const char *fmt, ...); void LogMsg(const char *prefix, UdpMsg *msg); void LogEvent(const char *prefix, const UdpProtocol::Event &evt); void SendSyncRequest(); void SendMsg(UdpMsg *msg); - void PumpSendQueue(); void SendPendingOutput(); bool OnInvalid(UdpMsg *msg, int len); bool OnSyncRequest(UdpMsg *msg, int len); @@ -129,27 +119,16 @@ protected: bool OnInputAck(UdpMsg *msg, int len); bool OnQualityReport(UdpMsg *msg, int len); bool OnQualityReply(UdpMsg *msg, int len); - bool OnKeepAlive(UdpMsg *msg, int len); - bool OnChat(UdpMsg *msg, int len); protected: /* * Network transmission information */ - Udp *_udp; - sockaddr_in _peer_addr; + ENetPeer *_peer; uint16 _magic_number; int _queue; uint16 _remote_magic_number; bool _connected; - int _send_latency; - int _oop_percent; - struct { - int send_time; - sockaddr_in dest_addr; - UdpMsg* msg; - } _oo_packet; - RingBuffer _send_queue; /* * Stats @@ -194,11 +173,6 @@ protected: GameInput _last_acked_input; unsigned int _last_send_time; unsigned int _last_recv_time; - unsigned int _shutdown_timeout; - unsigned int _disconnect_event_sent; - unsigned int _disconnect_timeout; - unsigned int _disconnect_notify_start; - bool _disconnect_notify_sent; uint16 _next_send_seq; uint16 _next_recv_seq; @@ -212,7 +186,6 @@ protected: * Event queue */ RingBuffer _event_queue; - std::vector _chatMessages; }; #endif diff --git a/dep/ggpo-x/src/poll.cpp b/dep/ggpo-x/src/poll.cpp deleted file mode 100644 index b5c0b5005..000000000 --- a/dep/ggpo-x/src/poll.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* ----------------------------------------------------------------------- - * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. - * - * Use of this software is governed by the MIT license that can be found - * in the LICENSE file. - */ - -#include "types.h" -#include "poll.h" - -Poll::Poll(void) : - _start_time(0) -{ -} - - -// -//void -//Poll::RegisterMsgLoop(IPollSink *sink, void *cookie) -//{ -// _msg_sinks.push_back(PollSinkCb(sink, cookie)); -//} - -void -Poll::RegisterLoop(IPollSink *sink, void *cookie) -{ - _loop_sinks.push_back(PollSinkCb(sink, cookie)); -} -//void -//Poll::RegisterPeriodic(IPollSink *sink, int interval, void *cookie) -//{ -// _periodic_sinks.push_back(PollPeriodicSinkCb(sink, cookie, interval)); -//} - -void -Poll::Run() -{ - while (Pump(100)) { - continue; - } -} - -bool -Poll::Pump(int timeout) -{ - int i; - bool finished = false; - - for (i = 0; i < _loop_sinks.size(); i++) { - PollSinkCb &cb = _loop_sinks[i]; - finished = !cb.sink->OnLoopPoll(cb.cookie) || finished; - } - return finished; -} -// -//int -//Poll::ComputeWaitTime(int elapsed) -//{ -// int waitTime = INFINITE; -// size_t count = _periodic_sinks.size(); -// -// if (count > 0) { -// for (int i = 0; i < count; i++) { -// PollPeriodicSinkCb &cb = _periodic_sinks[i]; -// int timeout = (cb.interval + cb.last_fired) - elapsed; -// if (waitTime == INFINITE || (timeout < waitTime)) { -// waitTime = MAX(timeout, 0); -// } -// } -// } -// return waitTime; -//} diff --git a/dep/ggpo-x/src/poll.h b/dep/ggpo-x/src/poll.h deleted file mode 100644 index 597cf58ba..000000000 --- a/dep/ggpo-x/src/poll.h +++ /dev/null @@ -1,56 +0,0 @@ -/* ----------------------------------------------------------------------- - * GGPO.net (http://ggpo.net) - Copyright 2009 GroundStorm Studios, LLC. - * - * Use of this software is governed by the MIT license that can be found - * in the LICENSE file. - */ - -#ifndef _POLL_H -#define _POLL_H - -#include "static_buffer.h" - -#define MAX_POLLABLE_HANDLES 64 - - -class IPollSink { -public: - virtual ~IPollSink() { } - //virtual bool OnMsgPoll(void*) = 0;//{ return true; } - // virtual bool OnPeriodicPoll(void*, int) = 0;// { return true; } - virtual bool OnLoopPoll(void*) = 0;// { return true; } -}; - -class Poll { -public: - Poll(void); - void RegisterLoop(IPollSink *sink, void *cookie = NULL); - - void Run(); - bool Pump(int timeout); - -protected: - //int ComputeWaitTime(int elapsed); - - struct PollSinkCb { - IPollSink *sink; - void *cookie; - PollSinkCb() : sink(NULL), cookie(NULL) { } - PollSinkCb(IPollSink *s, void *c) : sink(s), cookie(c) { } - }; - - struct PollPeriodicSinkCb : public PollSinkCb { - int interval; - int last_fired; - PollPeriodicSinkCb() : PollSinkCb(NULL, NULL), interval(0), last_fired(0) { } - PollPeriodicSinkCb(IPollSink *s, void *c, int i) : - PollSinkCb(s, c), interval(i), last_fired(0) { } - }; - - int _start_time; - // StaticBuffer _msg_sinks; - StaticBuffer _loop_sinks; - // StaticBuffer _periodic_sinks; -}; - -#endif diff --git a/src/core/core.props b/src/core/core.props index 6f6436108..1a7fe47d3 100644 --- a/src/core/core.props +++ b/src/core/core.props @@ -9,7 +9,7 @@ WITH_RECOMPILER=1;%(PreprocessorDefinitions) WITH_MMAP_FASTMEM=1;%(PreprocessorDefinitions) - $(SolutionDir)dep\ggpo-x\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\rcheevos\include;$(SolutionDir)dep\rapidjson\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) + $(SolutionDir)dep\enet\include;$(SolutionDir)dep\ggpo-x\include;$(SolutionDir)dep\tinyxml2\include;$(SolutionDir)dep\glad\include;$(SolutionDir)dep\stb\include;$(SolutionDir)dep\imgui\include;$(SolutionDir)dep\xxhash\include;$(SolutionDir)dep\zlib\include;$(SolutionDir)dep\rcheevos\include;$(SolutionDir)dep\rapidjson\include;$(SolutionDir)src;%(AdditionalIncludeDirectories) $(SolutionDir)dep\rainterface;%(AdditionalIncludeDirectories) $(SolutionDir)dep\xbyak\xbyak;%(AdditionalIncludeDirectories) @@ -19,7 +19,7 @@ - $(RootBuildDir)ggpo-x\ggpo-x.lib;$(RootBuildDir)tinyxml2\tinyxml2.lib;$(RootBuildDir)rcheevos\rcheevos.lib;$(RootBuildDir)imgui\imgui.lib;$(RootBuildDir)stb\stb.lib;$(RootBuildDir)xxhash\xxhash.lib;$(RootBuildDir)zlib\zlib.lib;$(RootBuildDir)util\util.lib;$(RootBuildDir)common\common.lib;%(AdditionalDependencies) + $(RootBuildDir)ggpo-x\ggpo-x.lib;$(RootBuildDir)enet\enet.lib;$(RootBuildDir)tinyxml2\tinyxml2.lib;$(RootBuildDir)rcheevos\rcheevos.lib;$(RootBuildDir)imgui\imgui.lib;$(RootBuildDir)stb\stb.lib;$(RootBuildDir)xxhash\xxhash.lib;$(RootBuildDir)zlib\zlib.lib;$(RootBuildDir)util\util.lib;$(RootBuildDir)common\common.lib;%(AdditionalDependencies) $(RootBuildDir)rainterface\rainterface.lib;%(AdditionalDependencies) $(RootBuildDir)vixl\vixl.lib;%(AdditionalDependencies) diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index fe1497d1a..09667027e 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -7,12 +7,14 @@ #include "common/timer.h" #include "digital_controller.h" #include "ggponet.h" +#include "enet/enet.h" #include "host.h" #include "host_settings.h" #include "pad.h" #include "spu.h" #include "system.h" #include +#include #include #include Log_SetChannel(Netplay); @@ -30,6 +32,8 @@ struct Input u32 button_data; }; +static bool InitializeEnet(); + static bool NpAdvFrameCb(void* ctx, int flags); static bool NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame); static bool NpLoadFrameCb(void* ctx, unsigned char* buffer, int len, int rb_frames, int frame_to_load); @@ -37,6 +41,7 @@ static bool NpBeginGameCb(void* ctx, const char* game_name); static void NpFreeBuffCb(void* ctx, void* buffer, int frame); static bool NpOnEventCb(void* ctx, GGPOEvent* ev); +static GGPOPlayerHandle PlayerIdToGGPOHandle(s32 player_id); static Input ReadLocalInput(); static GGPOErrorCode AddLocalInput(Netplay::Input input); static GGPOErrorCode SyncInput(Input inputs[2], int* disconnect_flags); @@ -44,8 +49,20 @@ static void SetInputs(Input inputs[2]); static void SetSettings(); +static bool CreateSystem(std::string game_path); + +// ENet +static void ShutdownEnetHost(); +static s32 GetFreePlayerId(); +static s32 GetPlayerIdForPeer(const ENetPeer* peer); +static bool ConnectToLowerPeers(gsl::span peer_addresses); +static bool WaitForPeerConnections(); +static void HandleEnetEvent(const ENetEvent* event); +static void PollEnet(Common::Timer::Value until_time); +static void HandleControlPacket(s32 player_id, const ENetPacket* pkt); + // l = local, r = remote -static s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred); +static s32 Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, s32 ldelay, u32 pred, std::string game_path); static void AdvanceFrame(); static void RunFrame(); @@ -62,13 +79,18 @@ static void Throttle(); // Desync Detection static void GenerateChecksumForFrame(int* checksum, int frame, unsigned char* buffer, int buffer_size); static void GenerateDesyncReport(s32 desync_frame); + ////////////////////////////////////////////////////////////////////////// // Variables ////////////////////////////////////////////////////////////////////////// static MemorySettingsInterface s_settings_overlay; -static std::string s_game_path; + +/// Enet +static ENetHost* s_enet_host; +static std::array s_enet_peers; +static s32 s_player_id; static GGPOPlayerHandle s_local_handle = GGPO_INVALID_HANDLE; static GGPONetworkStats s_last_net_stats{}; @@ -88,11 +110,301 @@ static s32 s_next_timesync_recovery_frame = -1; // Netplay Impl -s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred) +bool Netplay::CreateSystem(std::string game_path) +{ + // close system if its already running + if (System::IsValid()) + System::ShutdownSystem(false); + + // fast boot the selected game and wait for the other player + auto param = SystemBootParameters(std::move(game_path)); + param.override_fast_boot = true; + return System::BootSystem(param); +} + +bool Netplay::InitializeEnet() +{ + static bool enet_initialized = false; + int rc; + if (!enet_initialized && (rc = enet_initialize()) != 0) + { + Log_ErrorPrintf("enet_initialize() returned %d", rc); + return false; + } + + std::atexit(enet_deinitialize); + enet_initialized = true; + return true; +} + +void Netplay::ShutdownEnetHost() +{ + Log_DevPrint("Disconnecting all peers"); + + // forcefully disconnect all peers + // TODO: do we want to send disconnect requests and wait a bit? + for (u32 i = 0; i < MAX_PLAYERS; i++) + { + if (s_enet_peers[i]) + { + enet_peer_reset(s_enet_peers[i]); + s_enet_peers[i] = nullptr; + } + } + + enet_host_destroy(s_enet_host); + s_enet_host = nullptr; +} + +GGPOPlayerHandle PlayerIdToGGPOHandle(s32 player_id) +{ + return player_id + 1; +} + +s32 Netplay::GetPlayerIdForPeer(const ENetPeer* peer) +{ + for (s32 i = 0; i < MAX_PLAYERS; i++) + { + if (s_enet_peers[i] == peer) + return i; + } + + return -1; +} + +s32 Netplay::GetFreePlayerId() +{ + for (s32 i = 0; i < MAX_PLAYERS; i++) + { + if (i != s_player_id && !s_enet_peers[i]) + return i; + } + + return -1; +} + +void Netplay::HandleEnetEvent(const ENetEvent* event) +{ + switch (event->type) + { + case ENET_EVENT_TYPE_CONNECT: + { + // skip when it's one we set up ourselves, we're handling it in ConnectToLowerPeers(). + if (GetPlayerIdForPeer(event->peer) >= 0) + return; + + // TODO: the player ID should either come from the packet (for the non-first player), + // or be auto-assigned as below, for the connection to the first host + const s32 new_player_id = GetFreePlayerId(); + Log_DevPrintf("Enet connect event: New client with id %d", new_player_id); + + if (new_player_id < 0) + { + Log_ErrorPrintf("No free slots, disconnecting client"); + enet_peer_disconnect(event->peer, 1); + return; + } + + s_enet_peers[new_player_id] = event->peer; + } + break; + + case ENET_EVENT_TYPE_DISCONNECT: + { + const s32 player_id = GetPlayerIdForPeer(event->peer); + if (player_id < 0) + return; + + // TODO: This one's gonna get kinda tricky... who do we orphan when they disconnect? + Log_WarningPrintf("ENet player %d disconnected", player_id); + Host::OnNetplayMessage(fmt::format("*** DISCONNECTED PLAYER {} ***", player_id)); + ggpo_disconnect_player(s_ggpo, PlayerIdToGGPOHandle(player_id)); + s_enet_peers[player_id] = nullptr; + } + break; + + case ENET_EVENT_TYPE_RECEIVE: + { + const s32 player_id = GetPlayerIdForPeer(event->peer); + if (player_id < 0) + { + Log_WarningPrintf("Received packet from unknown player"); + return; + } + + if (event->channelID == ENET_CHANNEL_CONTROL) + { + HandleControlPacket(player_id, event->packet); + } + else if (event->channelID == ENET_CHANNEL_GGPO) + { + Log_TracePrintf("Received %zu ggpo bytes from player %d", event->packet->dataLength, player_id); + const int rc = ggpo_handle_packet(s_ggpo, event->peer, event->packet); + if (rc != GGPO_OK) + Log_ErrorPrintf("Failed to process GGPO packet!"); + } + else + { + Log_ErrorPrintf("Unexpected packet channel %u", event->channelID); + } + } + break; + + default: + { + Log_WarningPrintf("Unhandled enet event %d", event->type); + } + break; + } +} + +void Netplay::PollEnet(Common::Timer::Value until_time) +{ + ENetEvent event; + + u64 current_time = Common::Timer::GetCurrentValue(); + + for (;;) + { + const u32 enet_timeout = (current_time >= until_time) ? + 0 : + static_cast(Common::Timer::ConvertValueToMilliseconds(until_time - current_time)); + const int res = enet_host_service(s_enet_host, &event, enet_timeout); + if (res > 0) + { + HandleEnetEvent(&event); + + // make sure we get all events + current_time = Common::Timer::GetCurrentValue(); + continue; + } + + // exit once we're nonblocking + current_time = Common::Timer::GetCurrentValue(); + if (enet_timeout == 0 || current_time >= until_time) + break; + } +} + +bool Netplay::ConnectToLowerPeers(gsl::span peer_addresses) +{ + for (size_t i = 0; i < peer_addresses.size(); i++) + { + char ipstr[32]; + if (enet_address_get_host_ip(&peer_addresses[i], ipstr, std::size(ipstr)) != 0) + ipstr[0] = 0; + Log_DevPrintf("Starting connection to peer %u at %s:%u", i, ipstr, peer_addresses[i].port); + + DebugAssert(i != s_player_id); + s_enet_peers[i] = enet_host_connect(s_enet_host, &peer_addresses[i], NUM_ENET_CHANNELS, 0); + if (!s_enet_peers[i]) + { + Log_ErrorPrintf("enet_host_connect() for peer %u failed", i); + return false; + } + } + + return true; +} + +bool Netplay::WaitForPeerConnections() +{ + static constexpr float MAX_CONNECT_TIME = 30.0f; + Common::Timer timeout; + + const u32 clients_to_connect = MAX_PLAYERS - 1; + + for (;;) + { + // TODO: Handle early shutdown/cancel request. + u32 num_connected_peers = 0; + for (s32 i = 0; i < MAX_PLAYERS; i++) + { + if (i != s_player_id && s_enet_peers[i] && s_enet_peers[i]->state == ENET_PEER_STATE_CONNECTED) + num_connected_peers++; + } + if (num_connected_peers == clients_to_connect) + break; + + if (timeout.GetTimeSeconds() >= MAX_CONNECT_TIME) + { + Log_ErrorPrintf("Peer connection timeout"); + return false; + } + + Host::PumpMessagesOnCPUThread(); + Host::DisplayLoadingScreen("Connected to netplay peers", 0, clients_to_connect, num_connected_peers); + + const Common::Timer::Value poll_end_time = + Common::Timer::GetCurrentValue() + Common::Timer::ConvertMillisecondsToValue(16); + PollEnet(poll_end_time); + } + + Log_InfoPrint("Peer connection complete."); + return true; +} + +void Netplay::HandleControlPacket(s32 player_id, const ENetPacket* pkt) +{ + // TODO + Log_ErrorPrintf("Unhandled control packet from player %d of size %zu", player_id, pkt->dataLength); +} + +s32 Netplay::Start(s32 lhandle, u16 lport, const std::string& raddr, u16 rport, s32 ldelay, u32 pred, std::string game_path) { SetSettings(); + if (!InitializeEnet()) + return -1; + + ENetAddress host_address; + host_address.host = ENET_HOST_ANY; + host_address.port = lport; + s_enet_host = enet_host_create(&host_address, MAX_PLAYERS - 1, NUM_ENET_CHANNELS, 0, 0); + if (!s_enet_host) + { + Log_ErrorPrintf("Failed to create enet host."); + return -1; + } + + // Connect to any lower-ID'ed hosts. + // Eventually we'll assign these IDs as players connect, and everyone not starting it will get their ID sent back + s_player_id = lhandle - 1; + + std::array peer_addresses; + const u32 num_peer_addresses = s_player_id; + DebugAssert(num_peer_addresses == 0 || num_peer_addresses == 1); + if (num_peer_addresses == 1) + { + // TODO: rewrite this when we support more players + const u32 other_player_id = (lhandle == 1) ? 1 : 0; + if (enet_address_set_host_ip(&peer_addresses[other_player_id], raddr.c_str()) != 0) + { + Log_ErrorPrintf("Failed to parse host: '%s'", raddr.c_str()); + ShutdownEnetHost(); + return -1; + } + + peer_addresses[other_player_id].port = rport; + } + + // Create system. + if (!CreateSystem(std::move(game_path))) + { + Log_ErrorPrintf("Failed to create system."); + ShutdownEnetHost(); + return -1; + } InitializeFramePacing(); + // Connect to all peers. + if ((num_peer_addresses > 0 && + !ConnectToLowerPeers(gsl::span(peer_addresses).subspan(0, num_peer_addresses))) || + !WaitForPeerConnections()) + { + // System shutdown cleans up enet. + return -1; + } + /* TODO: since saving every frame during rollback loses us time to do actual gamestate iterations it might be better to hijack the update / save / load cycle to only save every confirmed frame only saving when actually needed. @@ -108,37 +420,51 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld GGPOErrorCode result; - result = - ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES); - // result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1); + result = ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", MAX_PLAYERS, sizeof(Netplay::Input), lport, MAX_ROLLBACK_FRAMES); + if (!GGPO_SUCCEEDED(result)) + { + Log_ErrorPrintf("ggpo_start_session() failed: %d", result); + return -1; + } ggpo_set_disconnect_timeout(s_ggpo, 2000); ggpo_set_disconnect_notify_start(s_ggpo, 1000); - for (int i = 1; i <= 2; i++) + for (s32 i = 0; i < MAX_PLAYERS; i++) { - GGPOPlayer player = {}; - GGPOPlayerHandle handle = 0; + if (!s_enet_peers[i] && i != s_player_id) + continue; - player.size = sizeof(GGPOPlayer); - player.player_num = i; + // This is *awful*. Need to merge the player IDs, enough of this indices-start-at-1 rubbish. + const GGPOPlayerHandle expected_handle = PlayerIdToGGPOHandle(i); - if (lhandle == i) + GGPOPlayer player = { sizeof(GGPOPlayer) }; + GGPOPlayerHandle got_handle; + player.player_num = expected_handle; + if (i == s_player_id) { - player.type = GGPOPlayerType::GGPO_PLAYERTYPE_LOCAL; - result = ggpo_add_player(s_ggpo, &player, &handle); - s_local_handle = handle; + player.type = GGPO_PLAYERTYPE_LOCAL; + result = ggpo_add_player(s_ggpo, &player, &got_handle); + if (GGPO_SUCCEEDED(result)) + s_local_handle = got_handle; } else { - player.type = GGPOPlayerType::GGPO_PLAYERTYPE_REMOTE; - StringUtil::Strlcpy(player.u.remote.ip_address, raddr.c_str(), std::size(player.u.remote.ip_address)); - player.u.remote.port = rport; - result = ggpo_add_player(s_ggpo, &player, &handle); + player.type = GGPO_PLAYERTYPE_REMOTE; + player.u.remote.peer = s_enet_peers[i]; + result = ggpo_add_player(s_ggpo, &player, &got_handle); } + + if (!GGPO_SUCCEEDED(result)) + { + Log_ErrorPrintf("Failed to add player %d", i); + return -1; + } + + Assert(expected_handle == got_handle); } + ggpo_set_frame_delay(s_ggpo, s_local_handle, ldelay); - ggpo_set_manual_network_polling(s_ggpo, true); return result; } @@ -152,6 +478,8 @@ void Netplay::CloseSession() s_save_buffer_pool.clear(); s_local_handle = GGPO_INVALID_HANDLE; + ShutdownEnetHost(); + // Restore original settings. Host::Internal::SetNetplaySettingsLayer(nullptr); System::ApplySettings(false); @@ -258,8 +586,10 @@ void Netplay::Throttle() const Common::Timer::Value sleep_period = Common::Timer::ConvertMillisecondsToValue(1); for (;;) { - // Poll network. - ggpo_poll_network(s_ggpo); + // TODO: make better, we can tell this function to stall until the next frame + PollEnet(0); + ggpo_network_idle(s_ggpo); + PollEnet(0); current_time = Common::Timer::GetCurrentValue(); if (current_time >= s_next_frame_time) @@ -322,7 +652,12 @@ void Netplay::AdvanceFrame() void Netplay::RunFrame() { // housekeeping + // TODO: get rid of double polling + PollEnet(0); + ggpo_network_idle(s_ggpo); + PollEnet(0); ggpo_idle(s_ggpo); + // run game auto result = GGPO_OK; int disconnect_flags = 0; @@ -358,6 +693,11 @@ void Netplay::CollectInput(u32 slot, u32 bind, float value) s_net_input[slot][bind] = value; } +GGPOPlayerHandle Netplay::PlayerIdToGGPOHandle(s32 player_id) +{ + return player_id + 1; +} + Netplay::Input Netplay::ReadLocalInput() { // get controller data of the first controller (0 internally) @@ -372,7 +712,6 @@ Netplay::Input Netplay::ReadLocalInput() void Netplay::SendMsg(const char* msg) { - ggpo_client_chat(s_ggpo, msg); } GGPOErrorCode Netplay::SyncInput(Netplay::Input inputs[2], int* disconnect_flags) @@ -414,13 +753,15 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string& // dont want to start a session when theres already one going on. if (IsActive()) return; - // set game path for later loading during the begin game callback - s_game_path = std::move(game_path); + // create session - int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES); + int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES, std::move(game_path)); // notify that the session failed if (result != GGPO_OK) + { Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result); + System::ShutdownSystem(false); + } else { // Load savestate if available @@ -465,17 +806,6 @@ void Netplay::ExecuteNetplay() bool Netplay::NpBeginGameCb(void* ctx, const char* game_name) { - // close system if its already running - if (System::IsValid()) - System::ShutdownSystem(false); - // fast boot the selected game and wait for the other player - auto param = SystemBootParameters(s_game_path); - param.override_fast_boot = true; - if (!System::BootSystem(param)) - { - StopNetplaySession(); - return false; - } SPU::SetAudioOutputMuted(true); // Fast Forward to Game Start if needed. while (System::GetInternalFrameNumber() < 2) @@ -554,22 +884,9 @@ bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev) case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER: Host::OnNetplayMessage(fmt::format("Netplay Synchronized With Player: {}", ev->u.synchronized.player)); break; - case GGPOEventCode::GGPO_EVENTCODE_DISCONNECTED_FROM_PEER: - Host::OnNetplayMessage(fmt::format("Netplay Player: %d Disconnected", ev->u.disconnected.player)); - break; case GGPOEventCode::GGPO_EVENTCODE_RUNNING: Host::OnNetplayMessage("Netplay Is Running"); break; - case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_INTERRUPTED: - Host::OnNetplayMessage(fmt::format("Netplay Player: {} Connection Interupted, Timeout: {}", ev->u.connection_interrupted.player, - ev->u.connection_interrupted.disconnect_timeout)); - break; - case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_RESUMED: - Host::OnNetplayMessage(fmt::format("Netplay Player: {} Connection Resumed", ev->u.connection_resumed.player)); - break; - case GGPOEventCode::GGPO_EVENTCODE_CHAT: - Host::OnNetplayMessage(ev->u.chat.msg); - break; case GGPOEventCode::GGPO_EVENTCODE_TIMESYNC: HandleTimeSyncEvent(ev->u.timesync.frames_ahead, ev->u.timesync.timeSyncPeriodInFrames); break; diff --git a/src/core/netplay.h b/src/core/netplay.h index 5b871a204..7456a748b 100644 --- a/src/core/netplay.h +++ b/src/core/netplay.h @@ -5,7 +5,7 @@ namespace Netplay { -enum : u32 +enum : s32 { // Maximum number of emulated controllers. MAX_PLAYERS = 2, @@ -13,6 +13,14 @@ enum : u32 MAX_ROLLBACK_FRAMES = 8, }; +enum : u8 +{ + ENET_CHANNEL_CONTROL = 0, + ENET_CHANNEL_GGPO = 1, + + NUM_ENET_CHANNELS, +}; + void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay, std::string game_path); void StopNetplaySession(); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 80b368fd1..9570a48a9 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1089,6 +1089,9 @@ void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const auto remAddr = remote_addr.trimmed().toStdString(); auto gamePath = game_path.trimmed().toStdString(); Netplay::StartNetplaySession(local_handle, local_port, remAddr, remote_port, input_delay, gamePath); + + // TODO: Fix this junk.. for some reason, it stays sleeping... + g_emu_thread->wakeThread(); } void EmuThread::sendNetplayMessage(const QString& message) @@ -2243,6 +2246,9 @@ int main(int argc, char* argv[]) std::string remote = "127.0.0.1"; std::string game = "D:\\PSX\\chd\\padtest.chd"; Netplay::StartNetplaySession(h, port_base + h, remote, port_base + nh, 1, game); + + // TODO: Fix this junk.. for some reason, it stays sleeping... + g_emu_thread->wakeThread(); }); } diff --git a/src/frontend-common/common_host.cpp b/src/frontend-common/common_host.cpp index 0b6e1d091..7aa5231ec 100644 --- a/src/frontend-common/common_host.cpp +++ b/src/frontend-common/common_host.cpp @@ -466,14 +466,14 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, ImGui::Text("%s: %d/%d", message, progress_value, progress_max); ImGui::ProgressBar(static_cast(progress_value) / static_cast(progress_max - progress_min), ImVec2(-1.0f, 0.0f), ""); - Log_InfoPrintf("%s: %d/%d", message, progress_value, progress_max); + Log_DebugPrintf("%s: %d/%d", message, progress_value, progress_max); } else { const ImVec2 text_size(ImGui::CalcTextSize(message)); ImGui::SetCursorPosX((width - text_size.x) / 2.0f); ImGui::TextUnformatted(message); - Log_InfoPrintf("%s", message); + Log_DebugPrintf("%s", message); } } ImGui::End();