diff --git a/dep/ggpo-x/include/ggponet.h b/dep/ggpo-x/include/ggponet.h index 96ce61500..188dfcaba 100644 --- a/dep/ggpo-x/include/ggponet.h +++ b/dep/ggpo-x/include/ggponet.h @@ -541,8 +541,9 @@ GGPO_API GGPOErrorCode __cdecl ggpo_disconnect_player(GGPOSession *, GGPO_API GGPOErrorCode __cdecl ggpo_advance_frame(GGPOSession *, uint16_t checksum); /* - * ggpo_get_current_frame -- current frame GGPO is dealing with - * + * ggpo_get_current_frame -- + * + * current frame GGPO is dealing with */ GGPO_API GGPOErrorCode __cdecl ggpo_get_current_frame(GGPOSession* ggpo, int& nFrame); /* @@ -573,8 +574,16 @@ 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 @@ -584,7 +593,15 @@ GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_timeout(GGPOSession *, * before the GGPO_EVENTCODE_NETWORK_INTERRUPTED event is sent. */ GGPO_API GGPOErrorCode __cdecl ggpo_set_disconnect_notify_start(GGPOSession *, - int timeout); + 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*); /* * ggpo_log -- diff --git a/dep/ggpo-x/src/backends/backend.h b/dep/ggpo-x/src/backends/backend.h index 9b2466a8d..7b747e0ae 100644 --- a/dep/ggpo-x/src/backends/backend.h +++ b/dep/ggpo-x/src/backends/backend.h @@ -22,12 +22,14 @@ public: 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; }; diff --git a/dep/ggpo-x/src/backends/p2p.cpp b/dep/ggpo-x/src/backends/p2p.cpp index ab090f9c9..210ae2e35 100644 --- a/dep/ggpo-x/src/backends/p2p.cpp +++ b/dep/ggpo-x/src/backends/p2p.cpp @@ -27,6 +27,7 @@ Peer2PeerBackend::Peer2PeerBackend(GGPOSessionCallbacks *cb, _callbacks = *cb; _synchronizing = true; _next_recommended_sleep = 0; + _manual_network_polling = false; /* * Initialize the synchronziation layer @@ -150,6 +151,7 @@ void Peer2PeerBackend::CheckDesync() } } + GGPOErrorCode Peer2PeerBackend::DoPoll() { @@ -165,7 +167,8 @@ Peer2PeerBackend::DoPoll() } if (!_sync.InRollback()) { - _poll.Pump(0); + if (!_manual_network_polling) + _poll.Pump(0); PollUdpProtocolEvents(); CheckDesync(); @@ -402,11 +405,27 @@ Peer2PeerBackend::SyncInput(void *values, } return GGPO_OK; } -GGPOErrorCode Peer2PeerBackend::CurrentFrame(int& current) + +GGPOErrorCode +Peer2PeerBackend::CurrentFrame(int& current) { current = _sync.GetFrameCount(); 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) { diff --git a/dep/ggpo-x/src/backends/p2p.h b/dep/ggpo-x/src/backends/p2p.h index 62cfea0f9..100df7dc6 100644 --- a/dep/ggpo-x/src/backends/p2p.h +++ b/dep/ggpo-x/src/backends/p2p.h @@ -34,7 +34,10 @@ public: virtual GGPOErrorCode SetDisconnectNotifyStart(int timeout) override; virtual GGPOErrorCode Chat(const char* text) override; virtual GGPOErrorCode CurrentFrame(int& current) override; -public: + virtual GGPOErrorCode PollNetwork() override; + virtual GGPOErrorCode SetManualNetworkPolling(bool value) override; + + public: virtual void OnMsg(sockaddr_in &from, UdpMsg *msg, int len); protected: @@ -72,6 +75,8 @@ protected: int _disconnect_timeout; int _disconnect_notify_start; + bool _manual_network_polling; + UdpMsg::connect_status _local_connect_status[UDP_MSG_MAX_PLAYERS]; struct ChecksumEntry { int nFrame; diff --git a/dep/ggpo-x/src/backends/spectator.cpp b/dep/ggpo-x/src/backends/spectator.cpp index 5d1011819..71ca42519 100644 --- a/dep/ggpo-x/src/backends/spectator.cpp +++ b/dep/ggpo-x/src/backends/spectator.cpp @@ -20,6 +20,7 @@ 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; @@ -46,10 +47,11 @@ SpectatorBackend::~SpectatorBackend() { } -GGPOErrorCode +GGPOErrorCode SpectatorBackend::DoPoll() { - _poll.Pump(0); + if (!_manual_network_polling) + _poll.Pump(0); PollUdpProtocolEvents(); return GGPO_OK; @@ -86,11 +88,27 @@ SpectatorBackend::SyncInput(void *values, return GGPO_OK; } -GGPOErrorCode SpectatorBackend::CurrentFrame(int& current) +GGPOErrorCode +SpectatorBackend::CurrentFrame(int& current) { current= _next_input_to_send; 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) { diff --git a/dep/ggpo-x/src/backends/spectator.h b/dep/ggpo-x/src/backends/spectator.h index 4d4645140..255fb4231 100644 --- a/dep/ggpo-x/src/backends/spectator.h +++ b/dep/ggpo-x/src/backends/spectator.h @@ -36,7 +36,8 @@ public: 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); @@ -57,6 +58,7 @@ protected: 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.h b/dep/ggpo-x/src/backends/synctest.h index 924dbfb27..8339dadf4 100644 --- a/dep/ggpo-x/src/backends/synctest.h +++ b/dep/ggpo-x/src/backends/synctest.h @@ -27,7 +27,10 @@ public: 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; -protected: + virtual GGPOErrorCode PollNetwork() override { return GGPO_OK; }; + virtual GGPOErrorCode SetManualNetworkPolling(bool value) override { return GGPO_OK; }; + + protected: struct SavedInfo { int frame; int checksum; diff --git a/dep/ggpo-x/src/main.cpp b/dep/ggpo-x/src/main.cpp index 36d0b264b..0ed0b9ad2 100644 --- a/dep/ggpo-x/src/main.cpp +++ b/dep/ggpo-x/src/main.cpp @@ -191,6 +191,16 @@ 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) { @@ -200,6 +210,16 @@ 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, diff --git a/src/core/netplay.cpp b/src/core/netplay.cpp index f2f68c081..2026c8190 100644 --- a/src/core/netplay.cpp +++ b/src/core/netplay.cpp @@ -7,10 +7,10 @@ #include "common/timer.h" #include "digital_controller.h" #include "ggponet.h" +#include "host_settings.h" #include "pad.h" #include "spu.h" #include "system.h" -#include "host_settings.h" #include #include Log_SetChannel(Netplay); @@ -49,6 +49,8 @@ static void Close(); static void AdvanceFrame(u16 checksum = 0); static void RunFrame(); +static s32 CurrentFrame(); + static void NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags); /// Frame Pacing @@ -63,7 +65,6 @@ static void Throttle(); static MemorySettingsInterface s_settings_overlay; static std::string s_game_path; -static u32 s_max_pred = 0; static GGPOPlayerHandle s_local_handle = GGPO_INVALID_HANDLE; static GGPONetworkStats s_last_net_stats{}; @@ -77,18 +78,17 @@ static std::array, NUM_CONTROLLER_AND_CARD_PORTS> s_net_in static float s_target_speed = 1.0f; static Common::Timer::Value s_frame_period = 0; static Common::Timer::Value s_next_frame_time = 0; +static s32 s_next_timesync_recovery_frame = -1; } // namespace Netplay // Netplay Impl - s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred) { SetSettings(); InitializeFramePacing(); - s_max_pred = pred; /* 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. @@ -104,8 +104,9 @@ 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, s_max_pred); - //result = ggpo_start_synctest(&s_ggpo, &cb, (char*)"asdf", 2, sizeof(Netplay::Input), 1); + 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); ggpo_set_disconnect_timeout(s_ggpo, 3000); ggpo_set_disconnect_notify_start(s_ggpo, 1000); @@ -133,6 +134,7 @@ s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ld } } ggpo_set_frame_delay(s_ggpo, s_local_handle, ldelay); + ggpo_set_manual_network_polling(s_ggpo, true); return result; } @@ -143,7 +145,6 @@ void Netplay::Close() s_ggpo = nullptr; s_save_buffer_pool.clear(); s_local_handle = GGPO_INVALID_HANDLE; - s_max_pred = 0; // Restore original settings. Host::Internal::SetNetplaySettingsLayer(nullptr); @@ -198,16 +199,38 @@ void Netplay::UpdateThrottlePeriod() void Netplay::HandleTimeSyncEvent(float frame_delta, int update_interval) { - // Distribute the frame difference over the next N frames. - s_target_speed = 1.0f + -(frame_delta / static_cast(update_interval)); + // threshold to what is is with correcting for. + if (frame_delta <= 1.0f) + return; + + float total_time = frame_delta * s_frame_period; + // Distribute the frame difference over the next N * 0.8 frames. + // only part of the interval time is used since we want to come back to normal speed. + // otherwise we will keep spiraling into unplayable gameplay. + float added_time_per_frame = -(total_time / (static_cast(update_interval) * 0.8f)); + float iterations_per_frame = 1.0f / s_frame_period; + + s_target_speed = (s_frame_period + added_time_per_frame) * iterations_per_frame; + s_next_timesync_recovery_frame = CurrentFrame() + std::ceil(static_cast(update_interval) * 0.8f); + UpdateThrottlePeriod(); - Log_DevPrintf("TimeSync: %f frames %s, target speed %.4f%%", std::abs(frame_delta), - (frame_delta >= 0.0f ? "ahead" : "behind"), s_target_speed * 100.0f); + Log_VerbosePrintf("TimeSync: %f frames %s, target speed %.4f%%", std::abs(frame_delta), + (frame_delta >= 0.0f ? "ahead" : "behind"), s_target_speed * 100.0f); } void Netplay::Throttle() { + // if the s_next_timesync_recovery_frame has been reached revert back to the normal throttle speed + s32 current_frame = CurrentFrame(); + if (s_target_speed != 1.0f && current_frame >= s_next_timesync_recovery_frame) + { + s_target_speed = 1.0f; + UpdateThrottlePeriod(); + + Log_VerbosePrintf("TimeSync Recovery: frame %d, target speed %.4f%%", current_frame, s_target_speed * 100.0f); + } + s_next_frame_time += s_frame_period; // If we're running too slow, advance the next frame time based on the time we lost. Effectively skips @@ -220,15 +243,13 @@ void Netplay::Throttle() s_next_frame_time += (diff / s_frame_period) * s_frame_period; return; } - // Poll at 2ms throughout the sleep. // This way the network traffic comes through as soon as possible. const Common::Timer::Value sleep_period = Common::Timer::ConvertMillisecondsToValue(1); for (;;) { // Poll network. - // TODO: Ideally we would sleep on the poll()/select() here instead. - ggpo_idle(s_ggpo); + ggpo_poll_network(s_ggpo); current_time = Common::Timer::GetCurrentValue(); if (current_time >= s_next_frame_time) @@ -250,8 +271,9 @@ void Netplay::AdvanceFrame(u16 checksum) void Netplay::RunFrame() { // run game + bool need_idle = true; auto result = GGPO_OK; - int disconnectFlags = 0; + int disconnect_flags = 0; Netplay::Input inputs[2] = {}; // add local input if (s_local_handle != GGPO_INVALID_HANDLE) @@ -262,14 +284,26 @@ void Netplay::RunFrame() // advance game if (GGPO_SUCCEEDED(result)) { - result = SyncInput(inputs, &disconnectFlags); + result = SyncInput(inputs, &disconnect_flags); if (GGPO_SUCCEEDED(result)) { // enable again when rolling back done SPU::SetAudioOutputMuted(false); - NetplayAdvanceFrame(inputs, disconnectFlags); + NetplayAdvanceFrame(inputs, disconnect_flags); + // coming here means that the system doesnt need to idle anymore + need_idle = false; } } + // allow ggpo to do housekeeping if needed + if (need_idle) + ggpo_idle(s_ggpo); +} + +s32 Netplay::CurrentFrame() +{ + s32 current = -1; + ggpo_get_current_frame(s_ggpo, current); + return current; } void Netplay::CollectInput(u32 slot, u32 bind, float value) @@ -313,7 +347,7 @@ s32 Netplay::GetPing() u32 Netplay::GetMaxPrediction() { - return s_max_pred; + return MAX_ROLLBACK_FRAMES; } void Netplay::SetInputs(Netplay::Input inputs[2]) @@ -321,9 +355,9 @@ void Netplay::SetInputs(Netplay::Input inputs[2]) for (u32 i = 0; i < 2; i++) { auto cont = Pad::GetController(i); - std::bitset buttonBits(inputs[i].button_data); + std::bitset button_bits(inputs[i].button_data); for (u32 j = 0; j < (u32)DigitalController::Button::Count; j++) - cont->SetBindState(j, buttonBits.test(j) ? 1.0f : 0.0f); + cont->SetBindState(j, button_bits.test(j) ? 1.0f : 0.0f); } } @@ -336,7 +370,7 @@ void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string& // 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, 8); + int result = Netplay::Start(local_handle, local_port, remote_addr, remote_port, input_delay, MAX_ROLLBACK_FRAMES); if (result != GGPO_OK) { Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result); @@ -401,9 +435,9 @@ bool Netplay::NpBeginGameCb(void* ctx, const char* game_name) bool Netplay::NpAdvFrameCb(void* ctx, int flags) { Netplay::Input inputs[2] = {}; - int disconnectFlags; - Netplay::SyncInput(inputs, &disconnectFlags); - NetplayAdvanceFrame(inputs, disconnectFlags); + int disconnect_flags; + Netplay::SyncInput(inputs, &disconnect_flags); + NetplayAdvanceFrame(inputs, disconnect_flags); return true; } diff --git a/src/core/netplay.h b/src/core/netplay.h index 6dd246754..d73efbaf3 100644 --- a/src/core/netplay.h +++ b/src/core/netplay.h @@ -8,7 +8,9 @@ namespace Netplay { enum : u32 { // Maximum number of emulated controllers. - MAX_PLAYERS = 2, + MAX_PLAYERS = 2, + // Maximum netplay prediction frames + MAX_ROLLBACK_FRAMES = 8, }; void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay,