Netplay: Isolate system logic
This commit is contained in:
parent
c7c6d8814d
commit
bc1fbfe103
|
@ -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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue