Netplay: Isolate system logic

This commit is contained in:
Stenzek 2023-04-11 21:16:31 +10:00
parent c7c6d8814d
commit bc1fbfe103
5 changed files with 303 additions and 294 deletions

View File

@ -1,22 +1,89 @@
#include "netplay.h"
#include "common/byte_stream.h"
#include "common/gpu_texture.h"
#include "common/log.h"
#include "digital_controller.h"
#include "ggponet.h"
#include "pad.h"
#include "spu.h"
#include "system.h"
#include <bitset>
#include <deque>
Log_SetChannel(Netplay);
#ifdef _WIN32
#pragma comment(lib, "ws2_32.lib")
#endif
Netplay::LoopTimer s_timer;
std::string s_game_path;
u32 s_max_pred = 0;
namespace Netplay {
GGPOPlayerHandle s_local_handle = GGPO_INVALID_HANDLE;
GGPONetworkStats s_last_net_stats{};
GGPOSession* s_ggpo = nullptr;
struct Input
{
u32 button_data;
};
std::array<std::array<float, 32>, NUM_CONTROLLER_AND_CARD_PORTS> s_net_input;
struct LoopTimer
{
public:
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
s32 UsToWaitThisLoop();
private:
float m_last_advantage = 0.0f;
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;
};
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);
static bool NpBeginGameCb(void* ctx, const char* game_name);
static void NpFreeBuffCb(void* ctx, void* buffer);
static bool NpOnEventCb(void* ctx, GGPOEvent* ev);
static Input ReadLocalInput();
static GGPOErrorCode AddLocalInput(Netplay::Input input);
static GGPOErrorCode SyncInput(Input inputs[2], int* disconnect_flags);
static void SetInputs(Input inputs[2]);
static LoopTimer* GetTimer();
// l = local, r = remote
static s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred);
static void Close();
static void RunIdle();
static void AdvanceFrame(u16 checksum = 0);
static void RunFrame(s32& waitTime);
static s32 CurrentFrame();
static std::string& GetGamePath();
static void SetGamePath(std::string& path);
static GGPONetworkStats& GetNetStats(s32 handle);
static GGPOPlayerHandle GetLocalHandle();
static u16 Fletcher16(uint8_t* data, int count);
static void NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags);
static LoopTimer s_timer;
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{};
static GGPOSession* s_ggpo = nullptr;
static std::deque<System::MemorySaveState> s_netplay_states;
static std::array<std::array<float, 32>, NUM_CONTROLLER_AND_CARD_PORTS> s_net_input;
} // namespace Netplay
// Netplay Impl
@ -117,7 +184,7 @@ void Netplay::RunFrame(s32& waitTime)
{
// enable again when rolling back done
SPU::SetAudioOutputMuted(false);
System::NetplayAdvanceFrame(inputs, disconnectFlags);
NetplayAdvanceFrame(inputs, disconnectFlags);
}
else
RunIdle();
@ -269,3 +336,189 @@ s32 Netplay::LoopTimer::UsToWaitThisLoop()
}
return timetoWait;
}
void Netplay::StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port,
s32 input_delay, std::string& game_path)
{
// 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
SetGamePath(game_path);
// set netplay timer
const u32 fps = (System::GetRegion() == ConsoleRegion::PAL ? 50 : 60);
GetTimer()->Init(fps, 180);
// create session
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);
}
}
void Netplay::StopNetplaySession()
{
if (!IsActive())
return;
s_netplay_states.clear();
Close();
}
void Netplay::NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags)
{
Netplay::SetInputs(inputs);
System::RunFrame();
Netplay::AdvanceFrame();
}
void Netplay::ExecuteNetplay()
{
// frame timing
s32 timeToWait;
std::chrono::steady_clock::time_point start, next, now;
start = next = now = std::chrono::steady_clock::now();
while (Netplay::IsActive() && System::IsRunning())
{
now = std::chrono::steady_clock::now();
if (now >= next)
{
Netplay::RunFrame(timeToWait);
next = now + std::chrono::microseconds(timeToWait);
// s_next_frame_time += timeToWait;
// this can shut us down
Host::PumpMessagesOnCPUThread();
if (!System::IsValid() || !Netplay::IsActive())
break;
System::PresentFrame();
System::UpdatePerformanceCounters();
}
}
}
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(Netplay::GetGamePath());
param.override_fast_boot = true;
if (!System::BootSystem(param))
{
StopNetplaySession();
return false;
}
// Fast Forward to Game Start
SPU::SetAudioOutputMuted(true);
while (System::GetInternalFrameNumber() < 2)
System::RunFrame();
SPU::SetAudioOutputMuted(false);
return true;
}
bool Netplay::NpAdvFrameCb(void* ctx, int flags)
{
Netplay::Input inputs[2] = {};
int disconnectFlags;
Netplay::SyncInput(inputs, &disconnectFlags);
NetplayAdvanceFrame(inputs, disconnectFlags);
return true;
}
bool Netplay::NpSaveFrameCb(void* ctx, uint8_t** buffer, int* len, int* checksum, int frame)
{
bool result = false;
// give ggpo something so it doesnt complain.
u8 dummyData = 43;
*len = sizeof(u8);
*buffer = (unsigned char*)malloc(*len);
if (!*buffer)
return false;
memcpy(*buffer, &dummyData, *len);
// store state for later.
int pred = Netplay::GetMaxPrediction();
if (frame < pred && s_netplay_states.size() < pred)
{
System::MemorySaveState save;
result = System::SaveMemoryState(&save);
s_netplay_states.push_back(std::move(save));
}
else
{
// reuse streams
result = System::SaveMemoryState(&s_netplay_states[frame % pred]);
}
return result;
}
bool Netplay::NpLoadFrameCb(void* ctx, uint8_t* buffer, int len, int rb_frames, int frame_to_load)
{
// Disable Audio For upcoming rollback
SPU::SetAudioOutputMuted(true);
return System::LoadMemoryState(s_netplay_states[frame_to_load % Netplay::GetMaxPrediction()]);
}
bool Netplay::NpOnEventCb(void* ctx, GGPOEvent* ev)
{
char buff[128];
std::string msg;
switch (ev->code)
{
case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER:
sprintf(buff, "Netplay Connected To Player: %d", ev->u.connected.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER:
sprintf(buff, "Netplay Synchronzing: %d/%d", ev->u.synchronizing.count, ev->u.synchronizing.total);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER:
sprintf(buff, "Netplay Synchronized With Player: %d", ev->u.synchronized.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_DISCONNECTED_FROM_PEER:
sprintf(buff, "Netplay Player: %d Disconnected", ev->u.disconnected.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_RUNNING:
msg = "Netplay Is Running";
break;
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_INTERRUPTED:
sprintf(buff, "Netplay Player: %d Connection Interupted, Timeout: %d", ev->u.connection_interrupted.player,
ev->u.connection_interrupted.disconnect_timeout);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_RESUMED:
sprintf(buff, "Netplay Player: %d Connection Resumed", ev->u.connection_resumed.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_CHAT:
sprintf(buff, "%s", ev->u.chat.msg);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_TIMESYNC:
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,
ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum);
msg = buff;
break;
default:
sprintf(buff, "Netplay Event Code: %d", ev->code);
msg = buff;
}
if (!msg.empty())
{
Host::OnNetplayMessage(msg);
Log_InfoPrintf("%s", msg.c_str());
}
return true;
}
void Netplay::NpFreeBuffCb(void* ctx, void* buffer)
{
free(buffer);
}

View File

@ -1,75 +1,24 @@
#pragma once
#include <array>
#include <ggponet.h>
#include <stdint.h>
#include <string.h>
#include <string>
#include "common/timer.h"
#include "digital_controller.h"
#include "types.h"
// C GGPO Event Callbacks. Should be defined in system.cpp
extern "C" {
bool NpAdvFrameCb(void* ctx, int flags);
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);
}
#include <string>
namespace Netplay {
struct Input
{
u32 button_data;
};
void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay,
std::string& game_path);
void StopNetplaySession();
struct LoopTimer
{
public:
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
s32 UsToWaitThisLoop();
private:
float m_last_advantage = 0.0f;
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;
};
// l = local, r = remote
s32 Start(s32 lhandle, u16 lport, std::string& raddr, u16 rport, s32 ldelay, u32 pred);
void Close();
bool IsActive();
void RunIdle();
void AdvanceFrame(u16 checksum = 0);
void RunFrame(s32& waitTime);
s32 CurrentFrame();
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
void ExecuteNetplay();
void CollectInput(u32 slot, u32 bind, float value);
Netplay::Input ReadLocalInput();
std::string& GetGamePath();
void SetGamePath(std::string& path);
void SendMsg(const char* msg);
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

View File

@ -75,18 +75,10 @@ SystemBootParameters::SystemBootParameters(std::string filename_) : filename(std
SystemBootParameters::~SystemBootParameters() = default;
struct MemorySaveState
{
std::unique_ptr<GPUTexture> vram_texture;
std::unique_ptr<GrowableMemoryByteStream> state_stream;
};
namespace System {
static std::optional<ExtendedSaveStateInfo> InternalGetExtendedSaveStateInfo(ByteStream* stream);
static bool InternalSaveState(ByteStream* state, u32 screenshot_size = 256,
u32 compression_method = SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE);
static bool SaveMemoryState(MemorySaveState* mss);
static bool LoadMemoryState(const MemorySaveState& mss);
static bool LoadEXE(const char* filename);
@ -104,7 +96,8 @@ static void DestroySystem();
static std::string GetMediaPathFromSaveState(const char* path);
static bool DoLoadState(ByteStream* stream, bool force_software_renderer, bool update_display);
static bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display, bool is_memory_state);
static void DoRunFrame();
static void WrappedRunFrame();
static void RunFramesToNow();
static bool CreateGPU(GPURenderer renderer);
static bool SaveUndoLoadState();
@ -197,19 +190,17 @@ static std::unique_ptr<ByteStream> m_undo_load_state;
static bool s_memory_saves_enabled = false;
static std::deque<MemorySaveState> s_rewind_states;
static std::deque<System::MemorySaveState> s_rewind_states;
static s32 s_rewind_load_frequency = -1;
static s32 s_rewind_load_counter = -1;
static s32 s_rewind_save_frequency = -1;
static s32 s_rewind_save_counter = -1;
static bool s_rewinding_first_save = false;
static std::deque<MemorySaveState> s_runahead_states;
static std::deque<System::MemorySaveState> s_runahead_states;
static bool s_runahead_replay_pending = false;
static u32 s_runahead_frames = 0;
static std::deque<MemorySaveState> s_netplay_states;
static TinyString GetTimestampStringForFileName()
{
return TinyString::FromFmt("{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(std::time(nullptr)));
@ -1532,12 +1523,12 @@ void System::ClearRunningGame()
void System::Execute()
{
while (System::IsRunning())
while (IsRunning())
{
if (s_display_all_frames)
System::RunFrame();
WrappedRunFrame();
else
System::RunFrames();
RunFramesToNow();
// this can shut us down
Host::PumpMessagesOnCPUThread();
@ -1550,13 +1541,7 @@ void System::Execute()
PauseSystem(true);
}
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
Host::RenderDisplay(skip_present);
if (!skip_present && g_host_display->IsGPUTimingEnabled())
{
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
s_presents_since_last_update++;
}
PresentFrame();
if (s_throttler_enabled)
System::Throttle();
@ -1568,35 +1553,14 @@ void System::Execute()
}
}
void System::ExecuteNetplay()
void System::PresentFrame()
{
// frame timing
s32 timeToWait;
std::chrono::steady_clock::time_point start, next, now;
start = next = now = std::chrono::steady_clock::now();
while (Netplay::IsActive() && System::IsRunning())
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
Host::RenderDisplay(skip_present);
if (!skip_present && g_host_display->IsGPUTimingEnabled())
{
now = std::chrono::steady_clock::now();
if (now >= next)
{
Netplay::RunFrame(timeToWait);
next = now + std::chrono::microseconds(timeToWait);
s_next_frame_time += timeToWait;
// this can shut us down
Host::PumpMessagesOnCPUThread();
if (!IsValid() || !Netplay::IsActive())
break;
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
Host::RenderDisplay(skip_present);
if (!skip_present && g_host_display->IsGPUTimingEnabled())
{
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
s_presents_since_last_update++;
}
System::UpdatePerformanceCounters();
}
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
s_presents_since_last_update++;
}
}
@ -2191,7 +2155,7 @@ void System::SingleStepCPU()
g_gpu->ResetGraphicsAPIState();
}
void System::DoRunFrame()
void System::RunFrame()
{
g_gpu->RestoreGraphicsAPIState();
@ -2231,7 +2195,7 @@ void System::DoRunFrame()
g_gpu->ResetGraphicsAPIState();
}
void System::RunFrame()
void System::WrappedRunFrame()
{
if (s_rewind_load_counter >= 0)
{
@ -2242,7 +2206,7 @@ void System::RunFrame()
if (s_runahead_frames > 0)
DoRunahead();
DoRunFrame();
RunFrame();
s_next_frame_time += s_frame_period;
@ -2304,7 +2268,7 @@ void System::Throttle()
#endif
}
void System::RunFrames()
void System::RunFramesToNow()
{
// If we're running more than this in a single loop... we're in for a bad time.
const u32 max_frames_to_run = 2;
@ -2316,7 +2280,7 @@ void System::RunFrames()
if (value < s_next_frame_time)
break;
RunFrame();
WrappedRunFrame();
frames_run++;
value = Common::Timer::GetCurrentValue();
@ -3716,7 +3680,7 @@ void System::DoRunahead()
while (frames_to_run > 0)
{
DoRunFrame();
RunFrame();
SaveRunaheadState();
frames_to_run--;
}
@ -4466,163 +4430,3 @@ void System::SetTimerResolutionIncreased(bool enabled)
timeEndPeriod(1);
#endif
}
void System::StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port,
s32 input_delay, std::string& game_path)
{
// dont want to start a session when theres already one going on.
if (Netplay::IsActive())
return;
// set game path for later loading during the begin game callback
Netplay::SetGamePath(game_path);
// set netplay timer
const u32 fps = (s_region == ConsoleRegion::PAL ? 50 : 60);
Netplay::GetTimer()->Init(fps, 180);
// create session
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);
}
}
void System::StopNetplaySession()
{
if (!Netplay::IsActive())
return;
s_netplay_states.clear();
Netplay::Close();
}
void System::NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags)
{
Netplay::SetInputs(inputs);
System::DoRunFrame();
Netplay::AdvanceFrame();
}
bool 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(Netplay::GetGamePath());
param.override_fast_boot = true;
if (!System::BootSystem(param))
{
System::StopNetplaySession();
return false;
}
// Fast Forward to Game Start
SPU::SetAudioOutputMuted(true);
while (s_internal_frame_number < 2)
System::DoRunFrame();
SPU::SetAudioOutputMuted(false);
return true;
}
bool NpAdvFrameCb(void* ctx, int flags)
{
Netplay::Input inputs[2] = {};
int disconnectFlags;
Netplay::SyncInput(inputs, &disconnectFlags);
System::NetplayAdvanceFrame(inputs, disconnectFlags);
return true;
}
bool NpSaveFrameCb(void* ctx, uint8_t** buffer, int* len, int* checksum, int frame)
{
bool result = false;
// give ggpo something so it doesnt complain.
u8 dummyData = 43;
*len = sizeof(u8);
*buffer = (unsigned char*)malloc(*len);
if (!*buffer)
return false;
memcpy(*buffer, &dummyData, *len);
// store state for later.
int pred = Netplay::GetMaxPrediction();
if (frame < pred && s_netplay_states.size() < pred)
{
MemorySaveState save;
result = System::SaveMemoryState(&save);
s_netplay_states.push_back(std::move(save));
}
else
{
// reuse streams
result = System::SaveMemoryState(&s_netplay_states[frame % pred]);
}
return result;
}
bool NpLoadFrameCb(void* ctx, uint8_t* buffer, int len, int rb_frames, int frame_to_load)
{
// Disable Audio For upcoming rollback
SPU::SetAudioOutputMuted(true);
return System::LoadMemoryState(s_netplay_states[frame_to_load % Netplay::GetMaxPrediction()]);
}
bool NpOnEventCb(void* ctx, GGPOEvent* ev)
{
char buff[128];
std::string msg;
switch (ev->code)
{
case GGPOEventCode::GGPO_EVENTCODE_CONNECTED_TO_PEER:
sprintf(buff, "Netplay Connected To Player: %d", ev->u.connected.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER:
sprintf(buff, "Netplay Synchronzing: %d/%d", ev->u.synchronizing.count, ev->u.synchronizing.total);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER:
sprintf(buff, "Netplay Synchronized With Player: %d", ev->u.synchronized.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_DISCONNECTED_FROM_PEER:
sprintf(buff, "Netplay Player: %d Disconnected", ev->u.disconnected.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_RUNNING:
msg = "Netplay Is Running";
break;
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_INTERRUPTED:
sprintf(buff, "Netplay Player: %d Connection Interupted, Timeout: %d", ev->u.connection_interrupted.player,
ev->u.connection_interrupted.disconnect_timeout);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_CONNECTION_RESUMED:
sprintf(buff, "Netplay Player: %d Connection Resumed", ev->u.connection_resumed.player);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_CHAT:
sprintf(buff, "%s", ev->u.chat.msg);
msg = buff;
break;
case GGPOEventCode::GGPO_EVENTCODE_TIMESYNC:
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,
ev->u.desync.ourCheckSum, ev->u.desync.remoteChecksum);
msg = buff;
break;
default:
sprintf(buff, "Netplay Event Code: %d", ev->code);
msg = buff;
}
if (!msg.empty())
{
Host::OnNetplayMessage(msg);
Log_InfoPrintf("%s", msg.c_str());
}
return true;
}
void NpFreeBuffCb(void* ctx, void* buffer)
{
free(buffer);
}

View File

@ -20,6 +20,9 @@ class Controller;
struct CheatCode;
class CheatList;
class GPUTexture;
class GrowableMemoryByteStream;
namespace BIOS {
struct ImageInfo;
struct Hash;
@ -224,12 +227,18 @@ bool LoadState(const char* filename);
bool SaveState(const char* filename, bool backup_existing_save);
bool SaveResumeState();
/// Memory save states - only for internal use.
struct MemorySaveState
{
std::unique_ptr<GPUTexture> vram_texture;
std::unique_ptr<GrowableMemoryByteStream> state_stream;
};
bool SaveMemoryState(MemorySaveState* mss);
bool LoadMemoryState(const MemorySaveState& mss);
/// Runs the VM until the CPU execution is canceled.
void Execute();
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
void ExecuteNetplay();
/// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed).
void RecreateSystem();
@ -238,7 +247,7 @@ bool RecreateGPU(GPURenderer renderer, bool force_recreate_display = false, bool
void SingleStepCPU();
void RunFrame();
void RunFrames();
void PresentFrame();
/// Sets target emulation speed.
float GetTargetSpeed();
@ -454,12 +463,6 @@ void ClearMemorySaveStates();
void UpdateMemorySaveStateSettings();
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
void SetRunaheadReplayFlag();
/// Netplay
void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay,
std::string& game_path);
void StopNetplaySession();
void NetplayAdvanceFrame(Netplay::Input inputs[], int disconnect_flags);
} // namespace System
namespace Host {

View File

@ -1090,7 +1090,7 @@ void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const
auto remAddr = remote_addr.trimmed().toStdString();
auto gamePath = game_path.trimmed().toStdString();
System::StartNetplaySession(local_handle, local_port, remAddr, remote_port, input_delay, gamePath);
Netplay::StartNetplaySession(local_handle, local_port, remAddr, remote_port, input_delay, gamePath);
}
void EmuThread::sendNetplayMessage(const QString& message)
@ -1110,7 +1110,7 @@ void EmuThread::stopNetplaySession()
QMetaObject::invokeMethod(this, "stopNetplaySession", Qt::QueuedConnection);
return;
}
System::StopNetplaySession();
Netplay::StopNetplaySession();
}
void EmuThread::runOnEmuThread(std::function<void()> callback)
@ -1464,7 +1464,7 @@ void EmuThread::run()
{
if (Netplay::IsActive())
{
System::ExecuteNetplay();
Netplay::ExecuteNetplay();
}
else if (System::IsRunning())
{