Netplay: Convert to namespace

This commit is contained in:
Stenzek 2023-04-11 20:46:50 +10:00
parent 714a2c24a6
commit c7c6d8814d
6 changed files with 130 additions and 154 deletions

View File

@ -8,18 +8,21 @@
#pragma comment(lib, "ws2_32.lib")
#endif
Netplay::LoopTimer s_timer;
std::string s_game_path;
u32 s_max_pred = 0;
GGPOPlayerHandle s_local_handle = GGPO_INVALID_HANDLE;
GGPONetworkStats s_last_net_stats{};
GGPOSession* s_ggpo = nullptr;
std::array<std::array<float, 32>, NUM_CONTROLLER_AND_CARD_PORTS> s_net_input;
// Netplay Impl
Netplay::Session::Session() = default;
Netplay::Session::~Session()
s32 Netplay::Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred)
{
Close();
}
int32_t Netplay::Session::Start(int32_t lhandle, uint16_t lport, std::string& raddr, uint16_t rport, int32_t ldelay,
uint32_t pred)
{
s_net_session.m_max_pred = pred;
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.
@ -35,11 +38,10 @@ int32_t Netplay::Session::Start(int32_t lhandle, uint16_t lport, std::string& ra
GGPOErrorCode result;
result = ggpo_start_session(&s_net_session.p_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport,
s_net_session.m_max_pred);
result = ggpo_start_session(&s_ggpo, &cb, "Duckstation-Netplay", 2, sizeof(Netplay::Input), lport, s_max_pred);
ggpo_set_disconnect_timeout(s_net_session.p_ggpo, 3000);
ggpo_set_disconnect_notify_start(s_net_session.p_ggpo, 1000);
ggpo_set_disconnect_timeout(s_ggpo, 3000);
ggpo_set_disconnect_notify_start(s_ggpo, 1000);
for (int i = 1; i <= 2; i++)
{
@ -52,8 +54,8 @@ int32_t Netplay::Session::Start(int32_t lhandle, uint16_t lport, std::string& ra
if (lhandle == i)
{
player.type = GGPOPlayerType::GGPO_PLAYERTYPE_LOCAL;
result = ggpo_add_player(s_net_session.p_ggpo, &player, &handle);
s_net_session.m_local_handle = handle;
result = ggpo_add_player(s_ggpo, &player, &handle);
s_local_handle = handle;
}
else
{
@ -64,38 +66,38 @@ int32_t Netplay::Session::Start(int32_t lhandle, uint16_t lport, std::string& ra
strcpy(player.u.remote.ip_address, raddr.c_str());
#endif
player.u.remote.port = rport;
result = ggpo_add_player(s_net_session.p_ggpo, &player, &handle);
result = ggpo_add_player(s_ggpo, &player, &handle);
}
}
ggpo_set_frame_delay(s_net_session.p_ggpo, s_net_session.m_local_handle, ldelay);
ggpo_set_frame_delay(s_ggpo, s_local_handle, ldelay);
return result;
}
void Netplay::Session::Close()
void Netplay::Close()
{
ggpo_close_session(s_net_session.p_ggpo);
s_net_session.p_ggpo = nullptr;
s_net_session.m_local_handle = GGPO_INVALID_HANDLE;
s_net_session.m_max_pred = 0;
ggpo_close_session(s_ggpo);
s_ggpo = nullptr;
s_local_handle = GGPO_INVALID_HANDLE;
s_max_pred = 0;
}
bool Netplay::Session::IsActive()
bool Netplay::IsActive()
{
return s_net_session.p_ggpo != nullptr;
return s_ggpo != nullptr;
}
void Netplay::Session::RunIdle()
void Netplay::RunIdle()
{
ggpo_idle(s_net_session.p_ggpo);
ggpo_idle(s_ggpo);
}
void Netplay::Session::AdvanceFrame(uint16_t checksum)
void Netplay::AdvanceFrame(u16 checksum)
{
ggpo_advance_frame(s_net_session.p_ggpo, checksum);
ggpo_advance_frame(s_ggpo, checksum);
}
void Netplay::Session::RunFrame(int32_t& waitTime)
void Netplay::RunFrame(s32& waitTime)
{
// run game
auto result = GGPO_OK;
@ -115,7 +117,7 @@ void Netplay::Session::RunFrame(int32_t& waitTime)
{
// enable again when rolling back done
SPU::SetAudioOutputMuted(false);
System::NetplayAdvanceFrame (inputs, disconnectFlags);
System::NetplayAdvanceFrame(inputs, disconnectFlags);
}
else
RunIdle();
@ -126,79 +128,79 @@ void Netplay::Session::RunFrame(int32_t& waitTime)
waitTime = GetTimer()->UsToWaitThisLoop();
}
int32_t Netplay::Session::CurrentFrame()
s32 Netplay::CurrentFrame()
{
int32_t frame;
ggpo_get_current_frame(s_net_session.p_ggpo, frame);
s32 frame;
ggpo_get_current_frame(s_ggpo, frame);
return frame;
}
void Netplay::Session::CollectInput(uint32_t slot, uint32_t bind, float value)
void Netplay::CollectInput(u32 slot, u32 bind, float value)
{
s_net_session.m_net_input[slot][bind] = value;
s_net_input[slot][bind] = value;
}
Netplay::Input Netplay::Session::ReadLocalInput()
Netplay::Input Netplay::ReadLocalInput()
{
// get controller data of the first controller (0 internally)
Netplay::Input inp{0};
for (uint32_t i = 0; i < (uint32_t)DigitalController::Button::Count; i++)
for (u32 i = 0; i < (u32)DigitalController::Button::Count; i++)
{
if (s_net_session.m_net_input[0][i] >= 0.25f)
if (s_net_input[0][i] >= 0.25f)
inp.button_data |= 1 << i;
}
return inp;
}
std::string& Netplay::Session::GetGamePath()
std::string& Netplay::GetGamePath()
{
return s_net_session.m_game_path;
return s_game_path;
}
void Netplay::Session::SetGamePath(std::string& path)
void Netplay::SetGamePath(std::string& path)
{
s_net_session.m_game_path = path;
s_game_path = path;
}
void Netplay::Session::SendMsg(const char* msg)
void Netplay::SendMsg(const char* msg)
{
ggpo_client_chat(s_net_session.p_ggpo, msg);
ggpo_client_chat(s_ggpo, msg);
}
GGPOErrorCode Netplay::Session::SyncInput(Netplay::Input inputs[2], int* disconnect_flags)
GGPOErrorCode Netplay::SyncInput(Netplay::Input inputs[2], int* disconnect_flags)
{
return ggpo_synchronize_input(s_net_session.p_ggpo, inputs, sizeof(Netplay::Input) * 2, disconnect_flags);
return ggpo_synchronize_input(s_ggpo, inputs, sizeof(Netplay::Input) * 2, disconnect_flags);
}
GGPOErrorCode Netplay::Session::AddLocalInput(Netplay::Input input)
GGPOErrorCode Netplay::AddLocalInput(Netplay::Input input)
{
return ggpo_add_local_input(s_net_session.p_ggpo, s_net_session.m_local_handle, &input, sizeof(Netplay::Input));
return ggpo_add_local_input(s_ggpo, s_local_handle, &input, sizeof(Netplay::Input));
}
GGPONetworkStats& Netplay::Session::GetNetStats(int32_t handle)
GGPONetworkStats& Netplay::GetNetStats(s32 handle)
{
ggpo_get_network_stats(s_net_session.p_ggpo, handle, &s_net_session.m_last_net_stats);
return s_net_session.m_last_net_stats;
ggpo_get_network_stats(s_ggpo, handle, &s_last_net_stats);
return s_last_net_stats;
}
int32_t Netplay::Session::GetPing()
s32 Netplay::GetPing()
{
const int handle = GetLocalHandle() == 1 ? 2 : 1;
ggpo_get_network_stats(s_net_session.p_ggpo, handle, &s_net_session.m_last_net_stats);
return s_net_session.m_last_net_stats.network.ping;
ggpo_get_network_stats(s_ggpo, handle, &s_last_net_stats);
return s_last_net_stats.network.ping;
}
uint32_t Netplay::Session::GetMaxPrediction()
u32 Netplay::GetMaxPrediction()
{
return s_net_session.m_max_pred;
return s_max_pred;
}
GGPOPlayerHandle Netplay::Session::GetLocalHandle()
GGPOPlayerHandle Netplay::GetLocalHandle()
{
return s_net_session.m_local_handle;
return s_local_handle;
}
void Netplay::Session::SetInputs(Netplay::Input inputs[2])
void Netplay::SetInputs(Netplay::Input inputs[2])
{
for (u32 i = 0; i < 2; i++)
{
@ -209,15 +211,15 @@ void Netplay::Session::SetInputs(Netplay::Input inputs[2])
}
}
Netplay::LoopTimer* Netplay::Session::GetTimer()
Netplay::LoopTimer* Netplay::GetTimer()
{
return &s_net_session.m_timer;
return &s_timer;
}
uint16_t Netplay::Session::Fletcher16(uint8_t* data, int count)
u16 Netplay::Fletcher16(uint8_t* data, int count)
{
uint16_t sum1 = 0;
uint16_t sum2 = 0;
u16 sum1 = 0;
u16 sum2 = 0;
int index;
for (index = 0; index < count; ++index)
@ -229,7 +231,7 @@ uint16_t Netplay::Session::Fletcher16(uint8_t* data, int count)
return (sum2 << 8) | sum1;
}
void Netplay::LoopTimer::Init(uint32_t fps, uint32_t frames_to_spread_wait)
void Netplay::LoopTimer::Init(u32 fps, u32 frames_to_spread_wait)
{
m_us_per_game_loop = 1000000 / fps;
m_us_ahead = 0;
@ -255,9 +257,9 @@ void Netplay::LoopTimer::OnGGPOTimeSyncEvent(float frames_ahead)
}
}
int32_t Netplay::LoopTimer::UsToWaitThisLoop()
s32 Netplay::LoopTimer::UsToWaitThisLoop()
{
int32_t timetoWait = m_us_per_game_loop;
s32 timetoWait = m_us_per_game_loop;
if (m_wait_count)
{
timetoWait += m_us_extra_to_wait;

View File

@ -1,8 +1,5 @@
#pragma once
#ifndef _NETPLAY_H
#define _NETPLAY_H
#include <array>
#include <ggponet.h>
#include <stdint.h>
@ -16,8 +13,8 @@
// C GGPO Event Callbacks. Should be defined in system.cpp
extern "C" {
bool NpAdvFrameCb(void* ctx, int flags);
bool NpSaveFrameCb(void* ctx, uint8_t** buffer, int* len, int* checksum, int frame);
bool NpLoadFrameCb(void* ctx, uint8_t* buffer, int len, int rb_frames, int frame_to_load);
bool NpSaveFrameCb(void* ctx, unsigned char** buffer, int* len, int* checksum, int frame);
bool NpLoadFrameCb(void* ctx, unsigned char* buffer, int len, int rb_frames, int frame_to_load);
bool NpBeginGameCb(void* ctx, const char* game_name);
void NpFreeBuffCb(void* ctx, void* buffer);
bool NpOnEventCb(void* ctx, GGPOEvent* ev);
@ -27,76 +24,52 @@ namespace Netplay {
struct Input
{
uint32_t button_data;
u32 button_data;
};
struct LoopTimer
{
public:
void Init(uint32_t fps, uint32_t frames_to_spread_wait);
void Init(u32 fps, u32 frames_to_spread_wait);
void OnGGPOTimeSyncEvent(float frames_ahead);
// Call every loop, to get the amount of time the current iteration of gameloop should take
int32_t UsToWaitThisLoop();
s32 UsToWaitThisLoop();
private:
float m_last_advantage = 0.0f;
int32_t m_us_per_game_loop = 0;
int32_t m_us_ahead = 0;
int32_t m_us_extra_to_wait = 0;
int32_t m_frames_to_spread_wait = 0;
int32_t m_wait_count = 0;
s32 m_us_per_game_loop = 0;
s32 m_us_ahead = 0;
s32 m_us_extra_to_wait = 0;
s32 m_frames_to_spread_wait = 0;
s32 m_wait_count = 0;
};
class Session
{
public:
Session();
~Session();
// l = local, r = remote
static int32_t Start(int32_t lhandle, uint16_t lport, std::string& raddr, uint16_t rport, int32_t ldelay,
uint32_t pred);
// l = local, r = remote
s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred);
static void Close();
static bool IsActive();
static void RunIdle();
static void AdvanceFrame(uint16_t checksum = 0);
static void RunFrame(int32_t& waitTime);
static int32_t CurrentFrame();
void Close();
bool IsActive();
void RunIdle();
static void CollectInput(uint32_t slot, uint32_t bind, float value);
static Netplay::Input ReadLocalInput();
void AdvanceFrame(u16 checksum = 0);
void RunFrame(s32& waitTime);
s32 CurrentFrame();
static std::string& GetGamePath();
static void SetGamePath(std::string& path);
static void SendMsg(const char* msg);
void CollectInput(u32 slot, u32 bind, float value);
Netplay::Input ReadLocalInput();
static GGPOErrorCode SyncInput(Netplay::Input inputs[2], int* disconnect_flags);
static GGPOErrorCode AddLocalInput(Netplay::Input input);
static GGPONetworkStats& GetNetStats(int32_t handle);
static int32_t GetPing();
static uint32_t GetMaxPrediction();
static GGPOPlayerHandle GetLocalHandle();
static void SetInputs(Netplay::Input inputs[2]);
std::string& GetGamePath();
void SetGamePath(std::string& path);
void SendMsg(const char* msg);
static Netplay::LoopTimer* GetTimer();
static uint16_t Fletcher16(uint8_t* data, int count);
private:
Netplay::LoopTimer m_timer;
std::string m_game_path;
uint32_t m_max_pred = 0;
GGPOPlayerHandle m_local_handle = GGPO_INVALID_HANDLE;
GGPONetworkStats m_last_net_stats{};
GGPOSession* p_ggpo = nullptr;
std::array<std::array<float, 32>, NUM_CONTROLLER_AND_CARD_PORTS> m_net_input;
};
GGPOErrorCode SyncInput(Netplay::Input inputs[2], int* disconnect_flags);
GGPOErrorCode AddLocalInput(Netplay::Input input);
GGPONetworkStats& GetNetStats(s32 handle);
s32 GetPing();
u32 GetMaxPrediction();
GGPOPlayerHandle GetLocalHandle();
void SetInputs(Netplay::Input inputs[2]);
Netplay::LoopTimer* GetTimer();
u16 Fletcher16(uint8_t* data, int count);
} // namespace Netplay
// Netplay Instance
static Netplay::Session s_net_session = Netplay::Session();
#endif // !_NETPLAY_H

View File

@ -169,12 +169,12 @@ void Pad::Reset()
bool Pad::ShouldAvoidSavingToState()
{
// Currently only runahead, will also be used for netplay.
return g_settings.IsRunaheadEnabled() || Netplay::Session::IsActive();
return g_settings.IsRunaheadEnabled() || Netplay::IsActive();
}
u32 Pad::GetMaximumRollbackFrames()
{
return (Netplay::Session::IsActive() ? Netplay::Session::GetMaxPrediction() : g_settings.runahead_frames);
return (Netplay::IsActive() ? Netplay::GetMaxPrediction() : g_settings.runahead_frames);
}
bool Pad::DoStateController(StateWrapper& sw, u32 i)

View File

@ -1541,7 +1541,7 @@ void System::Execute()
// this can shut us down
Host::PumpMessagesOnCPUThread();
if (!IsValid() || Netplay::Session::IsActive())
if (!IsValid() || Netplay::IsActive())
return;
if (s_frame_step_request)
@ -1574,17 +1574,17 @@ void System::ExecuteNetplay()
s32 timeToWait;
std::chrono::steady_clock::time_point start, next, now;
start = next = now = std::chrono::steady_clock::now();
while (Netplay::Session::IsActive() && System::IsRunning())
while (Netplay::IsActive() && System::IsRunning())
{
now = std::chrono::steady_clock::now();
if (now >= next)
{
Netplay::Session::RunFrame(timeToWait);
Netplay::RunFrame(timeToWait);
next = now + std::chrono::microseconds(timeToWait);
s_next_frame_time += timeToWait;
// this can shut us down
Host::PumpMessagesOnCPUThread();
if (!IsValid() || !Netplay::Session::IsActive())
if (!IsValid() || !Netplay::IsActive())
break;
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
@ -4471,15 +4471,15 @@ void System::StartNetplaySession(s32 local_handle, u16 local_port, std::string&
s32 input_delay, std::string& game_path)
{
// dont want to start a session when theres already one going on.
if (Netplay::Session::IsActive())
if (Netplay::IsActive())
return;
// set game path for later loading during the begin game callback
Netplay::Session::SetGamePath(game_path);
Netplay::SetGamePath(game_path);
// set netplay timer
const u32 fps = (s_region == ConsoleRegion::PAL ? 50 : 60);
Netplay::Session::GetTimer()->Init(fps, 180);
Netplay::GetTimer()->Init(fps, 180);
// create session
int result = Netplay::Session::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, 8);
if (result != GGPO_OK)
{
Log_ErrorPrintf("Failed to Create Netplay Session! Error: %d", result);
@ -4488,17 +4488,17 @@ void System::StartNetplaySession(s32 local_handle, u16 local_port, std::string&
void System::StopNetplaySession()
{
if (!Netplay::Session::IsActive())
if (!Netplay::IsActive())
return;
s_netplay_states.clear();
Netplay::Session::Close();
Netplay::Close();
}
void System::NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags)
{
Netplay::Session::SetInputs(inputs);
Netplay::SetInputs(inputs);
System::DoRunFrame();
Netplay::Session::AdvanceFrame();
Netplay::AdvanceFrame();
}
bool NpBeginGameCb(void* ctx, const char* game_name)
@ -4507,7 +4507,7 @@ bool NpBeginGameCb(void* ctx, const char* game_name)
if (System::IsValid())
System::ShutdownSystem(false);
// fast boot the selected game and wait for the other player
auto param = SystemBootParameters(Netplay::Session::GetGamePath());
auto param = SystemBootParameters(Netplay::GetGamePath());
param.override_fast_boot = true;
if (!System::BootSystem(param))
{
@ -4526,7 +4526,7 @@ bool NpAdvFrameCb(void* ctx, int flags)
{
Netplay::Input inputs[2] = {};
int disconnectFlags;
Netplay::Session::SyncInput(inputs, &disconnectFlags);
Netplay::SyncInput(inputs, &disconnectFlags);
System::NetplayAdvanceFrame(inputs, disconnectFlags);
return true;
}
@ -4542,7 +4542,7 @@ bool NpSaveFrameCb(void* ctx, uint8_t** buffer, int* len, int* checksum, int fra
return false;
memcpy(*buffer, &dummyData, *len);
// store state for later.
int pred = Netplay::Session::GetMaxPrediction();
int pred = Netplay::GetMaxPrediction();
if (frame < pred && s_netplay_states.size() < pred)
{
MemorySaveState save;
@ -4561,7 +4561,7 @@ bool NpLoadFrameCb(void* ctx, uint8_t* buffer, int len, int rb_frames, int frame
{
// Disable Audio For upcoming rollback
SPU::SetAudioOutputMuted(true);
return System::LoadMemoryState(s_netplay_states[frame_to_load % Netplay::Session::GetMaxPrediction()]);
return System::LoadMemoryState(s_netplay_states[frame_to_load % Netplay::GetMaxPrediction()]);
}
bool NpOnEventCb(void* ctx, GGPOEvent* ev)
@ -4603,7 +4603,7 @@ bool NpOnEventCb(void* ctx, GGPOEvent* ev)
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_TIMESYNC:
Netplay::Session::GetTimer()->OnGGPOTimeSyncEvent(ev->u.timesync.frames_ahead);
Netplay::GetTimer()->OnGGPOTimeSyncEvent(ev->u.timesync.frames_ahead);
break;
case GGPOEventCode::GGPO_EVENTCODE_DESYNC:
sprintf(buff, "Netplay Desync Detected!: Frame: %d, L:%u, R:%u", ev->u.desync.nFrameOfDesync,

View File

@ -1100,7 +1100,7 @@ void EmuThread::sendNetplayMessage(const QString& message)
QMetaObject::invokeMethod(this, "sendNetplayMessage", Qt::QueuedConnection, Q_ARG(const QString&, message));
return;
}
Netplay::Session::SendMsg(message.toStdString().c_str());
Netplay::SendMsg(message.toStdString().c_str());
}
void EmuThread::stopNetplaySession()
@ -1462,12 +1462,13 @@ void EmuThread::run()
// main loop
while (!m_shutdown_flag)
{
if (System::IsRunning())
if (Netplay::IsActive())
{
if (Netplay::Session::IsActive())
System::ExecuteNetplay();
else
System::Execute();
System::ExecuteNetplay();
}
else if (System::IsRunning())
{
System::Execute();
}
else
{
@ -1711,7 +1712,7 @@ void EmuThread::updatePerformanceCounters()
m_last_video_fps = vfps;
}
const s32 ping = Netplay::Session::GetPing();
const s32 ping = Netplay::GetPing();
if (m_last_ping != ping)
{
QMetaObject::invokeMethod(g_main_window->getStatusPingWidget(), "setText", Qt::QueuedConnection,

View File

@ -714,9 +714,9 @@ void InputManager::AddPadBindings(SettingsInterface& si, const std::string& sect
if (!System::IsValid())
return;
if (Netplay::Session::IsActive())
if (Netplay::IsActive())
{
Netplay::Session::CollectInput(pad_index, bind_index, value);
Netplay::CollectInput(pad_index, bind_index, value);
return;
}