Netplay: Isolate system logic
This commit is contained in:
parent
c7c6d8814d
commit
bc1fbfe103
|
@ -1,22 +1,89 @@
|
||||||
#include "netplay.h"
|
#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 "pad.h"
|
||||||
#include "spu.h"
|
#include "spu.h"
|
||||||
#include "system.h"
|
#include "system.h"
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
|
#include <deque>
|
||||||
|
Log_SetChannel(Netplay);
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#pragma comment(lib, "ws2_32.lib")
|
#pragma comment(lib, "ws2_32.lib")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
Netplay::LoopTimer s_timer;
|
namespace Netplay {
|
||||||
std::string s_game_path;
|
|
||||||
u32 s_max_pred = 0;
|
|
||||||
|
|
||||||
GGPOPlayerHandle s_local_handle = GGPO_INVALID_HANDLE;
|
struct Input
|
||||||
GGPONetworkStats s_last_net_stats{};
|
{
|
||||||
GGPOSession* s_ggpo = nullptr;
|
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
|
// Netplay Impl
|
||||||
|
|
||||||
|
@ -117,7 +184,7 @@ void Netplay::RunFrame(s32& waitTime)
|
||||||
{
|
{
|
||||||
// enable again when rolling back done
|
// enable again when rolling back done
|
||||||
SPU::SetAudioOutputMuted(false);
|
SPU::SetAudioOutputMuted(false);
|
||||||
System::NetplayAdvanceFrame(inputs, disconnectFlags);
|
NetplayAdvanceFrame(inputs, disconnectFlags);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
RunIdle();
|
RunIdle();
|
||||||
|
@ -269,3 +336,189 @@ s32 Netplay::LoopTimer::UsToWaitThisLoop()
|
||||||
}
|
}
|
||||||
return timetoWait;
|
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
|
#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"
|
#include "types.h"
|
||||||
|
#include <string>
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Netplay {
|
namespace Netplay {
|
||||||
|
|
||||||
struct Input
|
void StartNetplaySession(s32 local_handle, u16 local_port, std::string& remote_addr, u16 remote_port, s32 input_delay,
|
||||||
{
|
std::string& game_path);
|
||||||
u32 button_data;
|
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();
|
bool IsActive();
|
||||||
void RunIdle();
|
|
||||||
|
|
||||||
void AdvanceFrame(u16 checksum = 0);
|
/// Runs the VM and netplay loop. when the netplay loop cancels it switches to normal execute mode.
|
||||||
void RunFrame(s32& waitTime);
|
void ExecuteNetplay();
|
||||||
s32 CurrentFrame();
|
|
||||||
|
|
||||||
void CollectInput(u32 slot, u32 bind, float value);
|
void CollectInput(u32 slot, u32 bind, float value);
|
||||||
Netplay::Input ReadLocalInput();
|
|
||||||
|
|
||||||
std::string& GetGamePath();
|
|
||||||
void SetGamePath(std::string& path);
|
|
||||||
void SendMsg(const char* msg);
|
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();
|
s32 GetPing();
|
||||||
u32 GetMaxPrediction();
|
u32 GetMaxPrediction();
|
||||||
GGPOPlayerHandle GetLocalHandle();
|
|
||||||
void SetInputs(Netplay::Input inputs[2]);
|
|
||||||
|
|
||||||
Netplay::LoopTimer* GetTimer();
|
|
||||||
u16 Fletcher16(uint8_t* data, int count);
|
|
||||||
} // namespace Netplay
|
} // namespace Netplay
|
||||||
|
|
|
@ -75,18 +75,10 @@ SystemBootParameters::SystemBootParameters(std::string filename_) : filename(std
|
||||||
|
|
||||||
SystemBootParameters::~SystemBootParameters() = default;
|
SystemBootParameters::~SystemBootParameters() = default;
|
||||||
|
|
||||||
struct MemorySaveState
|
|
||||||
{
|
|
||||||
std::unique_ptr<GPUTexture> vram_texture;
|
|
||||||
std::unique_ptr<GrowableMemoryByteStream> state_stream;
|
|
||||||
};
|
|
||||||
|
|
||||||
namespace System {
|
namespace System {
|
||||||
static std::optional<ExtendedSaveStateInfo> InternalGetExtendedSaveStateInfo(ByteStream* stream);
|
static std::optional<ExtendedSaveStateInfo> InternalGetExtendedSaveStateInfo(ByteStream* stream);
|
||||||
static bool InternalSaveState(ByteStream* state, u32 screenshot_size = 256,
|
static bool InternalSaveState(ByteStream* state, u32 screenshot_size = 256,
|
||||||
u32 compression_method = SAVE_STATE_HEADER::COMPRESSION_TYPE_NONE);
|
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);
|
static bool LoadEXE(const char* filename);
|
||||||
|
|
||||||
|
@ -104,7 +96,8 @@ static void DestroySystem();
|
||||||
static std::string GetMediaPathFromSaveState(const char* path);
|
static std::string GetMediaPathFromSaveState(const char* path);
|
||||||
static bool DoLoadState(ByteStream* stream, bool force_software_renderer, bool update_display);
|
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 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 CreateGPU(GPURenderer renderer);
|
||||||
static bool SaveUndoLoadState();
|
static bool SaveUndoLoadState();
|
||||||
|
|
||||||
|
@ -197,19 +190,17 @@ static std::unique_ptr<ByteStream> m_undo_load_state;
|
||||||
|
|
||||||
static bool s_memory_saves_enabled = false;
|
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_frequency = -1;
|
||||||
static s32 s_rewind_load_counter = -1;
|
static s32 s_rewind_load_counter = -1;
|
||||||
static s32 s_rewind_save_frequency = -1;
|
static s32 s_rewind_save_frequency = -1;
|
||||||
static s32 s_rewind_save_counter = -1;
|
static s32 s_rewind_save_counter = -1;
|
||||||
static bool s_rewinding_first_save = false;
|
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 bool s_runahead_replay_pending = false;
|
||||||
static u32 s_runahead_frames = 0;
|
static u32 s_runahead_frames = 0;
|
||||||
|
|
||||||
static std::deque<MemorySaveState> s_netplay_states;
|
|
||||||
|
|
||||||
static TinyString GetTimestampStringForFileName()
|
static TinyString GetTimestampStringForFileName()
|
||||||
{
|
{
|
||||||
return TinyString::FromFmt("{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(std::time(nullptr)));
|
return TinyString::FromFmt("{:%Y-%m-%d_%H-%M-%S}", fmt::localtime(std::time(nullptr)));
|
||||||
|
@ -1532,12 +1523,12 @@ void System::ClearRunningGame()
|
||||||
|
|
||||||
void System::Execute()
|
void System::Execute()
|
||||||
{
|
{
|
||||||
while (System::IsRunning())
|
while (IsRunning())
|
||||||
{
|
{
|
||||||
if (s_display_all_frames)
|
if (s_display_all_frames)
|
||||||
System::RunFrame();
|
WrappedRunFrame();
|
||||||
else
|
else
|
||||||
System::RunFrames();
|
RunFramesToNow();
|
||||||
|
|
||||||
// this can shut us down
|
// this can shut us down
|
||||||
Host::PumpMessagesOnCPUThread();
|
Host::PumpMessagesOnCPUThread();
|
||||||
|
@ -1550,13 +1541,7 @@ void System::Execute()
|
||||||
PauseSystem(true);
|
PauseSystem(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
|
PresentFrame();
|
||||||
Host::RenderDisplay(skip_present);
|
|
||||||
if (!skip_present && g_host_display->IsGPUTimingEnabled())
|
|
||||||
{
|
|
||||||
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
|
|
||||||
s_presents_since_last_update++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (s_throttler_enabled)
|
if (s_throttler_enabled)
|
||||||
System::Throttle();
|
System::Throttle();
|
||||||
|
@ -1568,25 +1553,8 @@ 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())
|
|
||||||
{
|
|
||||||
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();
|
const bool skip_present = g_host_display->ShouldSkipDisplayingFrame();
|
||||||
Host::RenderDisplay(skip_present);
|
Host::RenderDisplay(skip_present);
|
||||||
if (!skip_present && g_host_display->IsGPUTimingEnabled())
|
if (!skip_present && g_host_display->IsGPUTimingEnabled())
|
||||||
|
@ -1594,10 +1562,6 @@ void System::ExecuteNetplay()
|
||||||
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
|
s_accumulated_gpu_time += g_host_display->GetAndResetAccumulatedGPUTime();
|
||||||
s_presents_since_last_update++;
|
s_presents_since_last_update++;
|
||||||
}
|
}
|
||||||
|
|
||||||
System::UpdatePerformanceCounters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::RecreateSystem()
|
void System::RecreateSystem()
|
||||||
|
@ -2191,7 +2155,7 @@ void System::SingleStepCPU()
|
||||||
g_gpu->ResetGraphicsAPIState();
|
g_gpu->ResetGraphicsAPIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::DoRunFrame()
|
void System::RunFrame()
|
||||||
{
|
{
|
||||||
g_gpu->RestoreGraphicsAPIState();
|
g_gpu->RestoreGraphicsAPIState();
|
||||||
|
|
||||||
|
@ -2231,7 +2195,7 @@ void System::DoRunFrame()
|
||||||
g_gpu->ResetGraphicsAPIState();
|
g_gpu->ResetGraphicsAPIState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void System::RunFrame()
|
void System::WrappedRunFrame()
|
||||||
{
|
{
|
||||||
if (s_rewind_load_counter >= 0)
|
if (s_rewind_load_counter >= 0)
|
||||||
{
|
{
|
||||||
|
@ -2242,7 +2206,7 @@ void System::RunFrame()
|
||||||
if (s_runahead_frames > 0)
|
if (s_runahead_frames > 0)
|
||||||
DoRunahead();
|
DoRunahead();
|
||||||
|
|
||||||
DoRunFrame();
|
RunFrame();
|
||||||
|
|
||||||
s_next_frame_time += s_frame_period;
|
s_next_frame_time += s_frame_period;
|
||||||
|
|
||||||
|
@ -2304,7 +2268,7 @@ void System::Throttle()
|
||||||
#endif
|
#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.
|
// 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;
|
const u32 max_frames_to_run = 2;
|
||||||
|
@ -2316,7 +2280,7 @@ void System::RunFrames()
|
||||||
if (value < s_next_frame_time)
|
if (value < s_next_frame_time)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
RunFrame();
|
WrappedRunFrame();
|
||||||
frames_run++;
|
frames_run++;
|
||||||
|
|
||||||
value = Common::Timer::GetCurrentValue();
|
value = Common::Timer::GetCurrentValue();
|
||||||
|
@ -3716,7 +3680,7 @@ void System::DoRunahead()
|
||||||
|
|
||||||
while (frames_to_run > 0)
|
while (frames_to_run > 0)
|
||||||
{
|
{
|
||||||
DoRunFrame();
|
RunFrame();
|
||||||
SaveRunaheadState();
|
SaveRunaheadState();
|
||||||
frames_to_run--;
|
frames_to_run--;
|
||||||
}
|
}
|
||||||
|
@ -4466,163 +4430,3 @@ void System::SetTimerResolutionIncreased(bool enabled)
|
||||||
timeEndPeriod(1);
|
timeEndPeriod(1);
|
||||||
#endif
|
#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;
|
struct CheatCode;
|
||||||
class CheatList;
|
class CheatList;
|
||||||
|
|
||||||
|
class GPUTexture;
|
||||||
|
class GrowableMemoryByteStream;
|
||||||
|
|
||||||
namespace BIOS {
|
namespace BIOS {
|
||||||
struct ImageInfo;
|
struct ImageInfo;
|
||||||
struct Hash;
|
struct Hash;
|
||||||
|
@ -224,12 +227,18 @@ bool LoadState(const char* filename);
|
||||||
bool SaveState(const char* filename, bool backup_existing_save);
|
bool SaveState(const char* filename, bool backup_existing_save);
|
||||||
bool SaveResumeState();
|
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.
|
/// Runs the VM until the CPU execution is canceled.
|
||||||
void Execute();
|
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).
|
/// Switches the GPU renderer by saving state, recreating the display window, and restoring state (if needed).
|
||||||
void RecreateSystem();
|
void RecreateSystem();
|
||||||
|
|
||||||
|
@ -238,7 +247,7 @@ bool RecreateGPU(GPURenderer renderer, bool force_recreate_display = false, bool
|
||||||
|
|
||||||
void SingleStepCPU();
|
void SingleStepCPU();
|
||||||
void RunFrame();
|
void RunFrame();
|
||||||
void RunFrames();
|
void PresentFrame();
|
||||||
|
|
||||||
/// Sets target emulation speed.
|
/// Sets target emulation speed.
|
||||||
float GetTargetSpeed();
|
float GetTargetSpeed();
|
||||||
|
@ -454,12 +463,6 @@ void ClearMemorySaveStates();
|
||||||
void UpdateMemorySaveStateSettings();
|
void UpdateMemorySaveStateSettings();
|
||||||
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
bool LoadRewindState(u32 skip_saves = 0, bool consume_state = true);
|
||||||
void SetRunaheadReplayFlag();
|
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 System
|
||||||
|
|
||||||
namespace Host {
|
namespace Host {
|
||||||
|
|
|
@ -1090,7 +1090,7 @@ void EmuThread::startNetplaySession(int local_handle, quint16 local_port, const
|
||||||
|
|
||||||
auto remAddr = remote_addr.trimmed().toStdString();
|
auto remAddr = remote_addr.trimmed().toStdString();
|
||||||
auto gamePath = game_path.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)
|
void EmuThread::sendNetplayMessage(const QString& message)
|
||||||
|
@ -1110,7 +1110,7 @@ void EmuThread::stopNetplaySession()
|
||||||
QMetaObject::invokeMethod(this, "stopNetplaySession", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(this, "stopNetplaySession", Qt::QueuedConnection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
System::StopNetplaySession();
|
Netplay::StopNetplaySession();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
void EmuThread::runOnEmuThread(std::function<void()> callback)
|
||||||
|
@ -1464,7 +1464,7 @@ void EmuThread::run()
|
||||||
{
|
{
|
||||||
if (Netplay::IsActive())
|
if (Netplay::IsActive())
|
||||||
{
|
{
|
||||||
System::ExecuteNetplay();
|
Netplay::ExecuteNetplay();
|
||||||
}
|
}
|
||||||
else if (System::IsRunning())
|
else if (System::IsRunning())
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue